news 2026/10/4 1:25:21

Cocos Creator v2.0 2D闯关安卓游戏课程设计:从搭建到打包全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cocos Creator v2.0 2D闯关安卓游戏课程设计:从搭建到打包全流程

简介:一款基于Cocos Creator v2.0的2D闯关安卓游戏课程设计资料包已发布,面向计算机科学与技术、软件工程、人工智能等专业的在校师生。内含可运行程序源码、系统设计方案、功能演示视频和配套讲解课件,覆盖二维场景渲染、物理引擎集成、交互逻辑设计等移动端游戏开发核心环节,适用于课程实践、毕业设计或项目实训。包内共375个文件,以png美术素材、js逻辑脚本、anim角色动画、meta配置信息、md文档、docx系统开发说明、mp4操作演示和pptx课件为主,压缩包整体约8.93MB,目录结构清晰。目前已有39人学习下载,适合希望系统梳理Cocos Creator安卓游戏项目的学习者。资源中的角色动作与关卡运行均经过多轮测试,核心玩法稳定。附带的系统开发说明文档可帮助快速理解环境配置和项目架构,具备编程基础的读者还可在现有框架上扩展自定义关卡或优化角色行为,具有较好的参考与二次开发价值。

1. Cocos Creator v2.0课程设计:先跳出“做游戏”的执念,回到“交作业”的及格线

拿到《基于Cocos Creator v2.0的2D闯关安卓游戏开发课程设计》这个题目,大多数人第一反应是去下个现成源码、换个皮就交。我见过太多这样翻车的同学:答辩时老师一句“这个跳跃力度为什么给 600?依据是什么”,人就愣住了。课程设计和商业项目是两种玩法——商业项目看留存和付费,课程设计看“需求能不能讲清、代码能不能改、演示能不能跑”。所以这篇笔记想聊的不是怎么把游戏做得多好玩,而是怎么用 Cocos Creator v2.0 把一个2D闯关安卓游戏从零搭起来,一路走到打包 APK 装进手机里,并且每一步都有东西可讲。适合三类人:要交课程设计的学生、第一次接触 Android 打包的新手、想快速验证 2D 闯关玩法的独立开发者。

2. 用Cocos Creator v2.0搭2D闯关地基:节点树、物理引擎和动画组件的正确姿势

Cocos Creator v2.0 不是新版本,但这恰恰是选它的理由。你搜“Cocos Creator 2D闯关教程”,十篇里有八篇是 v2.x 时代的,报错也能搜到对应答案;v3.x 改了大量 API,照着 v2 教程写会一路踩坑。课程设计求稳,v2.0 够用。

2.1 场景节点树先排清楚,后面写代码不迷路

闯关游戏场景里至少要有这几层:背景层、地图层、角色层、敌人层、UI 层、全局控制节点。我一般会这样建节点树:

Canvas ├── GameRoot // 空节点,挂全局控制脚本 │ ├── Background // 背景,普通节点加 Sprite │ ├── MapLayer // Tilemap 地图挂这里 │ ├── EnemyLayer // 所有敌人预制体挂这里,方便统一管理 │ └── Player // 角色节点,挂 RigidBody + 控制脚本 └── UIRoot // Canvas 下单独一层,血条、金币、关卡提示

有个容易被忽略的点:v2.0 的 Canvas 默认会带一个 Camera 子节点。角色节点如果直接放在 Canvas 下,摄像机跟随逻辑简单;但物理碰撞对节点层级有要求——刚体节点的父节点不要做频繁位移,否则物理位置会偏移。我一般把 Player 单独放一层,不跟 UI 混在一起。设计分辨率建议设 1280x720 或 960x540,横版闯关多数是这个比例。在“项目设置—项目数据—设计分辨率”里改,适配策略选 SHOW_ALL,边界留黑边也比裁掉内容强。

2.2 启用物理引擎和重力:先用一个全局脚本把环境稳住

2D 闯关离不开碰撞和重力。v2.0 内置了两套物理:Builtin 只做碰撞检测不做物理模拟,轻量但弹跳、摩擦都没有;Box2D 是完整刚体模拟,闯关游戏大多选它。物理引擎的启用不是拖个组件就行,要在一个最先加载的场景里做全局初始化。我习惯新建一个GameRoot节点挂GameManager.js,在onLoad里一次性把物理环境配好,原因后面避坑章会细说。

