
简介面向 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低端机回退RGBA88885.2 三个我踩过的深坑第一个坑是鱼转向。用atand或atan2计算角度时cocos的angle方向是水平向右为正方向如果美术给的鱼预制体默认朝上必须修正角度偏移。我当时没注意结果鱼群集体侧着身子游很诡异。第二个坑是金币飘动时坐标混用。金币节点从世界坐标飘到UI坐标一定要先做坐标转换再跑tween。我之前图省事直接把世界坐标和目标坐标混在一个tween里结果是金币明明在鱼身上飘的时候却齐刷刷往屏幕左下角跑找半天才反应过来是坐标空间没统一。第三个坑是编辑器里运行正常打包成apk后一进游戏就闪退。排查了很久才发现原来是资源加载路径里面有的用大写开头有的用小写开头在Windows的编辑器环境下没问题但安卓的资源管理系统严格区分大小写对不上就直接闪退。后面把所有加载路径写成了统一的规范格式并把资源加载用AssetManager封装了一下闪退问题彻底解决。做完这个项目我个人最大的体会是捕鱼游戏就像cocos creator引擎的一块试金石。可能有人觉得它是“小游戏”没什么含金量但做下来之后你会发现对象池、事件驱动、动画曲线、碰撞检测、性能优化、打包发布这些在真实项目中才会遇到的硬问题它全给你串了一遍。如果你想学cocos creator但不知道做什么练手捕鱼游戏是个非常合适的里程碑项目难度适中需求明确做完之后再回头做其他2D游戏很多套路是可以直接平移过去的。这个项目后续也有很大的扩展空间加战斗排行、锁定目标、多倍率炮弹、共享鱼群玩法深度是靠迭代堆出来的但这些底层框架都不用推翻重写。本文还有配套的精品资源点击获取