简介:这是一份基于 Unity3D 的街机捕鱼游戏完整源码,主要面向 Unity 初中级开发者、游戏策划及希望深入理解游戏逻辑的爱好者。项目基于 Unity5.3.5 构建,完整覆盖鱼群生成、炮台发射、子弹碰撞、鱼种分值、捕获概率、炮台升级和道具等玩法系统,并通过 C# 脚本实现游戏对象管理、用户输入处理、物理碰撞响应、UI 界面以及音效动画控制。资源包共 9122 个文件,包含 425 个 C# 脚本、663 个 Prefab 预制体、41 个 Unity 场景、98 个材质、1303 张 PNG 贴图、43 个 WAV 音频、22 个 Shader 着色器及 FBX 模型等,压缩包大小约 215.81MB,目录结构清晰,便于分段查阅。该资源已有 2904 人学习/下载。通过学习这套源码,可以完整掌握 Unity3D 游戏逻辑的编写方式,并借助现有框架快速自定义炮台、鱼种、道具和奖励机制,打造专属的街机捕鱼玩法。
1. 项目整体设计与思路拆解
1.1 街机捕鱼游戏的核心玩法与开发价值
街机捕鱼这类游戏,说到底就是一套“金币循环系统”:玩家用金币买炮弹,炮弹打鱼,鱼死亡后掉金币,金币再换炮弹。核心爽感来自两个层面,一个是画面上的特效轰炸,另一个就是概率带来的刺激感。这套循环看起来简单,但要做得好玩、让玩家愿意持续投币,背后全是数据和节奏的设计。作为Unity3D开发者,我第一次完整拆解这类源码时才发现,真正值钱的不是那几百行渲染代码,而是它的对象池、路径规划、碰撞判定和概率加权这套组合拳。
从Unity3D游戏开发的角度看,街机捕鱼是一个极其典型的“轻量级但高密度”项目。它不像MMORPG那样有复杂的服务器架构,也不像开放世界那样需要大规模场景流送,但它几乎涵盖了2D游戏开发的所有核心知识点:对象池管理、物理碰撞、动画控制、UI系统、音视频播放、粒子特效、数据持久化。尤其适合中级开发者去通读源码,一次搞懂一个游戏从零到上线需要的全部环节。
这也就是为什么我要写这篇拆解的原因。你手里如果有一份Unity3D街机捕鱼游戏源码,不要急着改贴图、换数值,先把整个系统的骨架理清楚,知道哪些模块是通用的、哪些模块是可以替换的。你真正复用的是这套框架,而不是照着原样抄一遍。接下来我会按实际开发顺序,把核心系统的设计和代码思路掰开揉碎讲一遍。
1.2 为什么选Unity3D而不是UE5来复刻这套玩法
很多刚接触游戏开发的朋友都会问:街机捕鱼这种2D偏3D表现的项目,Unity3D和UE5到底选哪个?我直接说结论:做这类商业街机捕鱼,Unity3D是更务实的方案。
从渲染管线来说,UE5的强项是把Lumen全局光照和Nanite虚拟几何体用在大型3D场景里,但这套东西在捕鱼游戏里几乎用不上——捕鱼场景大部分是固定的水族箱式镜头,甚至很多项目直接就是纯2D序列帧,你不可能用Nanite去渲染一张鱼的贴图。Unity3D这边,URP管线加一堆Shader特效足以支撑水体焦散、鱼鳞高光和子弹拖尾,而且包体小、启动快,这对街机一体机和低端Android设备尤其重要。很多线下街机框体用的就是安卓系统,Unity3D的跨平台打包方案比UE5成熟得多。
再从开发效率看,Unity3D的UGUI在2D UI交互上有绝对优势,而捕鱼游戏恰恰是UI极其密集的项目:炮台倍率切换、金币面板、排行榜、设置弹窗、活动入口,恨不得一个界面上挤十来个交互点。UGUI的Canvas层级管理和RectTransform适配,能做到一套UI自适应各分辨率;UE5的UMG虽然也能做,但在快速迭代和热更新配套上明显更繁琐。所以,如果你想快速上线并频繁调数值,Unity3D是正解。
1.3 源码项目里最常见的模块划分方式
拿到一份完整的Unity3D街机捕鱼游戏源码,建议先按目录结构去反推它的架构。我见过的大多数商业级源码,文件夹划分基本长这样:
- Scripts/Game/Core:核心循环,包括GameManager、EventManager、PoolManager
- Scripts/Game/Entity:鱼、炮台、子弹、金币这些实体类
- Scripts/Game/System:刷怪系统、移动路径系统、碰撞系统、奖励系统
- Scripts/UI:所有面板控制器和UI组件绑定
- Scripts/Data:配置表读取,包括鱼种类、概率表、炮台倍率表
- Prefabs/:鱼、子弹、特效、UI的预制体
- Art/:贴图、图集、动画、音效
- Resources/ 或 AssetBundles/:动态加载的资源目录
这个划分本身就很值得学习。它把表现层(Prefab和Animation)和逻辑层(Script)做了强分离,你在调概率、改路径的时候不需要去翻任何美术资源,在改鱼的外观时也不需要动一行逻辑代码。后面我讲的具体系统,都是在这个模块划分基础上展开的。你手里的源码如果目录乱得一塌糊涂,那大概率是教学版或半成品,重构优先级应该排第一位。
2. 核心系统解析与实操要点
2.1 鱼群生成与移动路径:从固定路径到贝塞尔曲线
鱼的移动路径看着是小事,实际决定了玩家手感的60%。早期很多源码里鱼就是沿着一条直线从屏幕边缘游到另一边,做完之后你会发现特别生硬。业内普遍的做法是用“路点队列 + 曲线插值”来控制鱼的游动。
所谓路点队列,就是一条鱼从出生到销毁,会依次经过若干个世界坐标点,鱼每到达一个点就去追下一个点。每个路点之间,可以用Vector3.Lerp做线性插值,也可以用Vector3.SmoothDamp模拟缓慢转向的鱼尾摆动效果,这样移动看起来就自然得多。更进阶的源码会直接上贝塞尔曲线——把原来的一串路点当作控制点,用一个BezierCurve类去逐帧采样,鱼的速度和方向完全由曲线导数决定。
写刷怪系统的时候,有三类参数一定要单独做成配置:每波鱼群的间隔时间、每波鱼的总数量、特殊鱼(比如Boss鲨鱼)出现的小概率。我见过一份源码是把这些参数直接写在Update里的,一旦策划要调难度,你就得重新编译整个项目,这属于灾难级设计。合理的做法是把它们放到一个FishWaveConfig的ScriptableObject里,策划改配置,程序不用管任何逻辑。哪怕你现在的项目就你一个人开发,也建议养成这个习惯,后面对接远程配置或热更新时会轻松很多。
2.2 炮弹发射与碰撞检测:物理引擎的正确打开姿势
炮弹发射看起来简单,但有一个细节容易踩坑:炮弹不应该用角色控制器或物理驱动的方式移动。街机捕鱼里的子弹飞行速度极快,如果直接用Rigidbody加Force来推动,物理引擎每帧的迭代次数根本不够用,弹道会发飘。正规源码里子弹都是在Update里直接改transform.position,然后靠一个独立的碰撞体去触发判定。
碰撞判定有两种写法。第一种是用OnTriggerEnter2D,把子弹的Collider设置为Trigger,一旦碰到鱼的Collider就进入命中流程。第二种是在子弹Update里做Physics2D.OverlapCircle检测,对子弹周围一定半径内的鱼做射线检测或圆形检测。我自己的经验是:大概率触发方式更稳,因为它天然避免了一个子弹在极快帧间穿过鱼体导致漏检的问题,配合刚体碰撞的Continuous模式会更保险。
这里有一个很容易被忽视的致命细节:子弹碰到鱼后,只做一次判定,然后就销毁子弹,不管这条鱼有没有被打死。很多新手会写成子弹在命中瞬间就去调鱼的血量,然后鱼死了特效也播放了,结果一发子弹同时穿死了两条鱼,金币哗哗出,经济系统崩掉。正确流程应该是子弹命中后,先走概率系统判定是否捕获成功,成功则调用鱼的死亡流程并生成金币,失败则只播放一个水花特效并销毁子弹,鱼继续游。
2.3 捕鱼概率与奖励计算:动态概率表的玩法设计
捕鱼游戏能不能留住玩家,几乎全看概率系统怎么设计。纯随机肯定是死路一条,因为纯随机意味着从统计上看,玩家的投入和产出在长时间后会趋近一个固定比例,体验会变得平淡,而且一旦脸黑连输十几把,基本就流失了。
好的源码里一定是动态概率,业内叫RTP(Return To Player,玩家回报率)。系统会实时统计每个玩家最近几十发炮弹的投入产出比,然后动态微调接下来每一条鱼的捕获概率。比如说参数设定了基准RTP在75%到85%之间浮动,当玩家最近连输、投入产出比低于75%时,系统就会提高大鱼捕获权重,让玩家“回一口血”,保持爽感;反过来如果玩家已经在连赢,RTP超过90%,系统就会悄悄降低捕获率,把差值收回来。
概率表的设计同样有讲究。每类鱼都有一个基础捕获权重和一个奖励倍数,大鱼的权重低但倍数高,小鱼的权重高但倍数低。子弹的倍率也会影响鱼的权重:玩家用100倍率打小鱼,系统会判定为“过度捕杀”,捕获权重会被压得极低,逼着玩家去合理匹配倍率。这一段逻辑建议大家照着源码慢慢啃,是整份源码里含金量最高的部分。
3. 实操过程与核心环节实现
3.1 场景搭建与摄像机设置:让鱼缸活起来
捕鱼场景本身并不复杂,但有几个设置要提前做对。
第一,摄像机一定要设置成正交模式(Orthographic)。捕鱼游戏是2D表现,正交摄像机才不会出现近大远小的透视畸变,也方便你做UI与3D场景的对齐。同时把Size调整到能让屏幕恰好显示完整的水族箱区域,一般情况下Size设为ScreenHeight / 2 / PixelsPerUnit,假设你的背景图是1920×1080,PixelsPerUnit是100,那Size就是1080 / 2 / 100 = 5.4。
第二,背景要分两层。一层是静态的水底贴图,另一层是动态的水波Shader或序列帧动画。动态层用Camera的Clear Flags设为Solid Color,静态层直接铺在场景里,动态层用一个半透明的Quad挂在摄像机前方来回摆动,就可以模拟出水流动的感觉。这个技巧在源码里不一定专门写了文档,但几乎所有商业项目都是这么干的。
第三,鱼的出生点不要做成单一位置,要沿着屏幕四条边均匀布点。常见的做法是把屏幕四边各分成8到12个等分点,每次刷鱼时随机选几个点作为出生点,鱼从这些点沿着朝向屏幕中央的方向游动。这样玩家就不会看到鱼全从同一个角落冒出来,沉浸感会好很多。
3.2 对象池:解决同屏百条鱼卡顿的关键
我做Unity3D项目踩过最大的一个坑,就是没有在第一时间做对象池。捕鱼游戏最极端的场景里,同屏鱼可能超过80条,再加上每一条鱼被击中时都要生成一堆金币和粒子特效,如果全部用Instantiate和Destroy,每帧的GC开销会直接把帧率从60拖到20。正式源码里,对象池是绝对标配,而且通常还会单独封装成一个PoolManager单例。
一个简单可靠的PoolManager实现思路是这样的:用一个Dictionary按预制体名称存栈,需要生成对象时先从栈里取,取不到再Instantiate,用完不Destroy而是SetActive(false)再进栈。关键点是每个池子要设置最大容量,超出容量的对象直接Destroy掉,防止长期运行后池子无限膨胀。比如子弹池最大容量设200,鱼池设100,金币池设300,完全可以覆盖街机场景的峰值。
public class PoolManager : MonoBehaviour { public static PoolManager Instance; private Dictionary<string, Stack<GameObject>> poolDict = new Dictionary<string, Stack<GameObject>>(); private void Awake() { Instance = this; } public GameObject Spawn(string key, GameObject prefab, Vector3 pos, Quaternion rot) { GameObject obj = null; if (poolDict.ContainsKey(key) && poolDict[key].Count > 0) { obj = poolDict[key].Pop(); obj.SetActive(true); } else { obj = Instantiate(prefab, pos, rot); } obj.transform.position = pos; obj.transform.rotation = rot; return obj; } public void Despawn(string key, GameObject obj, int maxCount = 100) { obj.SetActive(false); if (!poolDict.ContainsKey(key)) { poolDict[key] = new Stack<GameObject>(); } if (poolDict[key].Count < maxCount) { poolDict[key].Push(obj); } else { Destroy(obj); } } }真正让对象池发挥威力的地方,是配合“延迟回收”使用。子弹打中鱼后不会立刻回收子弹,而是先播放3帧水花特效,再用协程延迟0.1秒回收。这样玩家的视觉感受是子弹真实地“消失”了,而池子的压力也大大降低。鱼死亡的金币特效也是一样,先飞出去,飞完再回收,而不是生成完立刻回收。
3.3 UGUI适配与金币循环反馈
捕鱼游戏的UI不算复杂,但布局逻辑很讲究。炮台倍率切换按钮一般放在屏幕右下角,玩家用拇指就能点到;金币面板在左上角,实时展示当前金币数和历史最高分;开火按钮就在炮台旁边。这些布局看似随意,实际上全部参考了街机框体的物理按键位置,做移动端适配时不要随便挪。
UGUI这块还有一个容易出性能问题的地方:金币数字的Text组件如果用默认的Legacy Text,每次变化都会触发一次Canvas重建。捕鱼游戏里金币数字几乎每秒都在跳,频率一高,CPU开销立刻上去了。解决方法是把金币文本换成TMP(TextMeshPro),或者干脆用Sprite数字来自定义更新,只改显示对象的Sprite,不触发文本重建。很多源码就是因为没做这个替换,导致大量时间耗在UI Mesh重建上。
连击的反馈也依赖UI。当玩家连续命中鱼时,可以用一个缓慢放大的连击数字配合屏幕边缘的泛光特效,这个在源码里通常用DoTween或自写的Lerp脚本控制CanvasGroup的透明度和Scale。注意连击数字的缩放一定要有弹性节奏,不要用线性缩放,那样会显得很假。用的公式一般是scale = 1 + 0.3 * Mathf.Sin(Time.time * 20),在击中的一瞬间给一个瞬间冲量,再衰减回来。
4. 常见问题与排查技巧实录
4.1 子弹打不到鱼 / 碰撞检测失效
这个是最常见的初学者问题。排查路径按优先级走:先看鱼的Collider和刚体有没有配置好,鱼的Rigidbody2D必须是Dynamic或者至少是Kinematic,否则静态碰撞体会和子弹的Trigger产生穿透;再看Layer Collision Matrix,如果子弹和鱼被放到了不相干的Layer里且没勾选碰撞关系,物理系统会直接忽略它们;最后确认子弹碰撞体是否勾了Is Trigger,这个忘了勾会导致物理引擎走完全不同的碰撞阶段。
另一个很容易被忽略的坑是鱼的“假移动”。如果鱼是用动画里的位移去移动,而不是直接改Transform,那鱼的Collider每帧都在动画系统计算的瞬移位置上更新,物理检测极不稳定。正确做法是动画只负责鱼鳍摆动和鱼身旋转,位置移动始终交给逻辑层,两个系统各管各的。
4.2 帧率掉到30以下,怎么定位瓶颈
遇到掉帧,先用Unity Profiler拍一段采样,别靠猜。捕鱼项目的瓶颈通常集中在三块:粒子特效数量、对象池溢出、UI重建。
粒子特效这一块,我建议限制单个粒子的最大存活时间,并压低粒子总量。比如一个鱼死亡特效,发射粒子数量20个以内就够了,再叠加一点拖尾或者光晕,视觉上已经非常丰富。有些源码里的特效组件是从别的项目抄来的,一上来就发射200个粒子,那再多优化都白搭,直接在预制体里改参数比改任何代码都管用。
对象池溢出的话,Profiler里会看到大量的Instantiate和Destroy调用。对照我上面写的PoolManager,把池子容量调优,特别要注意子弹和金币这两个最频繁的池子。
UGUI的Canvas重建在Profiler里表现为“Canvas.SendWillRenderCanvases”耗时高,解决办法就是我前面说的用TMP或Sprite数字替换Legacy Text。还有一个大招:把静态UI元素所在的Canvas单独拆分出来,并设置Additional Shader Channels为无变化,这样该Canvas就不会在动态元素变化时跟着重建。
4.3 概率调高了玩家赚翻,调低了没人玩
概率调优不是一个固定的公式,而是一套在线调试流程。我实际操作中的做法是:先把概率表做成可运行时修改的配置,比如在Inspector面板里绑定一个ScriptableObject,跑Play模式时直接改数值看效果,然后通过日志输出当前每秒金币变化率,测算RTP。上线后,再通过远程配置平台调整,不要等玩家骂街了才发包。
另外有个小经验:第一次调概率,一定先记录“不调整”情况下的原始RTP基准值,再基于这个基准值做上下浮动的区间控制。比如基准是80%,允许波动范围是70%到90%,超过90%就强制压捕率,低于70%就保送一次大奖。有了这个闭环,玩家的收益曲线就始终在可控范围内。
4.4 源码拿到手后,最快的自定义方式
整理一下,改一份街机捕鱼源码最快上手的路径是这样的:先找到配置表文件和对应的ScriptableObject,改鱼的种类、血量、分值、移动速度和概率权重;再打开场景找一个代表鱼的预制体,把里面的Mesh或Sprite替换成自己的美术资源,注意保持和原预制体相同的尺寸和Collider边界;最后改炮台的倍率配置和UI上显示的文本,这通常在GameManager的初始化脚本里。
我特别建议在自己动手改代码之前,先跑一遍原版游戏,把每一条鱼的表现数值、每个按钮的交互反馈都记下来,再动手替换。这样你能在改错之后立刻回溯,判断问题是出在自己改的配置里还是出在系统逻辑里。我见过太多人拿到源码第一件事删了一大堆“冗余”代码,结果删到核心逻辑,整个游戏直接白屏,连报错都看不懂。改源码,动作要小,每改一步就跑一次验证,这是最稳的节奏。
5. 后续扩展的实用方向
如果你已经把基础流程跑通,下一步可以做三个方向的扩展。第一个是接入远程配置系统,把概率表、鱼群波次、活动参数全部搬到后台,策划不依赖程序员也能随时改经济数值。第二个是增加联机排行榜和好友邀请,捕鱼游戏虽然核心是单人强反馈,但社交模块能显著提升留存率。第三个是把资源包从Resources目录迁移到AssetBundle或Addressables,为后续热更新和活动内容动态下载打基础。
还有一个小细节,Unity3D的Audio Mixer别忘了用。捕鱼游戏里背景水声、炮声、金币声、连击提示音叠加在一起,如果不做分组和音量衰减,所有音效会糊成一团。把音效拆到SFX、BGM、UI三个Group里,BGM做低通滤波,SFX做侧链压缩,这样即使特效满天飞,玩家也能清楚听到金币落袋的声音——那个声音才是捕鱼游戏真正的“充值信号”。
我做这类项目最大的体会是:街机捕鱼的源码看起来不复杂,但它是一套被商业市场反复锤打过的设计模板。把它吃透了,你不仅会写对象池、会配物理碰撞,更会明白一个游戏经济系统是怎么从零搭起来并保持平衡的。接下来你就找一个稳定版本的源码,先从跑起来开始,再一步一步替换、调试、扩展,这个过程走完,你的Unity3D开发能力会有一次很明显的跃升。
本文还有配套的精品资源,点击获取