// GameManager.js cc.Class({ extends: cc.Component, onLoad() { // 启用 Box2D 物理系统,必须在任何物理组件使用前完成 let physicsManager = cc.director.getPhysicsManager(); physicsManager.enabled = true; physicsManager.gravity = cc.v2(0, -600); // y 轴负方向为重力方向 // 调试开关:打包前关掉,编辑态打开能看碰撞体边界 physicsManager.debugDrawFlags = cc.PhysicsManager.DrawBits.e_jointBit; } });

逻辑说明:enabled = true是开启物理模拟的总开关,不开则所有刚体都当静态物体处理;gravity用向量表示方向,(0, -600)表示垂直向下。600 这个数不是标准答案,它和场景的像素尺度、角色尺寸强相关——角色要是 30 像素高,600 会把它砸得贴地;要是 200 像素高,600 又显得飘。我的习惯是先给 -600,跑起来再调,这一步最好做成个可配置参数,别写死在代码里。

调试标志debugDrawFlags编辑态开着能看到角色周围一圈碰撞轮廓线,方便你对着地图检查碰撞体是否贴合。打包前务必关掉,否则手机性能会掉一截,而且玩家能看到不美观的线框。这里有个经验:物理初始化写成独立脚本并放在第一个加载场景,比在每个角色脚本里各自设置要稳。后面第五章的“真机和编辑器表现不一致”就是在这里埋的坑。

2.3 动画状态机和动画帧事件:让角色动起来而不是“瞬移”

角色动画是闯关游戏的门面。v2.0 里动画依靠 Animation 组件加 AnimationClip 资源实现。最少需要四个 Clip:idle(站立)、run(跑)、jump(跳)、dead(死亡)。制作流程是:美术序列帧拖入编辑器,生成 Clip,再把 Clip 挂到 Animation 组件上。代码里播放用anim.play('run')。

// PlayerController.js 里的动画切换方法 playState(stateName) { let anim = this.getComponent(cc.Animation); if (!anim) return; // 同一个状态不重复播放,避免每帧调用导致动画反复从第一帧开始 if (anim.getAnimationState(stateName).isPlaying) return; anim.play(stateName); }

参数说明:getAnimationState返回的是这个 Clip 的播放状态对象,isPlaying为 true 就说明已经在播,此时不必再play一次。很多人写跳跃时每帧都调用play('jump'),结果动画永远卡在跳跃第一帧——这就是没加这个判断。

动画和逻辑的衔接有两个方式:状态判断和帧事件。状态判断是给每个动画一个状态名,角色在哪个状态就放哪段动画;帧事件适合做跳跃踩地音效、刀光特效这类需要在某一帧触发的事。在动画编辑器里选中某一帧右键添加帧事件,代码里用onAnimationEvent接收。注意帧事件回调的本质是同步调函数,不要在回调里做耗时操作,比如读文件、加载资源,会造成明显卡顿。

3. 闯关逻辑的三件套:角色控制、Tilemap 关卡和存档模块怎么写才不出错

地基搭好,接下来是闯关游戏的核心玩法部分。三件套不是“三个功能”这么简单,而是三条线:角色怎么动、关卡怎么摆、进度怎么记。每一条拆开都是 50 行代码以内的事,合起来要是没有清晰结构,就是后半夜调 BUG 的起点。

3.1 角色控制器:一个脚本管住移动、跳跃和落地判定

v2.0 里的角色节点我一般挂三样东西:RigidBody(刚体)、Collider(碰撞体)、PlayerController(控制脚本)。刚体负责受重力、被碰撞体推开;碰撞体负责触发伤害判定;控制脚本只负责听键盘输入并给刚体施加影响。三种职责分开,以后改任何一个都动不到另外两个。

