简介:面向 Cocos Creator 开发者的捕鱼游戏完整工程资源,覆盖场景搭建、脚本编写、碰撞检测、动画控制与道具系统等核心开发环节,适合具备一定引擎基础、希望以真实项目练习休闲游戏开发流程的读者。压缩包共 221 个文件,体积仅 1.78MB,包含 145 个 json 配置、38 个 png 图片、4 个 js 脚本及 ts 入口、fire 场景文件、plist 图集描述等,类型清晰,便于按资源目录逐层拆解鱼群、炮台、子弹等模块。已有 1517 人学习下载。工程代码展示了鱼群游动动画、炮弹发射与碰撞判定、鱼种分值与防御属性、道具与特效触发等捕鱼玩法的具体实现;大量 json 配置体现了数据驱动思路,适合练习分数、炮弹等级、刷新参数等内容的配置方式。内置 gltf 模型或 mp4 演示可用于辅助理解整体运行效果,压缩包仅 1.78MB,轻量但结构完整,是一份能快速上手的实战参考资料。 我自己做捕鱼游戏,起因挺直接:想用cocos creator练手,又不想做换皮幻灯片。捕鱼游戏在cocos creator社区里很常见,搜索能搜到一大堆demo,但真正能跑起来、不卡、能打包上线的完整思路,其实没几个人讲透。做下来之后最大的感受是:这游戏看着休闲,技术含量全在暗处。鱼群要持续生成,炮弹要频繁发射,特效要不断爆开,金币要来回飘,这些高频对象如果直接new和destroy,手机撑不过三分钟。捕鱼游戏适合对cocos creator有基础、想系统性掌握对象池、动画、碰撞、性能优化的开发者。这篇文章把我做捕鱼游戏的完整技术思路拆出来,能帮你少走不少弯路。
1. 整体架构设计思路
1.1 从核心循环到模块拆分
捕鱼游戏本质上是一个循环:玩家花金币发射炮弹,炮弹打中鱼,鱼按倍率吐出金币,金币再变成炮弹。这个循环听起来简单,但实现时要拆的东西其实很多。我个人的原则一直是:不建议把逻辑全堆在一个GameManager里,否则改一个需求要全文件搜索半天。我习惯拆成几个独立模块,每个模块只干自己那一摊事。
- GameManager:只负责游戏流程状态,比如准备中、进行中、结算中。
- FishManager:负责鱼群的生成、回收、波次控制。
- BulletManager:负责炮弹生成、飞行和碰撞处理。
- CoinManager:负责金币结算、飘分动画、数字滚动。
- AudioManager:统一管理音效和背景音乐。
- UIManager:管所有UI面板的开关和数据刷新。
模块之间用事件通信,不要互相直接调用内部方法。比如鱼被打死了,FishManager只管把鱼节点回收,金币怎么结算,把事件发给CoinManager就够了。这样后面要加新玩法,比如特殊鱼掉护盾、定时炸弹鱼,改起来都不会伤筋动骨。事件名我习惯在项目里单独放一个EventName.ts文件统一维护,避免硬编码字符串,后面排查问题也能快速定位。
1.2 数据驱动与场景规划
鱼的种类、炮弹威力、倍率这些参数,如果用if else写死,后面调平衡性会非常痛苦。我习惯用一张fish.json配好所有鱼的基础属性,包括鱼ID、预制体路径、体积半径、基础速度、生命值、金币倍率。配置长这样:
[ { "id": 1001, "name": "小丑鱼", "prefab": "fish/clown", "radius": 30, "speed": 250, "hp": 1, "rate": 2 }, { "id": 1002, "name": "金枪鱼", "prefab": "fish/tuna", "radius": 45, "speed": 180, "hp": 3, "rate": 5 } ]这个问题上我踩过一次坑:一开始把速度、血量直接写在预制体脚本的Inspector面板里,策划要调数值时得一个个打开预制体改,费时还容易漏改。后来全部改成了配置驱动,策划只需要改JSON,代码逻辑一行不用动。
场景规划方面,我用了两个场景。Start场景负责启动和设置,Game场景承载主玩法,所有常驻节点都放在Game场景里。商城、设置这些弹窗全部做成预制体,运行时动态挂载到UI层级下。切换场景的成本比加载预制体高得多,弹窗类用预制体更轻,性能压力也更小。
2. 鱼群系统的实现细节
2.1 对象池:鱼群不卡顿的关键
鱼是捕鱼游戏里生成频率最高的对象,一屏几十条鱼,每条都有游动、碰撞、死亡。如果直接用instantiate和destroy,每销毁一次都会触发引擎资源释放,鱼一多,手机上明显会卡一下。cocos creator提供了cc.NodePool对象池,用法很简单:create一个预制体的池子,get时从池里取,放回时调pool.put。
不过对象池有一个坑:节点放回池里之后,并不会自动reset状态。鱼被回收时可能是旋转过的、透明度改过的、碰撞体开着的,下一次从池里取出来,这些状态还是旧的。所以我在鱼的预制体脚本里写了一个init(data)方法,每次get之后都要调用一遍,把所有状态归位。这个init方法主要做几件事:重置位置、重置角度、恢复透明度、关闭死亡特效节点、重置isDead标志。
提示:从对象池取出的节点,如果忘了reset,会出现“新生成的鱼半透明”或“鱼的角度全乱”这类奇怪问题。排查时首先要检查是不是对象池复用导致的状态残留。
2.2 贝塞尔曲线实现鱼群游动轨迹
鱼不能直挺挺地来回走,那样太假。我用了几个控制点组成一条连续的贝塞尔轨迹,鱼沿着这条轨迹游动,看起来才有那种在水里游弋的感觉。实现时有两条路:一种是用cc.tween的bezierTo逐段拼接,另一种是自己写轨迹点生成器,每隔一定时间把贝塞尔曲线上的点取出来作为目标点。
我选了第二种,因为更可控。我可以根据两帧之间的方向角让鱼转身,这是tween难做到的。鱼转身不能90度瞬间转,要做一个线性转向,从当前角度插值到目标角度。转得慢了鱼会像在漂移,转快了又显得生硬。计算角度时cocos的atan2返回的是弧度制,需要转成角度制再赋值给node.angle,这个细节经常有人写错。
另外,鱼的方向和预制体默认方向可能不一致。如果美术做的鱼头朝右,预制体默认角度0度正好是朝右,那问题不大。但美术给的鱼可能是朝上朝左的,这时候要对角度做一个固定偏移修正,否则鱼看着像是在横着游,特别出戏。
2.3 鱼种配置与波次刷新逻辑
不同鱼种不仅长得不一样,游速、血量、倍率都应该有差异。小鱼速度快、血量低、倍率低,适合作为普通目标;鲨鱼类速度慢、血量厚、倍率高,打中一条带来的爽感很强。配置表里这些值分开配,代码里统一读取,后面调平衡性会非常省心。
刷鱼策略我用了定时器按波次刷。每一波在屏幕边缘随机几个出生点,每个出生点往屏幕中央生成一群鱼,路径可以复用同一套贝塞尔曲线模板,但做缩放和旋转变化,让每波看起来都不一样。这里有个控制点:同时存活的鱼数量要设上限,我一般控制在20到40条以内,超过上限就不再生成,否则手机内存和绘制压力都扛不住。如果想做高密度鱼群玩法,就得配合更激进的对象池和DrawCall优化,后面第四章详细讲。
3. 炮弹、捕获与金币结算
3.1 炮弹的发射、飞行与检测
炮弹同样走对象池。每次点击屏幕时,从炮口节点生成一颗炮弹,方向是点击点方向,然后按设定速度和角度开始飞行。炮弹飞行动作可以用update自己驱动,也可以用cc.tween的by方法移动。我个人习惯在update里手动更新位置,因为后面要做多倍率炮弹、穿透炮弹这些扩展,手动控制更灵活。
碰撞检测这里踩过一个典型坑:炮弹速度很快,如果默认用普通碰撞回调,子弹可能一帧就从鱼身体里穿过去了,根本触发不了碰撞。解决思路有两个:一是适当放大子弹的碰撞半径,让包围盒比子弹视觉体大一圈,这个最简单有效;二是做手动线扫检测,从子弹上一帧位置到当前帧位置做射线,扫到的鱼都算命中。捕鱼游戏的炮弹速度一般不会快到离谱,放大碰撞范围已经够用。另外,碰撞系统要在游戏启动时主动开启,有些版本默认不开,一测试发现子弹穿鱼穿得干干净净。
碰撞处理时,子弹拿到鱼身上的鱼脚本,调它的onHit(damage)方法。鱼脚本内部判断血量,血量扣到0就进入死亡流程。这里要留意的是,不要在处理碰撞的同时直接销毁节点,可以打个标记,等这帧update结束时统一处理,避免在遍历碰撞回调时修改节点树结构导致崩溃。
3.2 鱼死亡的捕获判定与特效处理
捕获判定本身不复杂:子弹碰到鱼,鱼血量扣除,血量小于等于0,标记为死亡。但要注意一个问题:一条鱼可能同时被多发子弹打到,如果不加锁,会重复播放死亡、重复生成金币。我在鱼脚本里加了一个isDead标志,死亡处理只走一次,后续再收到碰撞直接忽略。这个标志在对象池的init方法里必须重置,否则就会出现鱼“死后又复活但不掉金币”的bug。
鱼死亡时要做两件事:播放死亡特效(水花、泡泡、爆炸光效),生成金币。死亡特效也走对象池,不同鱼种可以配不同特效。特效播放结束后把特效节点收回到特效池,鱼本体在爆炸动画播完后再回收。如果鱼本体和特效用同一个节点,需要在put之前停掉所有tween和动画组件,不然节点被池子复用时会继续播放上一个鱼的死亡动画。
3.3 金币结算与飘分动画的时序控制
金币从鱼死亡位置飘到金币栏,这一步如果直接用多个tween同时跑,很容易看到“金币飞一半停住”“乱窜”之类的问题。原因在于同一个节点同时被多个tween控制,或者节点被回收后还在跑tween。我最后改成用CoinManager统一管理:把所有待飘金币放在一个队列里,每次只处理最多5个飘动任务,飘完一个回收一个再飘下一个,保证同一时间在飞的金币数量可控。
飘分数字也做了同样处理:不要直接修改金币UI文本,而是把当前显示值和目标值分开存,用一个缓动差值方式让数字从当前值滚动到目标值。这样数字跳动很舒服,也方便后续做“加金币上飘”的动画。用队列化处理的另一个好处是:游戏进入结算阶段时,能明确知道所有动画是否已经结束,不会出现金币还没飘完就开始结算的状态不一致问题。
这里还要注意一个坐标转换问题:金币从鱼的位置飘到UI金币栏,鱼的位置是世界坐标,金币栏的位置是UI坐标。如果这两个概念混了,金币会飘到屏幕外面去。我的做法是:先在鱼节点世界坐标下创建一个临时根节点,运动的目标点用UI坐标转世界坐标。简单说,tween全程跑在世界坐标空间,或者全程跑在UI节点空间,不要混着来。
4. 性能优化与打包落地
4.1 DrawCall合并与图集管理
捕鱼游戏的素材量非常庞大:鱼种类多,每条鱼还有游动序列帧,加上炮弹、特效、UI,如果不打图集,DrawCall会飙升到200以上,手机端直接卡成PPT。把资源统一打到图集后,DrawCall基本按图集数量来算,效果非常明显。
打图集有几点建议。第一,鱼游动的序列帧要放到同一张图集里,这样一帧动画的切换不会产生额外的DrawCall切换。第二,图集尺寸不要贪大,2048是一个比较平衡的选择。过大容易在某些低端设备上出现加载问题。第三,图集内部要避免大量透明像素,透明区域占多了,即使图集整体尺寸不大,内存占用仍然会膨胀。做的时候可以检查一下,一张2048的图集如果实际有效内容只占一半,那图集还是需要重新整理。
4.2 对象池之外的性能细节
粒子是另一个隐性杀手。打死一条鱼的特效,如果每个粒子都独立绘制,DrawCall依然会很高。最简单的控制方案是:粒子数量不要堆高,水花用8到15个粒子就能有感觉,没必要拉满到50个。这个数值在开发时很容易被忽略,等真机一跑才发现掉帧,再优化又要重新测试。我的习惯是第一次做特效时就刻意限制粒子数,后面预览效果不够再加,而不是一上来就往高里调。
背景水纹、泡泡这类装饰性动画,如果运行在UI节点上用序列帧播放,开销也不小。更推荐的做法是写一个简单的shader做UV位移,一张静态纹理就能模拟水波效果,把整块的GPU压力降下来。装饰性UI上的Frame动画也尽量少用,能用数字滚动就用数字滚动,能用透明度渐变就用渐变,性能和复杂度都更友好。
4.3 cocos creator 打包apk时的注意点
打包安卓apk时,有几个点经常被忽略。第一,纹理格式要提前规划。新机型对ASTC支持好,老机型建议回退ETC2或RGB565,不然会出现贴图发紫发白的现象。我的做法是打两个纹理资源包,按设备能力动态选择下载,虽然工程量多了一点,但兼容性稳得多。第二,如果打出来的apk体积特别大,优先检查是否有散图资源没走图集,或者有没有重复打包的资源。第三,构建路径不要带中文和空格,这个问题在Windows环境特别多,安卓构建时容易因为路径问题报“找不到资源”错误,改了半天代码结果发现是路径的锅。
5. 常见问题与排查技巧实录
5.1 常见问题速查表
| 问题现象 | 可能原因 | 处理建议 |
|---|---|---|
| 鱼群游动卡顿,统计面板有大量节点创建/销毁 | 没使用对象池,update里直接instantiate/destroy | 改成cc.NodePool统一管理,鱼、炮弹、特效全部进池 |
| 子弹穿鱼不触发 | 碰撞体太小或碰撞系统未开启 | 放大子弹碰撞半径,确认CollisionManager启动并开启调试绘制 |
| 鱼死亡特效重复播放 | 多发子弹同时命中,未做防重入 | 鱼脚本加isDead标志,死亡流程只执行一次 |
| 金币飘分乱飞 | 同一节点被多个tween控制,或节点提前回收 | 用CoinManager做队列化飘分,tween回调里先校验节点有效性 |
| 打包APK后贴图白/偏色 | 纹理格式不兼容 | 换ASTC或ETC2,低端机回退RGBA8888 |
5.2 三个我踩过的深坑
第一个坑是鱼转向。用atand或atan2计算角度时,cocos的angle方向是水平向右为正方向,如果美术给的鱼预制体默认朝上,必须修正角度偏移。我当时没注意,结果鱼群集体侧着身子游,很诡异。
第二个坑是金币飘动时坐标混用。金币节点从世界坐标飘到UI坐标,一定要先做坐标转换再跑tween。我之前图省事,直接把世界坐标和目标坐标混在一个tween里,结果是金币明明在鱼身上,飘的时候却齐刷刷往屏幕左下角跑,找半天才反应过来是坐标空间没统一。
第三个坑是编辑器里运行正常,打包成apk后一进游戏就闪退。排查了很久才发现,原来是资源加载路径里面有的用大写开头,有的用小写开头,在Windows的编辑器环境下没问题,但安卓的资源管理系统严格区分大小写,对不上就直接闪退。后面把所有加载路径写成了统一的规范格式,并把资源加载用AssetManager封装了一下,闪退问题彻底解决。
做完这个项目,我个人最大的体会是:捕鱼游戏就像cocos creator引擎的一块试金石。可能有人觉得它是“小游戏”,没什么含金量,但做下来之后你会发现,对象池、事件驱动、动画曲线、碰撞检测、性能优化、打包发布这些在真实项目中才会遇到的硬问题,它全给你串了一遍。如果你想学cocos creator但不知道做什么练手,捕鱼游戏是个非常合适的里程碑项目,难度适中,需求明确,做完之后再回头做其他2D游戏,很多套路是可以直接平移过去的。这个项目后续也有很大的扩展空间:加战斗排行、锁定目标、多倍率炮弹、共享鱼群,玩法深度是靠迭代堆出来的,但这些底层框架都不用推翻重写。
本文还有配套的精品资源,点击获取