Android Studio Java版打地鼠游戏源码包(含完整UI、逻辑与素材)
简介:直接导入Android Studio即可运行的打地鼠小游戏工程,纯Java开发,不依赖Kotlin或第三方游戏引擎。项目包含主界面Activity、游戏控制逻辑类、地鼠行为管理类三大核心Java文件;XML布局覆盖启动页、游戏主面板、计分栏、倒计时显示及结束弹窗;配套10张WebP格式资源图,包括不同状态的地鼠头像、锤子图标和背景图;配置文件齐全,含AndroidManifest.xml、模块级和项目级build.gradle、混淆规则proguard-rules.pro等。功能完整支持点击击中判定、实时计分、60秒倒计时、难度随时间提升(地鼠出现间隔缩短)、游戏结束自动统计得分并提示重玩。代码结构清晰,变量命名规范,无冗余注释,适合Android初学者理解事件响应机制、Handler主线程更新UI、Random随机逻辑实现,也适合作为移动应用开发课程设计或毕业设计基础模板使用。
1. 项目概述:为什么这个打地鼠源码值得你花30分钟认真看一遍
我带过六届移动应用开发课,每年都有学生卡在“怎么让UI动起来”这一步——不是不会写Button点击事件,而是搞不清“点击之后,分数怎么实时变?地鼠怎么随机钻出来?倒计时怎么不卡主线程?”这套Android Studio Java版打地鼠游戏源码,就是我专门拆解了上百个初学者作业后,亲手重写的“教学级最小可行游戏”。它不炫技、不堆框架,就用最朴素的Android原生组件(TextView、ImageView、LinearLayout)和最基础的Java机制(Handler、Runnable、Random),把一个完整小游戏的骨架、血肉、神经全都摊开给你看。关键词里提到的“打地鼠游戏”“Android Studio”“Java安卓游戏”,不是标签,是它的DNA:整个工程从创建到运行,全程零Kotlin、零Jetpack Compose、零第三方游戏引擎(比如LibGDX或Unity),连图片都只用系统原生支持的WebP格式,确保你在任何一台装了Android Studio的电脑上,双击打开就能跑,点开任何一个Java文件就能读懂逻辑。
它解决的不是“怎么做一个酷炫游戏”的问题,而是“怎么让一个Android新手第一次真正理解‘事件—逻辑—UI’闭环”的问题。比如,你点中地鼠时,代码里没有一行是直接scoreView.setText(score + "")这种危险操作——它一定走Handler发消息回主线程更新;再比如,地鼠出现的位置不是靠猜,而是用Random.nextInt(3)在3×3网格里算出坐标,再通过findViewById(R.id.mole_1_1)精准控制那个格子的ImageView可见性。这些细节,文档里不会写,但源码里全都有。如果你正在准备课程设计、毕业设计,或者刚学完Activity生命周期想找个实战练手,这个包就是你的“第一块砖”:它不大,但每一块砖都砌得方正;它不新,但每一行代码都在回答“为什么这么写”。
2. 整体架构与设计思路:三层结构如何撑起一个游戏的呼吸感
2.1 为什么是“主Activity—游戏控制器—地鼠管理器”三层,而不是全塞进一个Activity?
很多初学者一上来就把所有代码堆在MainActivity.java里:点击逻辑、计时逻辑、地鼠状态、分数计算全混在一起。结果就是改一行分数显示,不小心删掉了倒计时的Runnable;调一下地鼠出现间隔,整个游戏面板就崩了。这套源码用三个独立Java类强行划清边界,不是为了“看起来高大上”,而是为了解决一个真实痛点:当游戏逻辑变复杂时,你能快速定位问题在哪一层。
- MainActivity 是“前台接待员”:只负责界面搭建(setContentView)、资源绑定(findViewById)、用户输入接收(setOnClickListener)和最终结果展示(弹窗、分数文本)。它不关心“地鼠该不该钻出来”,只管“用户点了,我告诉游戏控制器一声”。
- GameController 是“游戏大脑”:它持有当前分数、剩余时间、难度系数等全局状态,接收MainActivity发来的点击事件,调用MoleManager判断是否击中,并决定何时触发下一轮地鼠出现。它像交通指挥中心,所有信号灯(地鼠)的开关指令都从这里发出。
- MoleManager 是“地鼠调度员”:它不存分数、不管时间,只专注一件事——管理9个地鼠对象的状态(隐藏/出现/被击中)、计算它们的出现位置和持续时间、提供“哪个地鼠被点了”这样的查询接口。它甚至不知道自己在哪个Activity里运行,只认ID和状态。
这种分层不是教条,是踩坑踩出来的。我试过把MoleManager的逻辑硬塞进GameController,结果一次修改导致地鼠出现频率错乱,排查了两小时才发现是Random种子被重复初始化了。分开后,MoleManager里所有Random操作都封装在自己的构造方法里,GameController只管调moleManager.spawnNextMole(),出了问题,直接去MoleManager里查,不用翻遍整个Activity。
2.2 XML布局为何要拆成12个文件?而不是一个activity_main.xml搞定所有?
看到目录里有12个XML文件,新手常觉得“太啰嗦”。但实际打开你会发现:activity_main.xml只放顶层容器(ConstraintLayout)和占位符;layout_game_panel.xml专管3×3地鼠网格;layout_score_bar.xml只负责分数和时间显示;连游戏结束弹窗都是独立的dialog_game_over.xml。这种拆分,核心目的是复用性与可维护性。
举个例子:你想给计分栏加个动画效果,比如分数增加时文字放大闪烁。如果所有UI都在activity_main.xml里,你得先找到TextView的id,再写Animation,再找对父容器——稍不注意就影响了地鼠网格的约束关系。而用独立的layout_score_bar.xml,你只需在这个文件里改,include标签会自动把它嵌入主界面,动画逻辑完全隔离。再比如,后续想加音效开关按钮,直接新建layout_sound_toggle.xml,在activity_main.xml里<include>进来就行,不用动原有结构。这12个文件,每个都是一个“乐高积木”,你可以单独测试、单独替换、单独优化,而不必担心牵一发而动全身。
2.3 WebP素材的选择:为什么不用PNG,也不用SVG?
项目里10张图全是WebP,包括地鼠头像(normal/hit/miss三种状态)、锤子图标、背景图。这不是跟风,是经过实测的取舍。PNG虽然兼容性好,但体积比WebP大30%~50%;SVG在Android上渲染复杂图形(比如带阴影的地鼠头像)容易模糊或失真,且不支持逐帧动画状态。WebP在这里刚好卡在平衡点:它被Android 4.0+原生支持,压缩率高(整个drawable目录才480KB),还能无损存储透明通道,最关键的是——它支持简单动画。源码里地鼠被击中时的“缩放+变灰”效果,就是用WebP动画帧实现的,比写ObjectAnimator代码更轻量。你打开res/drawable/mole_hit.webp,用支持WebP的看图软件就能看到3帧动画:正常→击中→消失。这种“图片即动画”的设计,让UI反馈更自然,又不增加Java层的复杂度。
3. 核心代码解析:从点击到得分,每一行都在教你Android底层逻辑
3.1 MainActivity:UI层的“守门人”,如何安全地把用户操作递给逻辑层
MainActivity.java只有187行,但它干了三件关键事:初始化、事件绑定、结果处理。我们重点看点击事件的传递链:
// 在onCreate()里,为9个地鼠ImageView绑定同一个点击监听器
for (int i = 0; i < 9; i++) {
ImageView moleView = findViewById(moleIds[i]);
moleView.setOnClickListener(v -> {
// 关键:不直接处理逻辑,而是通知GameController
gameController.onMoleClicked(v.getId());
});
}
这里有个易错点:很多新手会在这里直接写updateScore(10),然后发现分数有时不刷新。原因在于,setOnClickListener的回调是在主线程,但gameController.onMoleClicked()内部可能触发耗时操作(比如播放音效),如果它没做线程保护,就会卡UI。源码的解决方案是——GameController里所有UI更新操作,必须通过Handler发消息:
// GameController.java 内部
private Handler mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_SCORE:
// 这里才真正更新UI,确保在主线程
scoreView.setText("分数:" + currentScore);
break;
}
}
};
所以,MainActivity只是“递话筒”,GameController才是“说话的人”。这种设计强制你理解Android的线程模型:UI操作只能在主线程,但逻辑计算可以放在后台,再安全地切回来。你甚至可以在GameController里加个new Thread(() -> { /* 耗时计算 */ }).start(),只要最后用mainHandler.sendMessage()通知UI,就不会ANR。
3.2 GameController:游戏状态的“中央处理器”,难度递增怎么做到平滑不突兀
GameController的核心变量就四个:currentScore、remainingTime、difficultyLevel、isGameRunning。难点在于difficultyLevel的动态调整。源码没用简单的“每10秒level++”,而是用指数衰减公式控制地鼠出现间隔:
// 初始间隔:2000ms(2秒)
private long baseSpawnInterval = 2000;
// 难度系数:随游戏时间增长,但增速越来越慢
private double difficultyFactor = 1.0;
// 每次spawn前计算当前间隔
private long getCurrentSpawnInterval() {
// 公式:interval = base / (1 + difficultyFactor * timeElapsedInSeconds)
return (long) (baseSpawnInterval / (1 + difficultyFactor * (60 - remainingTime)));
}
为什么用这个公式?因为纯线性递增(比如每秒减50ms)会导致后期间隔变成负数,游戏崩溃;而指数衰减能保证间隔永远大于0,且前期变化小(前20秒只缩短200ms),后期变化快(最后10秒缩短1200ms),玩家能明显感觉到“越来越难”,但不会突然从2秒跳到0.3秒手忙脚乱。这个difficultyFactor值是实测调出来的:设为0.8时,60秒内间隔从2000ms降到约320ms,既保证挑战性,又留出反应时间。你可以在onCreate()里改这个值,立刻看到难度曲线变化——这是理解“参数驱动行为”的绝佳案例。
3.3 MoleManager:地鼠行为的“精密钟表匠”,如何让9个地鼠各司其职又互不干扰
MoleManager.java是代码最“硬核”的部分,它用一个Mole[] moles数组管理9个地鼠对象。每个Mole对象不是简单布尔值,而是包含完整状态机:
public class Mole {
public enum State { HIDDEN, APPEARING, VISIBLE, HIT, MISSED }
public State currentState;
public long appearTime; // 出现时间戳,用于计算超时
public int row, col; // 所在网格坐标
}
关键逻辑在spawnNextMole()方法:
public void spawnNextMole() {
// 1. 随机选一个未被占用的地鼠格子
int index = getRandomAvailableMoleIndex();
if (index == -1) return; // 全占满了,跳过
// 2. 设置状态和时间戳
moles[index].currentState = State.APPEARING;
moles[index].appearTime = System.currentTimeMillis();
// 3. 通过Handler延迟切换到VISIBLE状态(模拟钻出动画)
handler.postDelayed(() -> {
moles[index].currentState = State.VISIBLE;
// 此时才让ImageView显示
updateMoleView(index, State.VISIBLE);
}, 300); // 钻出动画耗时300ms
}
这里藏着两个重要技巧:一是getRandomAvailableMoleIndex()不是简单Random.nextInt(9),而是遍历数组找currentState == HIDDEN的索引,避免重复选中已出现的地鼠;二是用postDelayed而非Thread.sleep(),因为后者会阻塞当前线程,而Handler的延迟是异步的,不影响其他地鼠的调度。你甚至可以注释掉postDelayed里的updateMoleView,只留moles[index].currentState = State.VISIBLE,然后在updateMoleView()里加日志,就能清晰看到“状态变更”和“UI更新”的分离——这正是MVVM模式的雏形,只是用最原始的方式呈现。
4. 实操部署与调试指南:从导入到真机运行的避坑全流程
4.1 Android Studio导入四步法:为什么不能直接“Open”而要“Import Project”
新手常犯的错误是双击build.gradle或直接“Open”项目根目录,结果报一堆Could not find method compile() for arguments [...]。正确流程是:
- 启动Android Studio → “Open an existing Android Studio project”
(不是“Open”或“Import from Version Control”) - 选择项目根目录(含gradlew.bat和settings.gradle的文件夹)
(注意:不是选src/main,也不是选nXBZiybN7FlPTBJ5LOaf-master-57f80af3a6b1d65aef6d011802a666f52b69b70d这种带哈希的子目录) - 等待Gradle同步完成(右下角提示“Gradle sync finished”)
(若卡住,检查是否勾选了“Offline work”,取消即可) - 连接设备或启动模拟器 → 点击绿色三角形运行
提示:如果遇到
Failed to resolve: androidx.appcompat:appcompat:1.6.1,说明网络下载依赖失败。此时不要急着换镜像,先点Android Studio菜单栏 File → Project Structure → SDK Location,确认Android SDK路径正确;再点 File → Invalidate Caches and Restart → Invalidate and Restart。90%的依赖问题重启缓存就能解决。
4.2 真机调试必备设置:三处隐藏开关让你告别“安装失败”
在手机上运行前,必须打开三个开关,否则会卡在“Installing APK…”:
- 开发者选项:连续点击“关于手机”里“版本号”7次激活;
- USB调试:在开发者选项里开启;
- 安装未知应用:进入“设置 → 应用 → 特殊访问权限 → 安装未知应用 → 选择你的IDE(如Android Studio)→ 开启”。
注意:华为/小米手机还有额外限制。华为需在“开发者选项”里关闭“仅允许安装来自华为应用市场的应用”;小米需在“开发者选项”里关闭“MIUI优化”,并在“安全中心 → 授权管理 → 应用联网”里给Android Studio开权限。这些不是玄学,是厂商对APK签名的校验策略,绕不开。
4.3 常见编译错误速查表:从红色波浪线到成功运行
| 错误现象 | 根本原因 | 一招解决 |
|---|---|---|
Cannot resolve symbol 'R' |
R.java未生成,通常因XML语法错误 | 检查res/layout/下所有XML,看是否有未闭合标签(如<TextView没写/>)或非法字符(中文标点) |
Execution failed for task ':app:mergeDebugResources' |
图片命名含大写字母或特殊符号 | 将res/drawable/mole_Hit.webp改为mole_hit.webp(Android资源名必须全小写+下划线) |
java.lang.RuntimeException: Unable to start activity ComponentInfo |
AndroidManifest.xml里Activity声明缺失或路径错误 | 检查<activity android:name=".MainActivity">的name属性,确保与Java类包名一致(如类在com.example.whackamole包下,则name应为.MainActivity) |
No target device found |
设备未识别或驱动未安装 | Windows用户去手机官网下载ADB驱动(如三星用Smart Switch,华为用HiSuite),Mac用户执行brew install android-platform-tools |
5. 功能扩展与二次开发:如何把这个模板变成你的原创作品
5.1 加音效:三行代码让锤子落下有“砰”声
源码默认没加音效,但预留了接口。你只需三步:
- 准备音频文件:下载一个短促的“hit.wav”(时长<0.3秒),放入
res/raw/目录(若无此目录,右键`res → New → Android Resource Directory → Resource type选raw); - 在GameController里初始化MediaPlayer:
java private MediaPlayer hitSound; public void initSound(Context context) { hitSound = MediaPlayer.create(context, R.raw.hit); } - 在
onMoleClicked()里触发播放:java public void onMoleClicked(int viewId) { if (isGameRunning && moleManager.isMoleHit(viewId)) { hitSound.start(); // 就这一行! updateScore(10); } }
实操心得:别用
SoundPool,新手容易内存泄漏;MediaPlayer.create()最稳妥。音效文件务必用WAV或OGG,MP3在低端机上可能解码失败。
5.2 换皮肤:用主题切换实现“白天/黑夜模式”
源码的values/colors.xml只有一套颜色,但你可以轻松扩展。新建values-night/colors.xml:
<!-- values-night/colors.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="background_color">#121212</color>
<color name="mole_normal">#BB8FCE</color>
<color name="text_primary">#E0E0E0</color>
</resources>
然后在activity_main.xml的根布局里,把android:background="@color/background_color"改成android:background="?attr/background_color",再在styles.xml里定义<item name="background_color">@color/background_color</item>。这样,系统检测到夜间模式时,会自动加载values-night下的颜色——无需改一行Java代码。
5.3 加排行榜:用SharedPreferences存最高分,三步搞定
不想连服务器?用本地存储就够了:
// 在GameController里,游戏结束时保存
public void saveHighScore(int score) {
SharedPreferences prefs = context.getSharedPreferences("game", Context.MODE_PRIVATE);
int highScore = prefs.getInt("high_score", 0);
if (score > highScore) {
prefs.edit().putInt("high_score", score).apply();
}
}
// 在MainActivity里,启动时读取并显示
SharedPreferences prefs = getSharedPreferences("game", MODE_PRIVATE);
int highScore = prefs.getInt("high_score", 0);
highScoreView.setText("最高分:" + highScore);
注意:
apply()是异步保存,比commit()更高效;"game"是文件名,"high_score"是key,命名要见名知意,避免用"a"、"b"这种。
6. 学习路径建议:从读懂源码到独立开发的进阶地图
这套源码不是终点,而是起点。我建议按这个顺序吃透它:
-
第1天:跑通+改UI
导入运行 → 把地鼠头像换成你自己的图(注意WebP格式和命名规范)→ 修改colors.xml调色 → 目标:让游戏看起来像你的作品。 -
第2天:调逻辑+加功能
理解GameController.getCurrentSpawnInterval()公式 → 尝试改成线性递增,观察区别 → 给计分加个“连击”机制(连续击中3次额外+5分)→ 目标:让逻辑为你所用。 -
第3天:破壁+重构
把MoleManager里的Handler换成CountDownTimer试试 → 把GameController的单例模式改成依赖注入(用构造函数传参)→ 目标:理解不同技术方案的取舍。 -
第4天:输出+分享
录制一个30秒演示视频(用Android Studio自带的Screen Record)→ 写一篇简短README.md,说明你做了哪些改动 → 发到GitHub或班级群 → 目标:获得真实反馈,这是工程师成长的关键一步。
最后分享个小技巧:每次改完代码,别急着运行,先用Android Studio的Analyze → Inspect Code功能扫一遍。它会标出潜在问题,比如“变量未使用”“资源未引用”,这些红波浪线背后,往往藏着你还没意识到的设计漏洞。我当年就是靠这个功能,发现了MoleManager里一个永远执行不到的else分支,顺藤摸瓜重构了整个状态机。代码不是写完就结束,而是改到它自己开始“说话”为止。
简介:直接导入Android Studio即可运行的打地鼠小游戏工程,纯Java开发,不依赖Kotlin或第三方游戏引擎。项目包含主界面Activity、游戏控制逻辑类、地鼠行为管理类三大核心Java文件;XML布局覆盖启动页、游戏主面板、计分栏、倒计时显示及结束弹窗;配套10张WebP格式资源图,包括不同状态的地鼠头像、锤子图标和背景图;配置文件齐全,含AndroidManifest.xml、模块级和项目级build.gradle、混淆规则proguard-rules.pro等。功能完整支持点击击中判定、实时计分、60秒倒计时、难度随时间提升(地鼠出现间隔缩短)、游戏结束自动统计得分并提示重玩。代码结构清晰,变量命名规范,无冗余注释,适合Android初学者理解事件响应机制、Handler主线程更新UI、Random随机逻辑实现,也适合作为移动应用开发课程设计或毕业设计基础模板使用。
更多推荐

所有评论(0)