// PlayerController.js cc.Class({ extends: cc.Component, properties: { speed: 300, // 水平移动速度,单位 px/s jumpForce: 620, // 跳跃瞬间的纵向速度,单位 px/s maxJumpCount: 1 // 跳跃次数,改 2 就是二段跳 }, onLoad() { // 拿到刚体组件,后续所有操作都走刚体而不是直接改节点坐标 this.rigidBody = this.getComponent(cc.RigidBody); this.jumpCount = 0; this.keys = {}; cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this); }, onKeyDown(e) { this.keys[e.keyCode] = true; }, onKeyUp(e) { this.keys[e.keyCode] = false; }, update(dt) { // 水平方向:A/D 或左/右方向键,给刚体一个恒定的水平速度 let vx = 0; if (this.keys[cc.macro.KEY.a]) vx = -this.speed; if (this.keys[cc.macro.KEY.d]) vx = this.speed; this.rigidBody.linearVelocity = cc.v2(vx, this.rigidBody.linearVelocity.y); // 跳跃:单独监听空格键,不放在 update 里轮询 if (this.keys[cc.macro.KEY.space]) { this.tryJump(); } }, tryJump() { if (this.jumpCount >= this.maxJumpCount) return; // 跳跃是瞬间改纵向速度,而不是给一个持续向上的力 this.rigidBody.linearVelocity = cc.v2(this.rigidBody.linearVelocity.x, this.jumpForce); this.jumpCount++; }, // 刚体碰撞回调,碰到地面层就重置跳跃次数 onBeginContact(contact, self, other) { if (other.node.group === 'ground') { this.jumpCount = 0; } } });

逻辑说明:移动不是直接改node.x,而是设置刚体的linearVelocity(线速度)。如果你直接改坐标,刚体的物理模拟会和你的手动设置打架,角色会在碰撞体里抖来抖去。linearVelocity的x取输入方向乘以 speed,y保留刚体当前纵向速度,这样下落和重力不受影响。跳跃同理,本质是瞬间把纵向速度设为jumpForce这个正数,让角色获得向上的初速度,然后重力再把速度拉下来。

参数推荐:speed 在 200~400 之间,分辨率为 1280x720 时 300 是个舒服的值;jumpForce 大一点小一点手感完全不同,620 大致能跳两到三个角色身高,具体还要看你角色碰撞体高度。maxJumpCount = 1是单段跳,想要二段跳就改成 2,很多动作闯关喜欢用这个参数调节难度——把怪物的血量调高不如把玩家的跳跃次数调低来得明显。

onBeginContact是刚体的接触回调,只有碰撞双方至少有一方是刚体时才会触发。重置跳跃次数的时机要选在“脚底碰到地面”而不是“身体任何部位碰到地面”,否则角色侧着蹭墙也能二段跳。常见做法是:给地面和墙体分不同的碰撞分组,只对地面分组重置跳跃数,或者用一个放在角色脚下的子节点做专门的触地检测。

3.2 用 Tilemap 搭关卡:地图文件、碰撞层和摄像机边界一次说清

闯关关卡不用一个个摆方块精灵,用 Tilemap 效率高得多。v2.0 里是配合 Tiled 地图编辑器使用:在 Tiled 里画好地图,导出成.tmx文件(包含地图数据)加图片图集,然后把.tmx拖进 Cocos Creator 资源管理器,场景里挂一个 TiledMap 组件引用该文件。Tiled 里画地图时把格子大小定成 32x32 或 64x64,格子越小地图越精细,但碰撞体数量也越多。我一般用 32,图集纹理一张尽量控制在 2048x2048 以内,避免安卓低端机纹理内存吃紧。

Tiled 里图层不建议只画视觉层,一定要单独画一个碰撞层,把有阻挡的格子填上。导出后在 Cocos 里给 TiledMap 节点加碰撞,或者把 Tiled 的对象层导出成多个矩形碰撞体。习惯做法是:在 Tiled 里用对象层画好障碍物的矩形范围,导出后在 Cocos 里脚本批量读取对象层数据生成静态碰撞体,这样地图有多少块都不用手动拖碰撞节点。

// MapLoader.js – 读取 Tiled 对象层并生成碰撞体 cc.Class({ extends: cc.Component, properties: { tiledMap: cc.TiledMap }, onLoad() { // 拿到对象层,层名要和 Tiled 里一致 let objGroup = this.tiledMap.getObjectGroup('collision'); if (!objGroup) return; let objects = objGroup.getObjects(); objects.forEach(obj => { // 每个对象生成一个矩形碰撞体,挂到地图节点下 let collider = this.node.addComponent(cc.PhysicsBoxCollider); collider.size = cc.size(obj.width, obj.height); collider.offset = cc.v2(obj.x + obj.width / 2, obj.y + obj.height / 2); collider.sensor = false; // 实体碰撞,角色会被挡住 }); } });

