ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Cocos Creator v2.0开发2D闯关安卓小游戏:从设计到打包全攻略

Cocos Creator v2.0开发2D闯关安卓小游戏:从设计到打包全攻略 简介这是一份面向安卓移动开发课程设计的完整实战资源基于 Cocos Creator v2.0 打造 2D 闯关小游戏并配套完整源码、设计方案、PPT、演示视频与说明文档。资源既能作为计算机相关专业学生的课设/毕设参考也适合想快速上手 Cocos Creator 的初学者跟做练习。压缩包共 312 个文件大小仅 8.91MB内含 JS 游戏逻辑脚本、场景与预制体配置、动画/音效资源、PNG 美术素材以及 MD/Word 文档、MP4 演示视频和 PPT 汇报材料结构清晰便于按模块查阅已有 73 人学习下载。代码经过运行验证基本功能可正常使用资源中还保留了角色跳跃、奔跑、死亡等关键动画文件与系统开发说明文档能帮助读者理解 2D 闯关游戏的状态机设计与项目组织方式。结合 README 与文档说明可快速跑通项目并据此扩展其他玩法。1. 用 Cocos Creator v2.0 做 2D 闯关安卓小游戏先想清楚这三件事课程设计季最常见的场景题目是“基于 Cocos Creator v2.0 的 2D 闯关安卓小游戏”要求交的却是一整套源码、APK、设计方案、PPT、演示视频。很多组把时间全砸在调碰撞和画角色上最后打包和文档草草交差答辩时被问两句就断片。选 Cocos Creator v2.0 很实际JS 脚本上手快编辑器里拖场景所见即所得构建面板能直接出安卓原生工程没有安卓原生开发经验也能把 APK 打出来。v2.0 架构虽然旧但课程设计相关的教程和案例多半以它为例踩坑基本都能搜到现成的解法。下面按「玩法实现 → 安卓打包 → 交付物 → 自检加分」的顺序讲一条能落地的路线每步给代码、给参数、给避坑点。2. 用 Cocos Creator v2.0 实现 2D 闯关关卡、角色、碰撞怎么串起来闯关游戏最容易做歪的地方不是角色动作多华丽而是每关手搭一个场景、在编辑器里摆几十个节点。我一般会把关卡抽象成纯数据用一个 Manager 按配置生成节点。这样做的好处有三条加关卡只改配置不分叉场景评估关卡难度只要看数值答辩现场改一个数字就能“临时生成新关”效果比念 PPT 强得多。2.1 关卡数据驱动设计用一份配置表生成整关先定义第一关的数据把平台、敌人、星星全部写进对象。// assets/scripts/data/LevelConfig.js // 每关一份配置后续加关卡只需要在末尾追加对象 module.exports { level1: { needItem: 3, // 集齐 3 颗星星才允许过关 spawn: { x: -260, y: 80 }, // 玩家出生点 platforms: [ { type: ground, x: 0, y: -280, w: 1280, h: 80 }, { type: box, x: 420, y: -100, w: 200, h: 40 }, { type: box, x: 680, y: -40, w: 200, h: 40 } ], enemies: [ { x: 900, y: -280, moveRange: 160, speed: 100 } ], items: [ { x: 450, y: -50 }, { x: 700, y: 10 }, { x: 1000, y: -260 } ] } };再写 LevelManager按配置实例化预制体// assets/scripts/manager/LevelManager.js const LevelConfig require(../data/LevelConfig); cc.Class({ extends: cc.Component, properties: { groundPrefab: cc.Prefab, // 编辑器里逐个从资源面板拖入 boxPrefab: cc.Prefab, enemyPrefab: cc.Prefab, itemPrefab: cc.Prefab }, loadLevel(id) { const cfg LevelConfig[level id]; const map this.node.getChildByName(Map); map.removeAllChildren(); // 平台按 w/h 改尺寸后再摆位预制体保持一份即可 cfg.platforms.forEach(p { const node cc.instantiate(this[p.type Prefab]); node.width p.w; node.height p.h; node.setPosition(p.x, p.y); map.addChild(node); }); // 敌人实例化后把巡逻参数传进 EnemyCtrl同一个预制体复用 cfg.enemies.forEach(e { const node cc.instantiate(this.enemyPrefab); node.setPosition(e.x, e.y); node.getComponent(EnemyCtrl).setup(e); map.addChild(node); }); // 星星没有额外参数直接批量生成 cfg.items.forEach(i { const node cc.instantiate(this.itemPrefab); node.setPosition(i.x, i.y); map.addChild(node); }); } });这段代码的逻辑是先找到 Canvas 下的空白节点 Map把上一次的关卡节点全部清掉再按配置生成新节点。type写成ground、box通过this[p.type Prefab]动态取属性就能让配置和预制体一一对应不需要写分支判断。注意星星和敌人的预制体不能互相替换setup(data)只在敌人身上调用星星预制体上没有 EnemyCtrl 组件。提示编辑器里 Blueprint 的用处就在这里——同一个预制体改配置就等于换一关。真正要放进 build 包的场景数量越少构建时间和出错概率都越低。2.2 玩家角色刚体、碰撞体与跳跃手感调参玩家节点上挂 Sprite、Animation、RigidBody动态刚体和一个 BoxCollider。碰撞体宽度不要铺满整个贴图取角色身体宽度的 70%80%否则角色侧身擦过平台边缘时会被当成撞墙这是最常见的“贴图没碰、人却卡住”的原因。物理系统默认不开启在玩家脚本的 onLoad 里打开并设重力// assets/scripts/player/PlayerCtrl.js cc.Class({ extends: cc.Component, properties: { moveSpeed: 320, // 水平移动速度 px/s jumpImpulse: 480, // 一次跳跃冲量配合重力一起调 groundGroup: ground }, onLoad() { const pm cc.director.getPhysicsManager(); pm.enabled true; pm.gravity cc.v2(0, -1200); // 负值向下绝对值越大滞空越短 this.body this.getComponent(cc.RigidBody); this.body.enabledContactListener true; // 不打开就收不到碰撞回调 this.groundContacts 0; // 键盘给预览和编辑器调试用 cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKey, this); // 触摸给安卓真机用 cc.systemEvent.on(cc.SystemEvent.EventType.TOUCH_START, this.onTouch, this); }, onKey(e) { if (e.keyCode cc.macro.KEY.space) this.jump(); }, onTouch() { this.jump(); }, update() { // 只在水平方向改速度垂直方向交给物理引擎 let vx 0; if (this.leftBtn this.leftBtn.pressed) vx - this.moveSpeed; if (this.rightBtn this.rightBtn.pressed) vx this.moveSpeed; this.body.linearVelocity cc.v2(vx, this.body.linearVelocity.y); if (vx ! 0) this.node.scaleX vx 0 ? 1 : -1; // 左右翻转朝向 }, jump() { if (this.groundContacts 0) return; // 不在地面不允许二段跳 // 先清空垂直速度再打冲量否则连续起跳会越跳越高 const v this.body.linearVelocity; this.body.linearVelocity cc.v2(v.x, 0); this.body.applyLinearImpulse(cc.v2(0, this.jumpImpulse), this.body.getWorldCenter(), true); }, onBeginContact(contact, self, other) { if (other.node.group this.groundGroup) this.groundContacts; }, onEndContact(contact, self, other) { if (other.node.group this.groundGroup) this.groundContacts--; } });这里有个容易被忽略的细节groundContacts是累计值而不是布尔值。角色同时站在两块平台上的时候一块平台脱离接触计数减一还有另一块兜底如果用布尔值会掉下去但状态没变表现就是“按了跳跃没反应”。既然物理在模拟就不要手动改 node 的 position改位置会跟刚体解算打架轻则抖动重则穿模。真机没有键盘UI 上放左右两个虚拟按钮各自挂一个 VirtualButton// assets/scripts/ui/VirtualButton.js cc.Class({ extends: cc.Component, properties: { dir: -1 // 左按钮填 -1右按钮填 1 }, onLoad() { this.pressed false; this.node.on(cc.Node.EventType.TOUCH_START, () { this.pressed true; }, this); this.node.on(cc.Node.EventType.TOUCH_END, () { this.pressed false; }, this); this.node.on(cc.Node.EventType.TOUCH_CANCEL, () { this.pressed false; }, this); } });然后把这两个节点拖到 PlayerCtrl 的 leftBtn、rightBtn 属性里。需要说明的是TOUCH_CANCEL 必须处理手指滑出按钮区域时系统会发 CANCEL不处理的话状态会卡在按下去。手感参数以如下为起点调一轮大约需要 10 分钟参数起点值调整方向gravity.y-1200绝对值变大跳跃干脆、滞空短变小则飘jumpImpulse480决定跳跃高度配合平台纵向间距换算moveSpeed320决定水平距离平台横向间距按它来排BoxCollider 宽度角色宽 70%80%太大蹭墙太小视觉上“没碰到也算碰”注意跳跃距离 ≈ 水平速度 × 滞空时间。改 moveSpeed 时一定要回头复核平台间距这是闯关手感出问题的第一来源。2.3 障碍、收集与关卡切换碰撞分组和状态流转物理碰撞矩阵在「项目设置 → 分组管理」里配置。先建四个分组ground、player、enemy、item然后把玩家节点的 Group 设为 player平台设为 ground敌怪设为 enemy星星设为 item。矩阵按下面这张表勾分组需要碰撞的对象用途groundplayer、enemy玩家落地判定敌人在地面巡逻enemyplayer、ground敌人保持在地面也能对玩家造成伤害itemplayer只跟玩家碰撞收集后消失playerground、enemy、item所有交互都在玩家身上收口敌人巡逻用 EnemyCtrl 实现// assets/scripts/enemy/EnemyCtrl.js cc.Class({ extends: cc.Component, properties: { speed: 100, moveRange: 160 }, setup(data) { if (data) { this.speed data.speed; this.moveRange data.moveRange; } this.originX this.node.x; }, update() { // 超过巡逻边界就反向原点 - range 作为边界 if (Math.abs(this.node.x - this.originX) this.moveRange) { this.speed * -1; } const b this.getComponent(cc.RigidBody); b.linearVelocity cc.v2(this.speed, b.linearVelocity.y); } });敌人必须也挂 RigidBody 并选动态类型才能跟地面产生物理碰撞否则会直接飘着穿过平台。这个脚本的写法有一点粗糙速度在边界反复反转时可能会在临界点抖动问题不大但答辩前建议把它替换成用originX cos(t)的往返公式视觉上更顺滑。收集星星和玩家死亡都放在一个全局单例 GameMgr 里处理// 挂在 UI 根节点上的 GameMgr.js window.Game window.Game || { score: 0, currentLevel: 1 }; cc.Class({ extends: cc.Component, restart() { if (window.Game.score 0) { window.Game.score 0; } cc.director.loadScene(Game); // 重新加载主场景LevelManager 按 currentLevel 重建 }, onItemGot() { window.Game.score; const cfg require(../data/LevelConfig); const need cfg[level window.Game.currentLevel].needItem; if (window.Game.score need) { window.Game.currentLevel; cc.director.loadScene(Game); } } });为什么不把分数和关卡号挂在组件属性上因为 loadScene 会销毁当前场景的节点属性值跟着没了。挂到 window 上的对象跨场景存活这是 Creator 里最简单直接的存档方式。玩家撞到敌人时在 PlayerCtrl 的 onBeginContact 里判断other.node.group enemy调到 GameMgr.restart() 即可。3. 用 Cocos Creator v2.0 打包安卓 APK构建配置与真机适配新人在这一步最容易卡住而且往往是环境问题而不是代码问题。先明确 Cocos Creator v2.0 的构建机制构建面板做的是“生成安卓原生工程 拷贝资源 编译脚本”最终 APK 由 Android Studio 完成。所以后面要用两个工具别指望 Creator 一个按钮直接出包。3.1 构建发布面板的几个必改项打开「项目 → 构建发布」平台选 Android构建一次输出到项目下的 build/android。构建面板里值得逐项确认的配置如下配置项推荐设置理由包名com.jx.classdesign反域名格式不能以数字开头不能含中文构建路径build/android默认即可交给答辩的源码包要排除该目录API LevelminSdk 21target 28 附近覆盖考场绝大多数安卓 9 / 安卓 10 真机ABIarm64-v8a、armeabi-v7a只保留 ARM 架构去掉 x86 能明显减小包体调试模式正式演示前关掉开启后每帧跑 debug 逻辑帧率更低v2.0 时代对应的原生工具链是 JDK 8 Gradle 4.x/5.xNDK 推荐 r16b 或 r18。电脑上如果装的是 JDK 17构建时会直接报 Gradle 不兼容这不是你代码的问题换 JDK 8 就好。首次构建会下载大量依赖建议提前连网构建一次别到答辩前一天才第一次开项目。3.2 用 Android Studio 出 release APK 的落地步骤构建完成后产物目录下就是安卓工程不同 2.x 小版本结构略有差别但一定有 build.gradle 和 gradlew。用 Android Studio 打开这个目录等 Gradle 同步完成。之后有两种打包方式菜单Build → Generate Signed Bundle / APK按向导操作或者在工程的 build.gradle 里直接配置签名// 放在 android 节点里 android { signingConfigs { release { storeFile file(release.keystore) // 用 keytool 生成或 AS 向导生成后放这里 storePassword demo123456 keyAlias release keyPassword demo123456 } } buildTypes { release { minifyEnabled false signingConfig signingConfigs.release } } }配置好之后在工程根目录执行./gradlew assembleRelease生成的 APK 在app/build/outputs/apk/release/下。需要说明两个常见坑第一签名文件要提交到源码包但保管好密码老师重新打包时会用到第二用 debug 签名打的包也能装但换一台机器重打后包名相同签名不同会提示卸载重装答辩前用同一台电脑、同一个签名文件出包最稳妥。有些同学喜欢直接把 build/android 整个目录打进源码包这是不对的解压到新电脑后会带着你本机的绝对路径。源码包里只保留 assets、project.json、package.json 等 Creator 工程文件build 目录和 temp 目录全部排除文档里写明“打开 Creator 后重新构建一次”即可。3.3 竖屏锁向、屏幕适配与刘海屏安全区2D 闯关按竖屏做比例建议 720×1280。Canvas 节点上把设计分辨率改成 720×1280勾选 Fit Width取消 Fit Height。对应的脚本等价写法是// 放在 GameMgr 的 onLoad 里优先级高于 Canvas 面板设置 cc.view.setDesignResolutionSize(720, 1280, cc.ResolutionPolicy.FIXED_WIDTH);FIXED_WIDTH 的含义是宽度始终铺满 720 逻辑像素高度按真机屏幕比例伸缩。竖屏游戏用这个策略左右永远不会被裁切上下多出来的区域用来做背景延伸。副作用是不同屏幕看到的纵向范围不同所以 UI 按钮不要贴边太近。锁竖屏要到安卓工程里改找到游戏主 Activity!-- AndroidManifest.xml 中游戏主 Activity 增加属性 -- activity android:nameorg.cocos2dx.javascript.AppActivity android:screenOrientationportrait android:configChangesorientation|screenSize|keyboardHidden /刘海屏适配不要自己算Creator 2.x 的组件库里自带 SafeArea拖到 UI 根节点或底部按钮节点的子节点上运行时会自动读取安全区并限制内容不进入挖孔区域。如果跑在旧手机上没刘海这个组件不会产生任何额外偏移放心挂。4. 把设计方案、PPT、演示视频做成答辩加分的组合拳很多组最后只交了 APK 和源码PPT 是保姆级复制别人的模板演示视频是手机随手一拍。这四样东西其实是同一套逻辑让一个没打开你项目的人在 10 分钟内看懂你做了什么、怎么做、有没有用心。4.1 设计方案文档按“评审视角”组织文档不要从“项目背景”这种万金油章节开场评审只会看三个问题需求是什么、你用什么方案实现、为什么用这个方案。推荐结构章节内容要点需求分析玩法一句话、功能清单、目标机型与系统版本总体设计场景划分、节点树截图、物理分组表、状态流转详细实现PlayerCtrl、LevelManager、EnemyCtrl 的核心代码片段配注释测试结果真机列表、帧率数据、碰到并解决的问题总结个人分工、难点攻克、可改进方向详细实现一章截图比代码更重要。编辑器里把「层级管理器」和「属性检查器」截图标注出刚体、碰撞体、分组三个关键属性评审一眼就能看出你理解组件体系。代码段只放关键 20 行前后全文贴源码会暴露你抄作业的事实。4.2 演示视频用 scrcpy 录真机画面再压一遍手机拍屏幕的问题是对焦飘、反光、旁边有杂音。我一般用 scrcpy 把安卓真机画面投到电脑同时录原始视频adb devices # 先确认设备连接 scrcpy --max-size 1080 --max-fps 30 --record demo_raw.mp4录完再交给 ffmpeg 做压缩和转码为的是让视频体积控制在 50MB 以内老师手机能直接看ffmpeg -i demo_raw.mp4 -c:v libx264 -b:v 3M -pix_fmt yuv420p -c:a aac -movflags faststart demo_final.mp4参数说明-b:v 3M限制视频码率-pix_fmt yuv420p保证任何播放器都能解码-movflags faststart让视频边下边播不用等全部加载。如果不想装 scrcpy用 Android Studio 自带的 Logcat 提供的设备录屏也行但分辨率固定不如 scrcpy 灵活。视频时长控制在 90150 秒前 10 秒一句话说明玩法接着 15 秒展示编辑器场景结构和物理分组中间 60 秒是完整的真机通关演示包括故意死一次再重试最后 15 秒切到代码编辑器讲其中一段关键实现结尾放一张关卡数据配置截图。演示视频里没有真人声音也没关系背景音乐 字幕就是及格水准。4.3 PPT 结构、讲稿节奏与常见扣分点PPT 控制在 1014 页每页只留一个结论。功能清单页用表格列功能优先级物理分组页贴碰撞矩阵的截图代码页不要粘贴源码而是用伪代码表达流程。页与页之间删掉“谢谢观看”之类的过渡页答辩时间很紧凑。常见的扣分点有三个一是 PPT 和实际项目版本对不上PPT 里写“支持暂停”演示的时候没这功能这是立场性问题二是视频里用的关卡和现场演示的不是同一关评审会直接问“这关是哪来的”三是把源码包发给老师前没有打开验证过解压报错印象分直接清零。我的做法是提交前把 zip 解压到另一台电脑按 README 重新构建一遍再交。5. 闯关可玩性加分项与答辩前的快速自检前面做到了项目已经及格。想在答辩现场被多问几句、多拿几分加两个手感部件土狼时间和跳跃缓冲。这是平台跳跃类游戏标准的手感优化代码量很小但效果极其明显。5.1 手感加分土狼时间与跳跃缓冲土狼时间的意思是玩家走出平台边缘后的 0.10.15 秒内仍然允许起跳消除“明明站在边缘却跳不起来”的挫败感。跳跃缓冲是反过来玩家在落地前 0.1 秒按下跳跃按钮落地瞬间自动弹出跳跃消除“按了没反应”的延迟感。// PlayerCtrl.js 中追加两个计时器属性 properties: { coyoteTime: 0.12, // 掉出平台后仍可起跳的宽容时间 jumpBufferTime: 0.1 // 落地前按跳保留 0.1 秒的跳跃意图 }, update(dt) { // 土狼时间在地面清零离开地面开始累计 this.coyoteTimer this.groundContacts 0 ? 0 : this.coyoteTimer dt; // 跳跃缓冲记录“想跳”的意图持续递减 this.bufferTimer Math.max(0, this.bufferTimer - dt); }, onTouch() { this.bufferTimer this.jumpBufferTime; // 先记意图不一定马上跳 this.jump(); }, jump() { // 条件土狼时间未超限且跳跃意图还保留着 if (this.coyoteTimer this.coyoteTime this.bufferTimer 0) { this.bufferTimer 0; this.coyoteTimer this.coyoteTime; // 防止一次落地触发连跳 const v this.body.linearVelocity; this.body.linearVelocity cc.v2(v.x, 0); this.body.applyLinearImpulse(cc.v2(0, this.jumpImpulse), this.body.getWorldCenter(), true); } }实现原理是两套独立计时器coyoteTimer 判断“还能不能跳”bufferTimer 判断“有没有跳的意图”两者同时成立才真正执行冲量。加完之后把 coyoteTime 调到 0.12、jumpBufferTime 调到 0.1让三个没玩过的人试玩观察他们是不是“明明感觉能跳却跳不出来”如果是把 coyoteTime 加到 0.15。5.2 物理调试、FPS 与提交前的快速自检物理调试对新手做碰撞特别有用把调试绘制打开屏幕上会画出所有碰撞体和关节// GameMgr 里临时打开发布前必须注释掉 const pm cc.director.getPhysicsManager(); pm.debugDrawFlags pm.DrawBits.e_aabbBit | pm.DrawBits.e_shapeBit;再用几行代码挂一个 FPS 标签真机上手一测就知道自己的优化水平// FpsLabel.js挂在 Canvas 下的文本节点上 cc.Class({ extends: cc.Component, update() { this.getComponent(cc.Label).string Math.round(1 / cc.director.getDeltaTime()) FPS; } });提交前按下面这组问题在真机上过一遍比任何自查表都实在连续两次快速起跳穿过薄平台后会不会复位角色卡进平台内超过 0.5 秒视为碰撞体参数不合格。死亡重试后分数、位置、敌人巡逻原点是否全部归位安卓返回键处理没有菜单层按返回应该弹确认框游戏中按返回应该暂停或回菜单直接闪退是硬伤。锁屏 30 秒再恢复游戏计时和音效状态是否正常同一关在 720×1280 和 1080×1920 两台真机上各跑一遍按钮是否被遮挡、画面是否被裁切最后把调好的手感参数备份到 LevelConfig 里换机器重打包也不至于丢了那套试了两个小时的数值。本文还有配套的精品资源点击获取
返回列表