Java黄金矿工游戏开发实战:从Swing到碰撞检测

发布时间:2026/10/8 5:15:59
Java黄金矿工游戏开发实战:从Swing到碰撞检测 简介一份基于Java实现的黄金矿工小游戏完整项目包面向正在学习Java语法、面向对象与GUI开发的初学者也适合作为高校课程设计或游戏开发入门练手素材。项目把矿工、金矿、钩子等游戏要素抽象为独立对象完整演示了类设计、Swing界面搭建、监听器事件处理、多线程并发更新、定时器驱动的动画效果以及文件读写保存进度等核心知识点。压缩包共30个文件除6个java源文件外还包含9个class编译产物、可直接运行的jar包、3个mf清单配置以及多张png/gif/jpg游戏素材图和README说明文档整体大小约240KB。目录沿用GoldMiner-main工程结构导入IDE即可查看源码、运行并调试。已有1187人学习下载通过该工程可以直观理解钩子抛抓、矿车移动、金矿收集、计分与关卡推进等游戏逻辑的实际编码方式同时也能为二次功能扩展或撰写实验报告提供参考。1. 这个 zip 里装的不只是游戏Java 黄金矿工为什么值得你一帧一帧抠拿到“Java实现黄金矿工.zip”多数人的第一反应是解压找 exe找不到就开始怀疑人生。这个 zip 里装的是一份 Java 源码工程黄金矿工是典型的小型 2D 游戏用 Swing/AWT 写窗口程序。它的价值不在“好玩”而在它把你学过的 Java 基础串起来了面向对象设计游戏角色、状态机管理流程、三角函数算钩爪位置、碰撞检测判断抓取、事件分发线程处理交互。很多 java 面试题爱问线程、集合、封装继承但真让你说项目时就卡住这类小游戏正是拿来当项目经验的起点。适合两类人想补课设/面试项目的 Java 初学者和想一周内快速练手 Swing 绘制的在职工程师。下面按“跑起来 → 拆机制 → 调手感 → 避坑 → 讲出去”的路线往下走。2. 先把工程跑起来从 zip 到可玩窗口的 20 分钟2.1 环境与工具JDK 版本和 IDE 的选择为什么决定你后面的命运很多同学拿到 zip 第一件事是双击解压然后直接找 exe找不到就开始慌。这个 zip 里是 Java 源码不是安装包所以你得先配好 JDK。JDK 8 还是 JDK 17黄金矿工这种 Swing 项目JDK 8 能跑JDK 17 也能跑差别主要在模块化。我一般建议直接装 JDK 8省事打开环境变量配置页面新建 JAVA_HOME 指向 JDK 解压目录再把 %JAVA_HOME%\bin 加到 Path 里命令行输入java -version能看到版本才算配好。如果你手头只有 JDK 17 的安装包也不是不行但要注意 javax.swing 在 JDK 9 以后被收进了 java.desktop 模块如果你的工程里有 module-info.java必须加一句requires java.desktop;否则编译直接报“程序包 javax.swing 不可见”。这一条就够很多人在环境上卡两小时。IDE 我推荐 IDEA社区版免费够用。新建工程时不用勾 Maven因为黄金矿工这种课设级项目一般没有第三方依赖纯 JDK 就能编译。IDEA 里打开 zip 解压出来的文件夹它会自动识别源码结构。注意一点工程里如果出现 pom.xml 就是 Maven 工程出现 build.gradle 就是 Gradle 工程都没有就是最原始的 javac 工程。这个项目大概率是第三种所以你也不需要等 Maven 下载依赖打开就能编译。2.2 解压与工程导入先看清目录结构再点运行解压后先别急着双击花两分钟看目录。常见做法是里面有 src 源码目录、res 或 images 资源目录可能还有一个 README 文件。src 下会按包名分层比如 com.goldminer 下有 GameMain、GamePanel、Mineral 这些类。资源目录里一般是金块、钻石、石头的图片以及背景图。如果图片资源丢了程序能跑但画面是空的后面避坑章会专门说。IDEA 导入方式File - Open选中解压出来的根目录如果弹出 Trust Project 对话框选 Trust。导入后展开 src 目录找到带public static void main的类类名一般叫 Main、GameStart 或 GameMain。右键那个类选 Run GameMain.main()窗口就应该弹出来了。如果项目里有多个 main 方法比如有人写了工具类也带 main你得认准游戏入口别一上来就 Run 了一个打印测试的类然后跑来问为什么没画面。2.3 跑通主类的两种方式IDE 一键运行与命令行编译没有 IDE 的时候命令行也能跑。先确认当前目录在项目根目录然后执行# 编译把 src 下所有 .java 编译到 out 目录-encoding 防止中文注释乱码报错 javac -encoding UTF-8 -sourcepath src -d out $(find src -name *.java) # 运行以 out 为 classpath指定完整主类名 java -cp out com.goldminer.GameMain第一条命令里的-encoding UTF-8必须带上很多课设源码注释是中文不带会报“编码 GBK 的不可映射字符”。-sourcepath src是指定源码根目录-d out指定编译产物输出目录。最后一段$(find src -name *.java)是 Linux 和 macOS 的写法Windows 的 cmd 不认得用 PowerShell 的Get-ChildItem -Recurse src -Filter *.java | % FullName或者干脆打开 cmd 进到 src 目录后用javac -encoding UTF-8 -d out $(dir /s /b *.java)。第二种运行方式把主类换成你在代码里看到的实际包名和类名这里com.goldminer.GameMain只是常见命名以源码为准。2.4 运行入口main 方法里到底发生了什么黄金矿工是个窗口程序main 方法通常很短只有几行创建窗口、设置大小和关闭行为、把游戏面板塞进去、显示窗口。代码大概是这个样子public class GameMain { public static void main(String[] args) { // 创建主窗口标题就叫“黄金矿工” JFrame frame new JFrame(黄金矿工); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(800, 600); frame.setResizable(false); // GamePanel 是真正的游戏画布所有绘制和逻辑都在这 frame.add(new GamePanel()); // 窗口居中显示 frame.setLocationRelativeTo(null); frame.setVisible(true); } }这里有几个决定性的点setResizable(false)很重要黄金矿工的所有坐标都是按固定窗口算的一旦拉伸窗口鼠标点击位置和绘制坐标就对不上钩爪会“打偏”。setDefaultCloseOperation必须用EXIT_ON_CLOSE不然关掉窗口 JVM 不退进程还占着你会觉得“关了怎么还在”。frame.add(new GamePanel())执行后Swing 才会把这个面板的paintComponent当作绘制入口后面所有游戏画面都从这个方法里画出来。3. 黄金矿工的核心机制拆解抓取、摆动与拉升的代码实现3.1 游戏循环Swing Timer 还是 while Thread.sleep游戏循环是这种小游戏的灵魂。Swing 里有两个选择javax.swing.Timer或者自己开线程while (true) { update(); repaint(); Thread.sleep(16); }。我推荐 Timer因为 Swing 的绘制必须在事件分发线程EDT上执行Timer 的回调正好跑在 EDT 上改完状态直接repaint()不会出现并发问题。而 while Thread.sleep 在子线程里改游戏状态虽然repaint()是线程安全的但状态读取和绘制之间容易产生竞态表现就是画面一会快一会慢这是典型的“玄学卡顿”。public class GamePanel extends JPanel implements ActionListener { private Timer timer; private int frameInterval 16; // 约 60 FPS public GamePanel() { // 每 16ms 触发一次 actionPerformed timer new Timer(frameInterval, this); timer.start(); } Override public void actionPerformed(ActionEvent e) { // 更新角度、位置、金钱等游戏状态 updateGame(); // 请求 Swing 重绘paintComponent 稍后会被调用 repaint(); } }frameInterval是帧间隔单位毫秒16 对应理论 60 FPS。这里有一个常见误用把updateGame()写得很重导致一次回调超过 16ms实际帧率就掉下去了。所以updateGame()里不要做文件读取、不要 new 对象只用数学计算和简单状态翻转。后面讲到拉升时你会看到真正的物理计算就几行这才符合游戏循环的轻量要求。3.2 钩爪的摆动角度、速度与按键响应黄金矿工的手感全在钩爪摆动上。常见实现是记录一个angle角度每帧按角速度改变它碰到左右边界就反向。按住方向键时角速度可以加快松开就恢复默认。钩爪顶端坐标用三角函数算这里开方和三角函数虽然比加减法慢但每帧只算一次完全够用// 钩爪摆动参数 private double angle 90.0; // 角度90 度表示竖直向下 private double swingSpeed 1.2; // 每帧摆动的度数 private double maxAngleLeft 30; // 允许摆到的最左角度 private double maxAngleRight 150; // 允许摆到的最右角度 private int hookLength 60; // 绳子露出长度像素 public void updateSwing(boolean leftPressed, boolean rightPressed, boolean downPressed) { if (!downPressed) { // 没有按“下”时钩爪才摆动 if (leftPressed) { angle swingSpeed * 1.8; // 按键时摆得快一点 } else if (rightPressed) { angle - swingSpeed * 1.8; } else { angle swingSpeed; // 默认自动摆向右边 } // 碰到边界就停在边界不反向也行下次按键会走回来 if (angle maxAngleRight) { angle maxAngleRight; } if (angle maxAngleLeft) { angle maxAngleLeft; } } } public int getHookX() { return (int) (centerX hookLength * Math.sin(Math.toRadians(angle))); } public int getHookY() { return (int) (centerY hookLength * Math.cos(Math.toRadians(angle))); }这里角度用弧度很容易出错我习惯全部用度数只在计算三角函数时用Math.toRadians转换一次。leftPressed那行让按键时角速度从 1.2 变成 2.16具体倍速你用 1.5 到 2.0 都行关键是不能让钩爪在按键时瞬间转向要保留一点摆动惯性。maxAngleLeft和maxAngleRight是按数学坐标系算的0 度朝右90 度朝下左边界 30 度右边界 150 度实际窗口里表现就是钩爪从左上往右下摆一个扇形。如果你的窗口是 800x600这个范围看起来正好。3.3 抓取判定圆与矩形/图片的碰撞检测抓取判定本质是碰撞检测。黄金矿工的矿物大多是矩形图片钩爪尖端是一个点最朴素的做法是把钩尖当作一个半径很小的圆和矿物的矩形做相交检测// 圆形钩尖与矩形矿物碰撞检测 public boolean hitTest(int hookX, int hookY, int hookRadius, int objX, int objY, int objWidth, int objHeight) { // 找到矩形上离钩尖最近的点 int closestX clamp(hookX, objX, objX objWidth); int closestY clamp(hookY, objY, objY objHeight); int dx hookX - closestX; int dy hookY - closestY; return (dx * dx dy * dy) (hookRadius * hookRadius); } private int clamp(int val, int min, int max) { return Math.max(min, Math.min(max, val)); }这段代码先找矩形边界上离钩尖最近的点再算两点距离平方小于钩尖半径的平方就认为抓住。用平方不用开方省掉Math.sqrt虽然只是微秒级但在每帧要检测十几个矿物时积累起来也不小。实际项目里矿物图片往往有透明边按图片宽高检测会“空气抓取”就是钩尖离图片还有好几个像素就抓住了看起来像隔空取物。常见做法是把碰撞矩形往内缩几个像素原版黄金矿工的抓取判定也比图片边缘小一圈这是故意给你手感的宽容度所以矿物对象里最好有hitWidth和hitHeight字段专门存缩水后的碰撞尺寸。3.4 拉升与抓取物弹簧模型和背包价值抓到矿物后游戏进入拉升状态。状态机非常朴素SWINGING摆动、EXTENDING伸出、RETRACTING收回。按下“下”键后从SWINGING切到EXTENDING让钩爪朝当前角度飞出去碰到矿物后切到RETRACTING钩爪带着矿物往回走回到顶部后把矿物价值加进总金额再切回SWINGING。拉升的物理是一个简化的弹簧模型速度会衰减代码长这样public void updateRetract() { // 当前钩尖到锚点顶部转轴的直线长度 double len Math.sqrt((hookX - centerX) * (hookX - centerX) (hookY - centerY) * (hookY - centerY)); // 越重的矿物回收越慢这是黄金矿工经典手感来源 double targetSpeed 8.0 / (1.0 attachWeight * 0.01); // 让当前回收速度向目标速度靠拢产生弹性阻尼 retractSpeed (targetSpeed - retractSpeed) * 0.1; len - retractSpeed; if (len hookLength) { len hookLength; state GameState.SWINGING; totalGold attachValue; // 矿物价值入账 attachObject null; } // 按当前角度重新算钩尖位置 hookX (int) (centerX len * Math.sin(Math.toRadians(angle))); hookY (int) (centerY len * Math.cos(Math.toRadians(angle))); }这段的关键是targetSpeed的公式权重越大目标速度越小钻石和石头的回收手感差别就出来了。0.01 这个系数你可以调成 0.02数字越大重量对速度的影响越明显。后面的 0.1 是阻尼系数它让速度不会突变如果去掉钩爪会以恒定速度回收看起来像瞬移一样生硬。很多新手在这块直接搞匀速收回玩起来就像抓了个假金子完全没有“拽着沉东西往上拉”的手感。顺着这个思路你还能给不同矿物配不同的weight和value这其实就是面向对象编程 java 里最朴素的用法矿物是一个类金块、钻石、石头是它的实例各自有重量和价值字段。4. 让画面不闪、手感不飘绘制与物理参数的调优4.1 双缓冲绘制为什么画面还会闪烁Swing 的 JPanel 默认开了双缓冲setDoubleBuffered(true)是默认值所以理论上画面不该闪。实际闪起来多半是写法问题有人把背景图和矿物都用JLabel直接add到面板上再在paintComponent里画绳子和钩爪两种绘制机制混在一起重绘顺序一乱就闪。另一个高频问题是不调用super.paintComponent(g)导致上一帧的画面残留拉绳子的时候满屏都是绳子的残影。Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 先清背景避免拖影 Graphics2D g2 (Graphics2D) g; // 打开抗锯齿线条和圆形边缘更平滑 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); drawBackground(g2); drawRope(g2); drawHook(g2); drawMinerals(g2); drawScore(g2); }super.paintComponent(g)这行是黑板擦先把面板清成背景色再画新内容。抗锯齿的RenderingHint开关只影响绘制质量不影响逻辑但如果你的矿物图片本身是像素风关掉反而更锐利。drawBackground到drawScore这些方法你自己拆原则是按图层从底往上画背景最底、矿物中间、绳子和钩爪最上、分数最顶。4.2 关键参数表一套能直接抄的手感基线如果引擎盖下的物理模型都建好了剩下来就是调参数。没有标准答案但我可以给你一套启动基线在这个基础上按自己手感微调参数推荐值调大后的效果调小后的效果摆动角速度1.2 度/帧钩爪来回快不好瞄准钩爪发飘等得着急按键摆动倍速1.8按键转向灵活转向迟钝错过矿物最大左/右角度30 / 150 度摆动范围大覆盖范围小钩爪初始绳长60 像素伸出去离锚点远回收后离锚点近伸出速度7 像素/帧下探快容错低下探慢容易瞄歪最大伸出长度700 像素能抓全屏远处矿物抓不到底部矿物回收基础速度8 像素/帧收得快手感轻收得慢手感重重量影响系数0.01重量差异明显所有矿物回收速度接近调参有个规律先调伸出速度和回收基础速度这两个决定“抓不抓得到”再调角速度和按键倍速这两个决定“瞄得准不准”最后调重量系数决定“玩起来有没有意思”。每次只调一个参数存档备份代码后再改不然你调完发现手感变差了想回退都不知道哪个参数动过。4.3 手感调优用固定时间步长消除帧率波动Timer 设 16ms但在不同机器上实际触发间隔会波动尤其后台开了别的东西逻辑速度就会忽快忽慢钩爪摆着摆着突然抽一下。解决办法是固定时间步长别用“每帧固定走多少像素”而是按累计时间决定走多少步。代码长这样private long lastTime System.nanoTime(); private double accumulator 0; private final double STEP 1_000_000_000.0 / 60.0; // 固定 60 帧的逻辑步长 Override public void actionPerformed(ActionEvent e) { long now System.nanoTime(); accumulator now - lastTime; lastTime now; // 按固定步长更新帧率再波动逻辑速度也一致 while (accumulator STEP) { updateGame(); accumulator - STEP; } repaint(); }accumulator累加两次回调之间的真实时间达到一个STEP就执行一次updateGame()。即使实际帧率掉到 30逻辑也还是按 60 步每秒在跑表现出来就是游戏略微变慢而不是“飘”。这个在避坑章还会提到很多“越跑越快”的根源就是没用这种增量逻辑。5. 避坑手册Java 黄金矿工最常见的 5 个翻车现场5.1 现象解压后源码全是乱码编译报“编码 GBK 的不可映射字符”原因课设源码多半是在 Windows 中文系统下用 GBK 编码保存的而你用 IDEA 的 UTF-8 打开所有中文注释和字符串全变乱码编译时还会爆编码错误。解决IDEA 右下角点编码栏选 GBK弹出提示选“Convert to UTF-8”让 IDEA 把文件转码命令行编译时像我第 2 章那样加-encoding UTF-8或者-encoding GBK取决于源码实际编码。判断源码是 GBK 还是 UTF-8最简单的办法是用记事本打开看中文注释是否正常。5.2 现象程序能启动但窗口里一片白或一片黑看不到矿物和绳子原因多数情况是图片资源路径没加载到ImageIO.read(new File(res/gold.png))用了相对路径而运行时工作目录不是项目根目录导致资源读取失败矿物和背景全是 null绘制方法一画就空指针或跳过。解决先把所有ImageIO.read(new File(...))改成通过类路径加载把图片放到 src 下的 resources 目录然后用getClass().getResource(/images/gold.png)去读。这样不管从 IDE 跑还是命令行java -cp out跑只要 classpath 里有资源文件就不会丢。这个改动要趁早等到画面上全是空白才开始找资源路径会浪费很多时间。5.3 现象钩爪明明碰到了金块却没有抓住直接穿过去原因碰撞检测的矩形用的是图片宽高图片四周有透明区域视觉上碰上了但透明区不算碰撞或者反过来钩尖判定矩形太大离金块还有一段距离就抓住了看起来像隔空取物。解决给你每个矿物对象加hitWidth和hitHeight在初始化时按图片宽高乘以 0.7 左右赋值。碰撞检测用这两个收缩后的值而不是图片原始宽高。缩多少取决于图片里的透明边有多大你自己量一下在绘图时临时把碰撞矩形画出来调试。// 调试手段把碰撞矩形画成半透明红色方便看判定区域 g2.setColor(new Color(255, 0, 0, 100)); g2.fillRect(obj.getHitX(), obj.getHitY(), obj.getHitWidth(), obj.getHitHeight());调试完记得删掉或者用开关关掉不然画面里全是红方块。5.4 现象游戏越跑越快或者摆着摆着角度突然跳一下原因逻辑更新没有按真实时间做增量。比如每帧把速度 0.5帧率越高速度涨得越快帧率一波动就会跳。这是典型的“把帧当时间”的问题没做固定时间步长也没有按System.nanoTime()算 delta。解决按 4.3 的固定步长方案重写actionPerformed。核心原则是所有移动量的单位都写成“像素每逻辑步”而不是“像素每帧”。如果主循环已经是固定步长那矿物位置、绳子长度这些更新就得全部用步长做单位不能一边固定步长一边还用“每帧加 1”的写法那就白固定了。5.5 现象打包成 jar 后双击没反应命令行 java -jar 报“没有主清单属性”原因jar 包的 MANIFEST.MF 里没有Main-Class或者写了类名但没写Class-Path资源文件也没打进去。解决用 IDEA 的 Artifacts 功能打包时手动指定 Main-Class。如果你更习惯命令行用jar命令手动建清单# 把 MANIFEST.MF 放在项目根目录内容写上 Main-Class 和 Class-Path jar cvfm goldminer.jar MANIFEST.MF -C out .MANIFEST.MF 里Manifest-Version: 1.0 Main-Class: com.goldminer.GameMain打包后先别双击命令行java -jar goldminer.jar跑一次看有没有真正的报错信息。双击运行时错误是直接弹对话框消失你什么都看不到命令行能看到完整堆栈这是唯一的排查路径。6. 从课设到面试怎么把它讲成亮点再往上加一档6.1 加一个能写进简历的功能关卡与难度曲线基础版跑通只是及格线想让它成为面试里能聊十分钟的项目加一个“关卡难度曲线”。逻辑很简单每抓够目标金额进入下一关每关的矿物分布偏向更重更难抓的石头同时钩爪的最大伸出长度缩窄一点。代码上就是在GamePanel里加一个level字段生成矿物时根据level改变矿物类型权重难度参数用表格里那几个值的插值计算。这个功能改动量不大但能体现出你有“游戏节奏设计”的意识面试官一听就知道你不是只会抄代码。6.2 验证方法用日志和截图留下证据项目做完做几个固定场景的验证开局默认状态下抓一个金块、按住左/右键看摆动范围、连按“下”键伸到最底。每个场景截一张图把分数变化和控制台输出对齐存进项目里的docs目录。写上一份简单的 README说明运行环境、主类位置、操作方式这样无论你过两个月回来看还是面试官拿到你的代码都能三分钟跑起来。很多人的课设代码过三个星期自己都跑不起来就是因为没有任何说明你补上这一步就已经超过大半的同学了。6.3 面试怎么讲从“我做了个游戏”到“我解决了这些问题”面试官听项目最怕听到的就是“我用了 Swing 写了个黄金矿工”。你要讲的是决策过程不是功能列表。比如为什么用 Timer 而不用 while Thread.sleep这是线程模型问题。为什么碰撞矩形要收缩这是用户手感和视觉误差的取舍。为什么回收速度要按重量衰减这是一个简单的物理模拟。这些点正好对应 java 面试题里常考的线程安全、面向对象设计、边界条件处理。如果被问到性能你就说逻辑循环里避免创建对象、碰撞检测用平方比较避免开方这两句足够展示你不是黑匣子使用者。按照这个思路这个项目就不再是“课设小游戏”而是一个能证明你基础扎实的样本。这个项目做下来我最大的教训是别急着写代码先花半小时把窗口、画布、状态机三个骨架立住后面所有功能都是往骨架里填肉。如果你一上来就抠钩爪摆动的三角函数大概率写到一半发现窗口初始化写错了又得推翻。先把最小可玩版本跑通再一层层加功能这样每次改动都有验证点。希望帮到你。本文还有配套的精品资源点击获取