逻辑说明:getObjectGroup('collision')按名字取对象层,getObjects()返回该层所有矩形对象,每个对象有x、y、width、height属性。生成的碰撞体全部挂在地图节点下,属于静态刚体(没有 RigidBody 就不参与物理模拟但能挡人)。sensor = false表示这是实体,玩家碰上会受阻;如果做成机关区域、传送门这类不需要阻挡的效果,才会设为sensor = true。

碰撞体生成后地图就“能站人”了,但摄像机还不会跟着角色跑。给摄像机挂一个跟随脚本,注意边界限制,不然镜头会一路跑到地图外面的空白区域。

// CameraFollow.js – 挂在主摄像机上 cc.Class({ extends: cc.Component, properties: { target: cc.Node, // 拖入角色节点 minX: 0, maxX: 640, // 地图的水平范围,按你的地图尺寸填 minY: 0, maxY: 360 }, lateUpdate() { if (!this.target) return; // 世界坐标转节点坐标,保证跟随平滑 let pos = this.node.position; pos.x = cc.misc.clamp(this.target.x, this.minX, this.maxX); pos.y = cc.misc.clamp(this.target.y, this.minY, this.maxY); this.node.position = pos; } });

参数说明:数值范围为地图边界,比如 1280x720 分辨率下横向 100 格、每格 32 像素的地图,maxX 就是 3200,视具体地图而定。cc.misc.clamp把角色坐标限制在地图区间内,角色跑出边界时镜头停在边界处,不会露出黑屏外的空场景。摄像机跟随用lateUpdate而不是update,因为update里先跑完角色物理和坐标更新,lateUpdate再接住最新坐标,避免“镜头比角色慢一帧”的拖拽感。

3.3 敌人巡逻、玩家受击和死亡重生:三个最常见的编码误区

敌人最简单的形态是“左右巡逻 + 碰到玩家造成伤害”。巡逻逻辑完全不依赖物理,就是一个脚本控制节点在起点两侧往返。常见误区是把敌人也做成刚体并设置速度,结果两个刚体对撞把敌人弹飞——敌人的移动千万别用 RigidBody,直接改坐标就行。

// EnemyPatrol.js cc.Class({ extends: cc.Component, properties: { patrolDistance: 150, // 向左/向右各巡逻多少像素 speed: 80 // 巡逻速度 px/s }, onLoad() { this.startX = this.node.x; this.dir = 1; }, update(dt) { let x = this.node.x + this.dir * this.speed * dt; // 超出巡逻边界就掉头 if (x > this.startX + this.patrolDistance) { this.dir = -1; } else if (x < this.startX - this.patrolDistance) { this.dir = 1; } this.node.x = x; } });

玩家受击判定走碰撞分组:把玩家设为player组,敌人设为enemy组,玩家刚体的onBeginContact里判断撞到的是不是enemy分组。这里有个大坑:onBeginContact在碰撞的一瞬间只触发一次,如果你在回调里把玩家传送到存档点,可能传送后仍然贴着敌人的碰撞体,下一帧立即再次触发,导致血量“跳楼式”归零。解决方法是受击后加一个短暂无敌状态,用计时器挡住重复回调。

