简介:一份面向Java课程设计、数据结构学习与算法练手的黄金矿工小游戏项目,适合初学Java或正在准备GUI大作业的在校生。程序基于Java与GUI开发,经过测试可直接运行,通过完整的游戏流程展示面向对象设计、事件监听、图形绘制与碰撞检测等常用知识点,能帮助读者建立从界面交互到逻辑处理的整体认知。压缩包共28个文件,体积约1.35MB,包含5个Java源码、项目配置文件及png、jpg等图片素材,编译后的class文件与源码同时齐备,便于直接运行体验并对照源码深入学习。目前已有374人学习下载。读者拿到的是完整可运行的小游戏项目和配套资源文件,可从中梳理游戏主循环、界面绘制与资源加载的整体思路,也可在现有框架上增加关卡、道具等玩法扩展,是课程设计阶段具有参考价值的练手项目。
1. 黄金矿工小游戏(Java).zip:这份压缩包里装的是一堂完整的 Java GUI 入门课
如果你手头正好有一份“黄金矿工小游戏(Java).zip”,别急着把它和那些来路不明的安装包划等号。解压之后你多半会看到一个典型的 Java 工程布局:src 目录放着源码,res 或 img 里是金子、石头和人物贴图,可能还带一个编译好的 out 或 dist。这个小游戏本身就是用 Java Swing/AWT 写的经典抓钩玩法,它把游戏主循环、物理模拟、图片加载、事件监听和资源打包全都串到了一起,是课程设计、面试作品和练习 Java 面向对象编程的高频素材。适合刚学完 Java 基础、想补一个能跑的 GUI 项目的人,也适合准备 java 开发工程师面试题时拿来做讲稿。这一篇我会按从解压到跑通、再到改代码的顺序,把这份 zip 里的门道讲清楚,顺带告诉你哪些坑值得提前绕开。
2. 从 zip 到可运行窗口:先看目录、再编译、最后用 JDK 8 跑起来
2.1 解压后先别急着双击:目录结构决定启动方式
拿到「黄金矿工小游戏(Java).zip」,多数人第一件事是把整个压缩包解压,然后找带图标的 class 或 jar 双击。这个动作可以理解,但对 Java 项目来说成功率不高。class 文件本身不是可执行程序,它需要 JVM 加载;而 zip 里装的到底是源码包还是成品包,直接决定你接下来用 IDE 还是命令行。
常见的黄金矿工 Java 工程,解压后会有这么几块:源码目录 src,下面按 package 组织,通常是 com/game/main 或类似的多层目录;资源目录 res 或 img,存放金子、石头、钻石、人物和背景的贴图;输出目录 out 或 bin,放编译好的 class 文件;运气好的话还有一个 dist 目录,里面是打包好的 jar。如果你看到 src 里有 .java,但 out 目录是空的或不存在,说明这份 zip 给的是源码,需要你手动编译一次。
提示:先打开 zip 看根目录有没有 README 或启动说明。很多课程设计源码都会写清楚 JDK 版本和入口类,省得你晚上十点对着满屏 class 文件发呆。
我的习惯是先把整个 zip 解压到一个没有空格的路径,比如 D:\workspace\goldminer,而不是直接丢到“桌面”。原因是 javac 在部分旧版本下对路径空格处理很敏感,容易报“javac: 文件不存在”,或者在资源加载阶段拼出错误路径。后面讲图片加载时你会看到,中文路径还会带来编码问题,所以这里先留个心眼。另外,解压时尽量用系统自带压缩工具或 7-Zip 这类靠谱工具,别用某些在线解压站,zip 里如果有中文文件名,经常被解成乱码,导致源码里的 package 路径对不上。
2.2 用 javac/java 跑一个不依赖 IDE 的最小命令
在没有 IDE 或者想验证这份源码能不能独立编译时,我一般直接走命令行。先把 Java 环境变量配置好,打开命令行窗口,输入 java -version。如果显示的是 1.8.0_xxx,说明你用的是 JDK 8,这是跑这类老 Swing 项目最稳的版本。如果提示“java 不是内部或外部命令”,就得装 JDK 或者手动配置 JAVA_HOME 和 PATH。常见做法是下载一个解压版的 jdk8 zip 包,解压到 D:\jdk8,然后设置系统环境变量,好处是不用安装程序,也不影响电脑上已有的其他 JDK 版本。注意JAVA_HOME要指到 jdk 解压根目录,PATH里追加%JAVA_HOME%\bin,配完重新打开命令行窗口,环境变量才会生效。
确认环境后,用下面这组命令把项目编译到 out 目录。这是我在 Windows 上调试这类小游戏的最低成本命令:
cd /d D:\workspace\goldminer dir /s /b src\*.java javac -encoding UTF-8 -sourcepath src -d out src\game\main\GoldMiner.java java -cp out game.main.GoldMiner第一行cd /d后面跟盘符和完整路径,切到工程根目录。第二行dir /s /b src\*.java递归列出所有源码,先确认入口文件是不是真的叫GoldMiner.java,以及它所在的包路径。第三行是核心编译命令:-encoding UTF-8防止源码里的中文注释或字符串在编译时变成乱码;-sourcepath src告诉 javac 到 src 目录里找编译依赖的其余源文件;-d out把 class 输出到 out 目录。最后一行用java -cp out指定 classpath,后面跟的是主类的全限定名,也就是包名加上类名,比如game.main.GoldMiner。入口类里必须有一个public static void main(String[] args),否则 JVM 会直接报“找不到主类”。
注意:主类的包名和类名必须和源码一致。很多人把运行命令写成
GoldMiner,但实际类在game.main包下,javac 能编过,java 运行就报“找不到或无法加载主类”。查错的绝招是看源码第一行的package是什么,再照着写全限定名。这个报错在 Java 启动失败的问题里出现频率极高,多数情况不是代码错,而是类路径和主类名没对上。
如果你嫌每次输一长串麻烦,可以把编译和运行写成一个run.bat,放在工程根目录,内容就是上面四行。做课程设计答辩时双击一下就能跑,比在 IDE 里找绿色按钮更直观。成功率一旦低了,就用javac -verbose加详细输出,它会打出每个被编译的源文件路径,你能很清楚地看到编译过程卡在了哪个类上。
2.3 如果它自带 lib 或 jar:classpath 的正确写法
黄金矿工这种纯 Swing 游戏,一般不会用到第三方库,图形、事件、音频全在 JDK 里。但你保不齐遇到魔改版:有人给背景加了音乐,用到javax.sound;有人做了本地存档,用 JSON 写配置;还有人接了数据库排行榜,引了 mysql-connector。这时候 zip 里会多出一个 lib 目录,里面躺着若干个 jar。
这种工程的 classpath 就不再是单点了,编译和运行时都需要把 lib 下的 jar 全部带上。Windows 上路径分隔符用分号,Linux 和 macOS 用冒号。常见的写法是这样:
javac -encoding UTF-8 -cp "lib/*;lib" -sourcepath src -d out src/game/main/GoldMiner.java java -cp "out;lib/*" game.main.GoldMiner-cp "lib/*;lib"里的星号通配符能一次引入 lib 目录下所有 jar,注意通配符只能匹配 jar 和 zip 这类归档文件,不能匹配 class。运行时把out和lib/*都放进 classpath,JVM 会自动加载这些依赖。这个写法也适用于带依赖的小型 java 项目,我在处理服务器上用 javac 手动编译带依赖模块时,仍然在用这套逻辑。如果你把项目放进 IDEA 或 Eclipse,classpath 细节由 IDE 自动拼好,命令行这一关能过,说明你对依赖边界是清楚的,后面排查各种 Java 启动失败时会多一条思路。
3. 读懂核心玩法:抓钩摆动、抓取判定与分数增长的实现逻辑
3.1 游戏主循环:Swing Timer 和 while+Thread.sleep 的取舍
黄金矿工最核心的代码不是一个完整的业务系统,而是一条持续运转的游戏主循环。你要理解四个东西的协作关系:时钟、界面刷新、游戏状态和用户输入。常见做法是用javax.swing.Timer,每隔几十毫秒触发一次更新,然后调用repaint()重绘界面。相比while(true){ update(); Thread.sleep(30); },Swing Timer 的优势是把更新回调放到 EDT(事件分发线程)里,不会和鼠标键盘事件打架,也就不容易出现界面卡死。
用 Timer 的骨架一般是这个形状:
public class GamePanel extends JPanel implements ActionListener { private Timer timer; private int stage = 0; // 0 摆动, 1 发射, 2 回拉 public GamePanel() { timer = new Timer(30, this); // 约 33 帧/秒 timer.start(); } @Override public void actionPerformed(ActionEvent e) { updateGameState(); repaint(); } private void updateGameState() { // 更新钩子角度、长度、抓取物坐标 } }间隔30毫秒是这类游戏的常见配置,换算下来约 33 帧每秒。间隔越短,动画越流畅,但 CPU 占用也越高;间隔太长,你拖动钩子时会感觉到明显的“一顿一顿”。如果你拿到手的源码用的是Thread.sleep,注意看它有没有把入口类和Timer混用,很多卡屏问题的根源是两套更新机制同时在工作,互相抢资源。另外,updateGameState()里建议只做状态计算,不做任何耗时 IO,比如加载图片、写日志之类的事全都挪到初始化阶段,否则每帧都读磁盘,游戏会像幻灯片一样。
3.2 抓钩摆动模型:用角度和角速度代替复杂物理引擎
黄金矿工的抓钩之所以好玩,是因为它有个近似单摆的运动:钩子挂在天上,没发射时来回摆动,发射后在长度增加的同时角度基本锁定,抓住目标后钩子往回拉。很多新手上来就想着用向量物理引擎去模拟绳索,但这份小游戏最优雅的简化是只用一个角度和一个角速度,加上阻尼系数,就模拟出了摆动效果。
核心逻辑可以收敛成这么一小段:
public class Hook { double angle; // 当前角度,0 表示垂直向下 double angularVel; // 角速度,正负代表方向 double damping = 0.97; // 每次更新的阻尼系数,越小停得越快 boolean swinging = true; boolean retracting = false; double length = 80; double maxLength = 300; public void update() { if (swinging) { angularVel -= Math.sin(angle) * 0.008; angularVel *= damping; angle += angularVel; } else if (!retracting) { // 发射状态,只加长钩绳 length += 3; if (length >= maxLength) retracting = true; } else { length -= 4; if (length <= 80) { swinging = true; retracting = false; } } } }这里angularVel -= Math.sin(angle) * 0.008是重力项的简化表达:角度不为零时,重力会把钩子拉回垂直位置,0.008相当于重力加速度的缩放系数。angularVel *= damping模拟空气阻力,阻尼值越接近 1,摆动越久,反之钩子很快就停在正中央。这些参数不是拍脑袋定死的,正常人玩的感觉是钩子摆过去再摆回来正好能瞄准,太飘会让玩家烦躁,太死又不容易调整方向。你拿到源码后,可以把这几个常量抽出来做调试,看到手感不对就调,比在代码里到处翻有效得多。
发射和回拉阶段直接改长度length,发射加 3、回拉减 4,数字后面的逻辑是:回拉速度略大于发射速度,这是节奏控制的核心。如果两段速度一样,玩家会感觉拖东西回来很拖沓;如果回拉太快,抓取物会飞出屏幕,出现各种穿模。这个差值的比例,我一般习惯发拉比在 0.7 到 0.8 之间。
3.3 抓取判定:从“勾到不算数”到“能拖回来”的距离阈值
抓取判定是很多人的拦路虎:明明钩子尖看起来已经碰到金块了,但代码就是不给分。原因通常是判定用的圆和绘制用的圆不是同一个圆心,或者只比较了“刚体中心到钩尖的直线距离”,没考虑钩尖本身有半径、目标物也有一个碰撞半径。
实际的判定逻辑一般长这样:
public Object tryPickup(double hookX, double hookY, ArrayList<MineObject> objects) { for (MineObject obj : objects) { if (obj.isPicked()) continue; double dist = Math.hypot(hookX - obj.getX(), hookY - obj.getY()); double pickupRadius = obj.getRadius() + 6; // 6 是钩尖的像素半径 if (dist <= pickupRadius) { obj.setPicked(true); return obj; } } return null; }重点在obj.getRadius() + 6这行。不同矿物的尺寸差异很大,大石头画出来有 30 像素半径,小钻石可能只有 10 像素,如果不加钩尖半径,小目标即使图像相交也不算命中。Math.hypot是 Java 提供的安全求欧氏距离方法,能避免dx*dx + dy*dy在极端值下溢出,比手写Math.sqrt更稳。这里的上下文是典型的 Java 容器应用:用ArrayList<MineObject>管理场上所有矿物,循环里跳过isPicked()的对象,避免一个金块被反复抓走。
抓取之后还有一个隐藏逻辑:不同重量的矿物在回拉时速度不同。钻石轻,回拉速度几乎不变;大石头重,钩子会明显减速,甚至卡在原地。这个重量减速经常用重量系数乘在回拉增量上:
double pullSpeed = retractBaseSpeed / obj.getWeight(); offHookLength += pullSpeed;retractBaseSpeed是回拉基础速度,obj.getWeight()是矿物重量。重量为 1 的钻石直接拉,重量为 3 的大岩石拉得慢,这种设计让游戏有了策略性——先抓近处的小东西提款,还是冒险拖远处的巨石。改这个系数,就是后面调难度最直接的入口之一。
4. 改参数与加功能:把钩子速度、摆动阻尼和钻石权重调成你想要的样子
4.1 核心参数表:哪些常量值得改,改完有什么效果
拿到源码后,最让人兴奋也最容易上头的就是改参数。但要知道哪些参数可控、哪些参数改完会导致整个游戏失去平衡。我把常见的可调参数整理成一张表,方便你对号入座:
| 参数 | 常见默认值 | 影响 | 新手建议调整方向 |
|---|---|---|---|
| 摆动重力系数 | 0.008 | 摆动的加速度,越大摆得越急 | 调低到 0.005 适合初学,调高到 0.012 增加难度 |
| 阻尼系数 | 0.97 | 摆动衰减速度,越接近 1 停得越慢 | 0.95 手感偏粘,0.99 新手会晕 |
| 发射速度 | 3 | 钩子伸出去的长度增速 | 2.5 ~ 3.5 之间试 |
| 回拉速度 | 4 | 钩子收回来时长度减速度 | 建议比发射速度大 1 左右 |
| 最大钩长 | 300 | 钩子能伸到的最远距离 | 缩小到 220 提高难度 |
| 抓取半径 | 6 | 钩尖额外的碰撞半径 | 改成 10 会明显好抓,改成 3 是地狱难度 |
| 矿物重量 | 1 ~ 3 | 影响回拉速度 | 巨石改成 5 就会拖不动 |
这些值通常集中在HookUpdateService或GameConfig这种类里。如果源码里散落一堆魔法数字,我建议你第一步就是用常量替换它们,给每个数字一个名字。比如把length += 3改成length += HOOK_SHOOT_SPEED,这样改参数时不用全文搜索,也不怕改漏。这也是把别人代码变成自己作品的第一步,面试时提到这个细节,比说“我看懂了”更有说服力。
4.2 加一种新矿物:在枚举或 switch 里补类型和分值
黄金矿工原版里最常见的矿物就是普通金子、小钻石、大钻石和巨石。很多课程设计版本喜欢在里面加红宝石、祖母绿甚至炸弹。加矿物最直接的方式是找到矿物类型定义,它可能是枚举,也可能是int常量加switch分发。
假设你拿到的是枚举版本,加一个新类型并不难:
public enum MineralType { GOLD(10, 1, "images/gold.png"), DIAMOND(50, 1, "images/diamond.png"), RUBY(80, 1.5, "images/ruby.png"); // 新增 private final int score; private final double weight; private final String imagePath; MineralType(int score, double weight, String imagePath) { this.score = score; this.weight = weight; this.imagePath = imagePath; } public int getScore() { return score; } public double getWeight() { return weight; } public String getImagePath() { return imagePath; } }加了枚举还不够,你要检查三个地方:第一,矿物生成系统里有没有一个按概率决定类型的数组或switch,要把RUBY加进去,并且给它一个合适的出现权重;第二,绘制代码里是否按enum.getImagePath()加载图片,如果是独立switch的话必须补一个分支;第三,计分面板是否直接取getScore(),如果是的话不用改,但如果用case硬编码的值,就得同步更新。
注意:加高价值矿物要控制出现概率,否则玩家只盯着红宝石抓,游戏几分钟就结束了。常见做法是在生成逻辑里放一个权重数组,比如金子 50、钻石 30、红宝石 15、石头 5,每次生成前摇一个
[0,100)的随机数,落到哪个区间就生成哪种矿物。
4.3 把固定窗口改成动态尺寸:布局和坐标换算
老版本的黄金矿工 Swit 工程绝大多数写死了窗口尺寸,比如setSize(800, 600),游戏里所有物体坐标也都是基于 800x600 的绝对坐标。一旦你把窗口拉大,画面不会跟着放大,只会留下一片空背景,矿物还缩在左上角。这个问题的根源是在paintComponent里直接用了逻辑坐标,没有做坐标换算。
让界面自适应窗口的思路不复杂:把所有物体坐标保存成“逻辑坐标”,绘制时乘上一个比例因子。比如你设计时按 800x600 摆放,窗口实际是 1000x700,那么scaleX = getWidth() / 800f,scaleY = getHeight() / 600f,所有绘制坐标都先乘scaleX/scaleY。代码骨架:
@Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; float sx = getWidth() / 800f; float sy = getHeight() / 600f; g2.translate(0, 0); g2.scale(sx, sy); // 原有绘制代码保持坐标不变 drawGame(g2); }这里用g2.scale(sx, sy)是投机取巧,把整个绘制坐标系缩放,所以原绘制代码一行都不用改。但有三个注意点:第一,字体和图片也会跟着拉伸,如果sx和sy不一样,画面会变形,最好保持窗口宽高比,或者统一用Math.min(sx, sy)做等比缩放;第二,鼠标点击坐标也要做反向换算,否则你点击屏幕上的位置和游戏逻辑里的位置对不上,常见做法是把MouseEvent的坐标除以scaleX再判断;第三,setResizable(true)改成允许拉伸只是第一步,还要在组件ComponentListener里强制重绘,否则拖动窗口时内容不会及时刷新,出现黑边。
5. 避坑指南:Java 黄金矿工最容易翻车的 5 个瞬间
5.1 图片资源路径加载不到:NullPointerException 或界面黑块
现象:程序能启动,但画布上一片空白,或者矿物画出来是黑色矩形;控制台抛出NullPointerException,指向ImageIO.read或getResource。
原因:多半是用了new File("images/gold.png")这种相对路径。当你从 IDE 里运行,当前工作目录是工程根目录,没问题;但当你把 class 拷到别的地方用java -cp out运行,当前目录变了,图片路径自然失效。另一种是类文件在 jar 包里,new File根本读不到 jar 内部的资源。
解决:把图片加载改成类路径方式,也就是用getClass().getResourceAsStream("/images/gold.png")或ClassLoader.getSystemResourceAsStream。这个写法不管从 IDE 启动还是从 jar 启动都能找到资源,前提是images目录必须存在于 classpath 根下。你在打包 jar 时要注意,资源文件别只放在src下,要确保它们也被打进 jar,并且路径首字母带不带/会直接影响查找结果,建议在代码里打日志把实际 URL 打出来,比盲试快得多。
5.2 中文乱码和字体方块:编码与字体族不匹配
现象:游戏标题、按钮上的中文全部变成“锟斤拷”或者方格,英文正常。
原因:第一层是源码文件本身的编码和你编译时的-encoding不一致。很多 Windows 工程默认 GBK,你用 UTF-8 去编译,字符串全乱了。第二层是运行时字体问题,Swing 默认字体在某些精简系统里不支持中文。
解决:编译时统一用-encoding UTF-8,并且把源码文件另存为 UTF-8 编码,这是最稳妥的组合。如果源码已经是 GBK,那就得反过来,用-encoding GBK编译。字体问题建议别依赖默认字体,在启动代码里显式设置:UIManager.put("Label.font", new Font("微软雅黑", Font.PLAIN, 14)),这样至少 Windows 上是稳的。如果运行环境是 Linux 服务器,记得确认安装了中文字体,否则Font里指定的名字不存在,一样是方块。这种玄学问题,排查最快的办法是先跑一个只输出System.out.println("中文")的最小类,看控制台是否正常,再判断问题出在编译还是渲染。
5.3 游戏速度时快时慢:没有锁帧的经典症状
现象:同一份代码,在配置高的电脑上钩子飞一样快,在旧笔记本上慢吞吞;切窗口再切回来速度突变。
原因:主循环把游戏速度直接绑定在了 CPU 或屏幕刷新率上。常见写法是while(true){ update(); repaint(); }不加任何延时,也不依赖 Swing Timer;这样刷新率高的显示器会让update()跑得更频繁,游戏自然变快。
解决:统一用javax.swing.Timer控制更新间隔,或者自己记时间戳算deltaTime,把位移量乘上时间差。比如上面提到的length += 3,严格写应该是length += 3 * deltaTime / 30,其中30是期望的帧间隔。这样不管你机器跑 60 帧还是 144 帧,每秒的位移量基本一致。做完这个改动后,你会明显感觉到手感在不同机器上接近一致,这也是游戏质量里的隐形分。
5.4 打包成 jar 后双击没反应:Main-Class 与资源路径的双重问题
现象:在 IDE 里运行正常,按第 2 章的javac命令也能跑,但打成jar之后双击没有任何反应,用java -jar运行报错“no main manifest attribute”。
原因:jar 包里的META-INF/MANIFEST.MF没有声明Main-Class。很多人打包时只把编译后的 class 压缩进去,没写清单,JVM 自然找不到入口。还有一个更隐蔽的问题:资源文件没进 jar,运行后虽然主类能找到,但图片全部加载失败,黑屏一片。
解决:手动写一个MANIFEST.MF,里面至少有两行:
Manifest-Version: 1.0 Main-Class: game.main.GoldMiner然后用jar cfm GoldMiner.jar MANIFEST.MF -C out . -C res .打包,注意要把 res 目录一并放进去。实测中很多人忘了-C out .会连包名目录一起打进去,导致主类全限定名对不上,启动后同样报找不到类。这个坑我踩过好几回,现在每次打完 jar 都会先执行jar tf GoldMiner.jar,看看game/main/GoldMiner.class这个路径是否存在,路径对了再双击。
5.5 键盘没反应或按键方向反了:焦点与 KeyListener 的坑
现象:游戏界面出来了,但按方向键调整角度没反应;有时候第一次按没效果,点一下鼠标又好了。
原因:Swing 的KeyListener只有在组件获得焦点时才能收到键盘事件。如果主窗口里还有按钮或文本框,焦点可能在它们身上,游戏面板拿不到事件。另一个常见原因是代码里把左右方向写反了,比如angle -= speed当成了“左转”,但实际角度正负方向和屏幕坐标方向相反,看起来就是按左键往右摆。
解决:先在游戏面板上调用setFocusable(true),在窗口显示后调用gamePanel.requestFocusInWindow(),并且把KEY_PRESSED事件绑定到面板上而不是窗口上。方向问题建议打印angle的实际变化:按右键angle应该朝正方向增大,如果反了就把加减号对调。更优雅的替代方案是用InputMap和ActionMap,把按键映射到自定义Action上,它不依赖焦点,还能避免按键连发时卡顿。这个过程会涉及 java 基础里的Map使用,算是把容器知识用进了实际场景。
6. 把这份代码变成你自己的作品:从跑通到验收的验证清单与改造方向
当你把上面的坑都蹚过一遍后,下一步别再满足于“能跑”,要按一套验收清单确认每个功能真的符合预期。我会用这样一个单子过一遍:启动后窗口是否立即可交互,无黑屏;钩子发射前是否在摆动,并且幅度符合预期;点击鼠标后钩子是否沿当前角度发射,松开后是否停住;钩子碰到左右边界时是否反弹,碰到顶部时是否回收;抓取轻矿物时回拉速度是否不变,抓巨石时是否明显减速;分数面板是否即时刷新,达到目标分数后是否进入下一关;窗口拉伸后画面是否等比缩放,鼠标点击位置是否和钩子方向一致。这张清单既是自测,也是答辩时的演示脚本,每一项都对应一个可复现的行为,比空口说“我实现了”强。
一个值得做的进阶改造是把难度曲线抽出来。原版通常只有固定参数,越玩越无聊。我的做法是写好一个Difficulty类,内部用静态方法计算当前关卡对应的最大钩长、摆动速度、矿物权重和时间上限。例如maxHookLength = 300 - level * 10,goldWeight = 1 + level * 0.05。这样每一关都有变化,而不用每次改核心逻辑。你把这个类和GameConfig接好,AB 测试时可以通过配置文件调参数,不用改代码重新编译,这也是真实项目里常见的配置隔离思路。
最后说个我自己的教训:有一次我把红宝石的图片路径写错,图片加载失败,界面画出来是黑块,我还以为抓取逻辑写错了,调了两天。后来才发现是getResource返回null,直接ImageIO.read抛异常,游戏走进错误分支。从那以后,我每次加载资源都会先判空,并把加载失败的信息打印到控制台,绝不静默吞掉。做这类 Java 小游戏,最耗时间的往往不是游戏逻辑,而是你默认“能跑就行”的侥幸心态。希望这份清单和踩坑记录能帮你省下那些无谓的调试夜,把时间花在真正让作品亮眼的功能上。
本文还有配套的精品资源,点击获取