onBeginContact(contact, self, other) { if (other.node.group === 'enemy' && !this.invincible) { this.hp--; this.invincible = true; // 1 秒无敌时间,期间敌人碰撞不会扣血 this.scheduleOnce(() => { this.invincible = false; }, 1); if (this.hp <= 0) { this.respawn(); } } }

存档模块比你想的简单,v2.0 提供cc.sys.localStorage,本质是 key-value 本地存储,不需要引第三方库。

// 保存进度:当前关卡编号和金币数量 saveProgress(levelId, coinCount) { cc.sys.localStorage.setItem('levelId', levelId); cc.sys.localStorage.setItem('coinCount', coinCount); } // 读取进度,取不到就返回默认值 loadProgress() { let levelId = cc.sys.localStorage.getItem('levelId'); return { levelId: levelId ? parseInt(levelId) : 1, coinCount: parseInt(cc.sys.localStorage.getItem('coinCount') || '0') }; }

注意getItem拿到的永远是字符串,存进去的数字拿出来要手动parseInt。另一个坑是:课程设计演示时如果老师要求“清空进度重新玩”,你要在设置界面或游戏开场加一个“重置存档”按钮,否则老师会当场掏手机让你找怎么删数据。

4. Cocos Creator v2.0 打包安卓 APK:环境配置、签名和真机安装全流程

2D 闯关游戏在编辑器里跑得欢不算完,课程设计验收时老师大概率要求你把 APK 装到安卓手机上现场演示。打包安卓是 Cocos Creator v2.0 的老大难,多数问题出在环境配置而不是代码上。

4.1 构建前的环境三件套:JDK、Android SDK、NDK

v2.0 打包安卓走的是“Cocos 生成原生工程 + Gradle 编译 APK”,所以电脑上必须装好三样东西:JDK(Java 开发环境)、Android SDK(安卓开发库)、NDK(原生开发工具包,v2.0 的安卓构建强制依赖)。装好后在 Cocos Creator 菜单“偏好设置—原生开发环境”里分别填三个路径,填错会在构建开始后立刻报错,所以构建前先确认路径真实存在且版本匹配。SDK 路径要指到有platform-tools和build-tools的目录,NDK 要指到根目录而不是toolchains子目录。这一步是血泪经验:很多人路径指到 NDK 内部子目录,构建器找不到版本号,报错信息还看不懂。

v2.0 对 NDK 版本比较挑剔,太新的 NDK 反而编不过。如果你电脑上只有一个新版 NDK,先测 C++ 那层编译动不动得了——跑一次构建就知道。课程设计时间紧,建议直接装稳定版本,一次通过比用新版折腾三天划算得多。

4.2 构建发布面板的参数配置和签名生成

打开“菜单—项目—构建发布”,平台选 Android。发布路径默认在项目文件夹下build目录里,生成的原生工程也在里面。面板上几个关键参数说清楚:

参数取值建议说明
包名com.你名字拼音.游戏名Android 应用的唯一标识,装到手机后不可改
API Level21 以上太低了部分功能不可用,太高了老手机装不上
构建模式Release 优先Debug 包体积大、运行慢,不适合演示
构建 APK勾选不勾选只生成原生工程,还要用 Android Studio 二次签名
MD5 Cache勾选资源名带哈希值,加快构建且避免资源替换残留

签名文件是 APK 安装到手机的“身份证”。安卓要求每个 APK 必须有签名,Cocos 默认有个调试签名,但调试签名的 APK 有些手机装不上。课程设计建议自己生成一个正式签名。生成命令在命令行里执行:

keytool -genkey -alias game.keystore -keyalg RSA -keysize 2048 -validity 10000 -keystore game.keystore

参数说明:-alias是这个钥匙在钥匙库里的名字,后面构建面板里要填对;-validity 10000表示有效天数,约 27 年,不用担心过期;-keystore是输出的文件名。执行后会交互式问你密码、姓名、组织等信息,密码一定要记牢,后面每次打新包都要用,而且这个 keystore 文件别删,丢了就再也签不了同一个包的升级版。生成后把game.keystore放到项目外单独目录,别放项目里,不然最后打包时会把签名文件也塞进 APK 资源里,这是低级但又常见的翻车。

构建面板里把签名文件路径、密码、别名填进去,点击构建。第一次构建会下载 Gradle 依赖包,耗时可能 20 分钟以上,不是死机,耐心等。构建完成后 APK 会输出到build/android目录下,文件名一般是游戏名-release.apk。

4.3 真机安装和运行日志排查

APK 拷到手机有两种方式:数据线 + adb,或者直接微信/QQ 传给手机。演示用后者省事,但调试阶段推荐 adb,因为能看到运行日志,定位白屏和崩溃全靠这个。

adb install -r build/android/你的游戏-release.apk

-r表示覆盖安装,保留手机上的存档数据。安装成功后先别急着点图标,先在电脑上开一眼日志:

adb logcat -s cocos AndroidRuntime | grep -iE "error|crash|exception"

这段命令把日志里带安卓崩溃信息、Cocos 报错的行过滤出来直接滚屏打印。游戏运行时崩了,日志里能找到具体报错文件和方法名。注意一点:APK 安装出现“解析包错误”时,八成不是代码问题,而是打包时资源路径异常导致 APK 被 Android 系统判定为损坏,回构建面板勾选“清理”后重打。

真机调试和模拟器最大的差别在于渲染和内存。模拟器上纹理占用和 GPU 驱动跟真机差太远,游戏在模拟器能跑、真机一进关卡就闪退的情况,我在课程设计里见过太多次。结论只有一个:尽早用真机测,别等到答辩前一天。

5. 课程设计验收前的避坑:5 个让演示当场翻车的经典问题

下面是按“现象 → 原因 → 解决”写的高频问题。每一条都是真实发生过的,前两条尤其值得注意,因为它们都会在答辩现场直接让游戏“黑屏/闪退”,而你几乎无法当场救场。

5.1 打包后的 APK 首屏白屏或黑屏

现象:浏览器里跑得好好的,打出来的 APK 装上后启动就黑屏,过几秒自动闪退。原因:多数是脚本运行时报错。编辑器和浏览器环境报错会弹控制台,安卓上 JS 报错不会弹窗,直接黑屏。最常见的报错源是用了浏览器专属 API(比如window.alert)或者资源路径大小写不对。排查:先连 adb 跑一次adb logcat -s cocos AndroidRuntime看日志;如果是脚本报错,把window.onerror的报错信息打印到日志里。解决:临场最快的办法是找出报错脚本改掉;长期做法是打包前在编辑器里用“浏览器预览”把所有关卡跑一遍,重点看控制台有没有红色报错。

5.2 真机上角色物理行为和编辑器完全不一样

现象:编辑器里角色跳跃高度刚好,装到手机上下落极慢或者直接穿墙。原因:物理引擎的初始化时机和重力设置没有在游戏最早期场景执行。你在编辑器里看到的重力可能来自预览时某个脚本的初始化;打 APK 后启动场景加载顺序不同,重力参数没生效,Box2D 默认重力就变成它的规范值,导致体感全变。解决:把物理引擎启用和重力设置放到一个常驻场景的onLoad里,也就是第二章的GameRoot,并确认该场景在“项目设置—参与构建的场景”里排在第一位。顺带检查角色刚体的gravityScale属性,别不小心改成了 0,那会让角色像在太空里走路。

5.3 改完代码重新打包,装到手机上还是旧版

现象:代码明明改了,重新构建的 APK 装上还是老功能。原因:Cocos 构建有增量缓存,某些旧的脚本和资源文件残留;手机端系统也可能复用了安装缓存。解决:构建发布面板勾选“清理”后重新构建,它会清掉build目录重来;手机卸载旧版本后再安装新 APK,不要使用覆盖安装。这个“做完了没生效”的问题最容易在答辩前一晚制造恐慌,熟练以后固定流程:清构建 → 卸载手机旧包 → 装新包,三分钟搞定。

5.4 安卓返回键一按就退出游戏

现象:演示时老师随手按了手机返回键,游戏瞬间退出回桌面,进度没存,场面尴尬。原因:v2.0 默认没有处理安卓返回键,系统把它当“结束 Activity”处理。解决:监听系统返回键事件,弹出一个确认框,确认后才退出。

cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); onKeyDown(e) { if (e.keyCode === cc.macro.KEY.back) { // 弹自定义确认框,不然就直接退出 this.showExitDialog(); } }

参数说明:cc.macro.KEY.back是安卓返回键的键码,浏览器里对应退格键,不会误触。注意在 v2.0 里该事件要在onLoad注册、onDestroy注销,否则场景销毁后回调还在,会报“调用已销毁对象的方法”错误。

5.5 图片或字体在安卓上显示异常

现象:编辑器里正常的游戏标题字体,打包后变成方块或默认宋体;高清图在手机上发虚。原因:v2.0 的资源加载在 web 平台不区分大小写,但安卓原生环境区分。如果你资源文件名是GameTitle.png,代码里写成gametitle.png,浏览器预览没事,APK 里就加载失败,表现出来就是字体丢失或图片空白。解决:项目里所有资源路径统一小写字母加下划线,比如game_title.png,代码引用时严格一致。字体建议用系统字体或把需要的字做成图片,不要指望打包时自动嵌入一套中文字体,那样 APK 体积会大几 MB 且仍有缺字风险。

6. 答辩前最后的打磨:跑通一遍验收清单,再留一条升级后路

别急着交,先把下面的验收清单逐条跑一遍。这条清单能让你答辩时心里有底,也能防止演示到一半手滑翻车。

检查项验证方法预期结果
角色移动进第一关左右移动方向正确、无抖动、速度适中
跳跃手感跳上平台再跳下跳跃高度合理,落地不滑步
碰撞阻挡角色撞墙被挡住,不穿墙不卡墙缝
受击处理故意碰到敌人扣血并进入无敌状态,不连续扣血
死亡重生血量归零回到当前关存档点,进度不丢
通关跳转到达终点进入下一关,关卡编号正确
存档恢复退出游戏重进解锁关卡和金币保留
安卓返回键按返回键弹确认框,不直接退出
APK 安装真机全新安装不报解析包错误、无白屏

走完清单后,如果时间富余,性价比最高的一条升级是给角色加受伤闪白和顿帧效果,用 v2.0 的cc.tween几十行就能实现,但视觉上会明显比隔壁同学的“原版角色”更完整。

// 受击时闪白:把精灵颜色切到白色,0.2秒内慢慢恢复 let sprite = this.getComponent(cc.Sprite); sprite.node.color = cc.Color.WHITE; cc.tween(sprite.node) .to(0.2, { color: cc.Color.WHITE }, { easing: 'quadOut' }) .to(0.1, { color: cc.Color.WHITE }) .call(() => { sprite.node.color = cc.Color.WHITE; }) .start();

代码说明:cc.tween是 v2.0 提供的补间动画接口,.to(0.2, { color: ... })表示在 0.2 秒内把属性渐变到目标值。闪白的实现是先把颜色提亮变白,再渐变回正常颜色,比直接切黑屏或淡出淡入更有打击感。

做完这些,你的课程设计从“能跑”变成“能讲”。讲的时候别忘了把每一步设计决策说成“我为什么这么做”——比如重力为什么用 -600、无敌时间为什么是 1 秒、存档为什么用 localStorage 而不是数据库。这些思考过程才是课程设计的评分重点。我自己早期做过一个项目,功能全跑通了,答辩被问“你这里为什么没有做失败处理”,当场答不上来。后来养成的习惯就是写代码前先问自己三个问题:异常输入怎么办、极端操作怎么办、数据丢了怎么办。这个习惯一直带到今天。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 1:25:07

旋转矩阵的本质:从空间直觉到工程实现的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 1:24:42

MR25H40CDF与STM32F767BI:工业MRAM存储选型与掉电保存实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 1:24:36

@Transactional滥用导致连接池耗尽的根因与修复

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 1:24:35

低功耗测量探头选型与实操:从nA级信号到干净波形的全流程指南

1. 低功耗测量的核心挑战与探头选型逻辑搞低功耗测量的朋友多半有过这种体验&#xff1a;板子明明设计得挺好&#xff0c;休眠电流理论值算下来只有几微安&#xff0c;可示波器上抓出来的波形却像心电图一样上蹿下跳&#xff0c;底噪大得离谱&#xff0c;根本分不清哪些是真实的…

作者头像 李华
网站建设 2026/10/4 1:24:05

备份体系设计:从3-2-1原则到rclone+restic实战

备份这件事&#xff0c;我从“存过就行”到“必须能还原”&#xff0c;中间隔了一次丢数据的教训。当年一台云服务器磁盘故障&#xff0c;阵列里几个盘一起罢工&#xff0c;当时手头所谓“每天备份”的文件&#xff0c;解压出来一半是空壳&#xff0c;数据库也停在三周之前&…

作者头像 李华
网站建设 2026/10/4 1:23:45

告别本地环境:二十多款ESP32在线开发工具全解析

1. 为什么我彻底放弃了本地装 ESP 开发环境三年前我第一次接触 ESP32 的时候&#xff0c;干的第一件事就是照着教程装 Arduino IDE&#xff0c;然后加开发板管理器网址、下载几百兆的离线包、配 Python 环境、装 esptool、折腾串口驱动。那台老笔记本硬盘本来就不宽裕&#xff…

作者头像 李华