免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Unity3D捕鱼游戏开发全流程:对象池、鱼群AI与碰撞交互实战

Unity3D捕鱼游戏开发全流程:对象池、鱼群AI与碰撞交互实战 简介面向Unity3D初学者的捕鱼游戏期末作业工程内含完整源程序可直接运行体验捕鱼玩法。项目覆盖C#游戏逻辑、UI界面、音频管理、难度选择等模块并配有清晰的代码结构与说明文档适合课程设计、自学练手或二次开发。压缩包约210.94MB包含约2000个文件以C#脚本744个、Prefab预制体92个、PNG贴图1188张、Anim动画81个、Controller状态机80个、WAV音频41个为主同时带有多套场景、材质、UI样式和Markdown文档便于对照学习。已有1094人学习下载对新手有较高参考价值。通过学习这份工程可以掌握炮台发射、碰撞检测、计分、按钮交互、背景音乐与音量调节、加载进度条、难度切换等常见游戏功能的实现思路也能借助预制体、动画和音频资源熟悉Unity3D的资源组织与复用方式为后续独立开发完整游戏打下基础。1. unity3D期末项目捕鱼游戏的开发定位与成品结构捕鱼游戏常年占据unity3d期末作业选题的热门位置因为场景好看、玩法直接模型、动画、物理、UI、音频覆盖得全稍加改造就能提交答辩。这个项目最常见的坑是一上来先写开火逻辑结果场景里只有一条鱼在那里抽搐。我的建议是把“鱼群在水里游起来”当成最重要的一步等鱼群自己会沿着曲线巡航、遇到干扰知道逃跑再往上加点击撒网和计分整个游戏才立得住。下面按真题项目的开发顺序从场景搭建、鱼群对象池、捕鱼交互、动画AI到打包验证逐段讲代码层级以单场景为主适合初学者拿到源程序后一条线读完。2. 用unity3d搭建海洋海底场景与模型资源组织2.1 导入海洋海底鱼模型与动画资源时的检查项场景构成很简单一个半透明水面、一个海底地形、若干个鱼群预制体、一个粒子系统做气泡。地形不一定要用Heightmap用一个Plane拆成几个小块高低错开摆放再刷上岩石贴图即可。鱼模型在unity3d资源目录里搜“海洋海底鱼模型”能拿到一批带骨骼和待机、游动动画的FBX不要直接拖进Hierarchy先检查导入设置检查项推荐值作用Scale Factor1保证鱼模型尺寸与场景单位一致Animation TypeGeneric决定Animator Controller能否复用Loop TimeTrue游动动画必须循环Optimize Game ObjectsFalse保留骨骼层级方便挂碰撞体导入后把鱼模型分别存成Fish_01、Fish_02这样的PrefabCollider统一用Box并包住身体主体避免用Mesh Collider在碰撞检测上浪费性能。初学者容易忽略的是FBX默认的Scale Factor如果被改成0.01模型会缩到极小在Scene视图里不明显放进对象池后子弹怎么都瞄不准多半就是缩放问题。2.2 水面材质参数与海底灯光布置半透明水面直接用Standard的Transparent模式做比用Water Shader简单期末项目不会因为水面波动特效加分反而容易掉帧。水面材质需要调整三个参数Rendering Mode设为Transparent颜色Alpha值设在0.18到0.35之间能透过水面看到下面的鱼群Smoothness设在0.6左右让光源在水面形成一层高光场景照明方面主方向光用Soft Shadows颜色偏白Intensity约1.2海底微弱的蓝紫光由一个Point Light补足Range取视距的两倍Intensity不超过1.2。这样鱼模型表面的漫反射会保留层次不会像被灯光直射一样发白。2.2.1 用C#脚本调整水面的半透明颜色在unity3d里修改透明度需要走Material.SetColor链路。可以用这段最简代码在游戏启动时让水面颜色慢慢变到目标值using UnityEngine; public class WaterAlphaController : MonoBehaviour { public Material waterMaterial; [Range(0.1f, 0.8f)] public float targetAlpha 0.25f; private float currentAlpha 0.5f; void Update() { currentAlpha Mathf.Lerp(currentAlpha, targetAlpha, Time.deltaTime * 0.8f); Color color waterMaterial.GetColor(_Color); color.a currentAlpha; waterMaterial.SetColor(_Color, color); } }这段代码每帧访问材质并修改alpha会产生少量SetPass开销不适合多水面场景但期末单相机项目完全可以接受。targetAlpha越低越能看到鱼但水面高光会丢失0.25配合蓝绿色背景观感最自然。气泡用粒子系统实现Start Lifetime取3到5秒Start Size取0.02到0.08Shape选Sphere并设半径0.1Simulation Space设为World。气泡数量超过200个时把Max Particles改成100否则底部粒子会挤成一片白雾。测试运行时能看到鱼群在水面下巡游、水面透明就可以进入下一阶段。3. 用对象池控制unity3d鱼群游动逻辑3.1 为什么写FishPool而不直接Instantiate直接Instantiate鱼在Scene视图里看效果最直观但捕鱼游戏里每条鱼都要反复经历产生、受击、消失的过程。如果一分钟内创建上百条鱼GC Alloc会让移动端帧率明显下降。对象池的处理方式是游戏启动时创建固定数量的鱼捕获后停用而不是销毁等鱼重新激活后再回到池子。这样源程序所有文件里不需要额外插件纯C#就能完成。FishPool挂在场景空物体上做成单例using System.Collections.Generic; using UnityEngine; public class FishPool : MonoBehaviour { public static FishPool Instance { get; private set; } [SerializeField] private FishAgent fishPrefab; [SerializeField] private int poolSize 36; [SerializeField] private float spawnRadius 18f; private readonly ListFishAgent pool new ListFishAgent(); void Awake() { Instance this; } void Start() { for (int i 0; i poolSize; i) { FishAgent fish Instantiate(fishPrefab, transform); fish.gameObject.SetActive(false); pool.Add(fish); } SpawnBatch(poolSize / 3); } public FishAgent Take(Vector3 pos) { FishAgent fish pool.Find(f !f.gameObject.activeSelf); if (fish null) return null; fish.transform.position pos; fish.gameObject.SetActive(true); return fish; } public void Release(FishAgent fish) { fish.StopAllCoroutines(); fish.gameObject.SetActive(false); } private void SpawnBatch(int count) { for (int i 0; i count; i) { Vector2 dir Random.insideUnitCircle * spawnRadius; FishAgent f Take(new Vector3(dir.x, 0, dir.y)); f?.StartCruise(); } } }poolSize决定同屏鱼的数量推荐20到40低端机超过40会有可见的状态切换卡顿。Take方法里的pool.Find是线性查找池子上百条时建议换成Queue期末项目36条远不到性能瓶颈不必过度设计。SpawnBatch只在开局生成三分之一剩下的鱼在后续捕鱼消耗中再取用避免开局画面里鱼群过于密集。3.2 鱼群的巡航移动与转身逻辑单条鱼的移动脚本挂在鱼预制体上使用“目标点 速度Lerp”实现连续转向。这是捕鱼小游戏项目里最常见、也最容易控制的移动方式using UnityEngine; public class FishAgent : MonoBehaviour { public enum FishState { Cruise, Flee, Caught } public FishState state FishState.Cruise; public float baseSpeed 1.2f; public float maxSpeed 3f; public float turnSpeed 2.5f; public int scoreValue 10; private Vector3 targetPoint; private Vector3 currentVelocity; private float fleeTimer; void Start() { PickNewTarget(); } void Update() { if (state FishState.Caught) return; Vector3 steer; if (state FishState.Cruise) { if (Vector3.Distance(transform.position, targetPoint) 0.8f) { PickNewTarget(); } steer (targetPoint - transform.position).normalized * baseSpeed; } else if (state FishState.Flee) { fleeTimer - Time.deltaTime; if (fleeTimer 0f) { state FishState.Cruise; PickNewTarget(); } Vector3 away (transform.position - Camera.main.transform.position).normalized; steer away * maxSpeed; } else { steer Vector3.zero; } currentVelocity Vector3.Lerp(currentVelocity, steer, turnSpeed * Time.deltaTime); transform.position currentVelocity * Time.deltaTime; if (currentVelocity.sqrMagnitude 0.01f) { transform.forward Vector3.Slerp(transform.forward, currentVelocity.normalized, turnSpeed * Time.deltaTime); } Animator animator GetComponentAnimator(); if (animator ! null) { animator.SetFloat(swimSpeed, currentVelocity.magnitude); } } public void PickNewTarget() { Vector2 r Random.insideUnitCircle * 12f; targetPoint new Vector3(r.x, Random.Range(-0.6f, 0.6f), r.y); } public void StartCruise() { state FishState.Cruise; PickNewTarget(); gameObject.SetActive(true); } }3.2.1 游动参数与转向时的坐标限制baseSpeed为1.2时鱼在画面上大约每秒前进12个场景单位适合普通水下场景turnSpeed取2.5到4之间视角上最接近真实鱼转身小于1则鱼头长时间不朝前进方向大于6则会出现抖动。PickNewTarget里的Y范围限制在±0.6防止鱼游出水面或扎进地形。参数推荐范围说明poolSize20~40超过40移动端可能掉帧baseSpeed0.8~2.0低于0.8鱼群显得死板maxSpeed2.0~5.0逃跑时的峰值速度turnSpeed1.5~6.0限制转弯平滑度spawnRadius12~30开局鱼的分散程度如果希望鱼群像捕鱼达人那样结群游动比较朴素的做法是在场景里放一个空对象作为Leader让每条鱼的目标点相对Leader偏移再让Leader按矩形路径移动。前一种随机换点代码更少够交期末项目后一种巡游效果更自然但多一个偏移参数需要调。4. unity3d捕鱼交互系统点击、渔网与碰撞计分4.1 从摄像机射线到渔网生成捕鱼玩法的操作方式通常有两种瞄准鱼发射子弹和点击撒网。期末项目推荐撒网实现代码量更少碰撞反馈也更直观整群鱼被同时网住的效果比单发子弹好得多。玩家点击屏幕后先用Camera.main的ScreenPointToRay生成一条三维射线再用Physics.Raycast求出落点using UnityEngine; public class NetThrower : MonoBehaviour { public GameObject netPrefab; public float throwRange 100f; public Camera mainCamera; void Update() { if (!Input.GetMouseButtonDown(0)) return; Ray ray mainCamera.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, throwRange)) { GameObject net Instantiate(netPrefab, hit.point, Quaternion.identity); net.GetComponentNetEffect().Initialize(); } } }throwRange取100即可覆盖水下可见范围。注意Physics.Raycast只识别带Collider的物体水面Plane即使透明也会挡射线所以水面不要加碰撞器或者把碰撞器设成Trigger海底地形则必须加Collider否则渔网落不到正确深度。4.2 渔网展开与碰撞判定渔网预制体上放一个Sphere Collider从极小的半径在0.3秒内扩大到2.5勾选Is Trigger。这样网不需要写移动逻辑只在点击点原地展开using System.Collections; using UnityEngine; public class NetEffect : MonoBehaviour { public float expandTime 0.3f; public float maxRadius 2.5f; private SphereCollider col; void Awake() { col GetComponentSphereCollider(); col.isTrigger true; col.radius 0.01f; } public void Initialize() { StartCoroutine(Expand()); } IEnumerator Expand() { float t 0f; while (t expandTime) { t Time.deltaTime; col.radius Mathf.Lerp(0.01f, maxRadius, t / expandTime); yield return null; } yield return new WaitForSeconds(0.15f); Destroy(gameObject); } void OnTriggerEnter(Collider other) { FishAgent fish other.GetComponentFishAgent(); if (fish null || fish.state FishAgent.FishState.Caught) return; fish.state FishAgent.FishState.Caught; fish.GetComponentAnimator().SetTrigger(caught); GameUI.Instance.AddScore(fish.scoreValue); FishPool.Instance.Release(fish); // 可选在这里显示飘字加分 // UIManager.Instance.ShowFloatingScore(fish.scoreValue, transform.position); } }OnTriggerEnter的触发条件是渔网碰撞体与鱼碰撞体至少一方为触发器并且另一方的Rigidbody存在。给鱼预制体加Rigidbody比较稳妥Use Gravity项设为FalseInterpolate设为Interpolate。这样渔网碰到鱼时捕获响应基本不会延迟。4.2.1 触发器参数调校参数推荐值备注expandTime0.3s小于0.2s玩家反应不过来maxRadius2.5比大鱼模型半径大1.5倍单条鱼捕获耗时0网碰到即捕获scoreValue10~50小鱼10大鱼50渔网展开期间连续碰到两条鱼会触发两次OnTriggerEnter所以用fish.state Caught做守卫防止同一鱼被重复计分。鱼被Release后停用但渔网协程还会继续运行直到Destroy这段残留时间正好播放渔网收拢动画。4.3 用数学平面替代地形射线定位撒网点如果不想让渔网被海底地形的高低起伏影响可以绕开物理射线用数学平面求交public static Vector3 ScreenToSeaPlane(Camera cam, Vector3 screenPos, float seaLevel 0f) { Plane plane new Plane(Vector3.up, new Vector3(0, seaLevel, 0)); Ray ray cam.ScreenPointToRay(screenPos); plane.Raycast(ray, out float enter); return ray.GetPoint(enter); }这种方式的优点是渔网生成位置和鱼碰撞完全分离点击水面任意一点都会生成渔网不需要地形Collider参与。把seaLevel设为海底地形高度加0.4网就在离海底半路空中展开视觉效果比直接贴地自然。提示如果点击后渔网出现在相机背后检查取的是不是mainCamera场景里存在第二个投影相机时ScreenPointToRay拿到的是depth最大的相机视图。5. 鱼类的动画状态机与逃跑AI实现5.1 鱼受击动画状态切换鱼模型一般自带idle、swim、die三段动画。把这三段放进Animator Controller后控制器参数建议这样设计Animator参数类型用途swimSpeedFloat驱动BlendTree里的游动速度caughtTrigger受击后播放捕获动画fleeBool逃跑状态切换更快的游泳动作BlendTree设置成idle在0速度、swim在1速度、fleeRun在3速度。FishAgent在Update里把currentVelocity.magnitude传给swimSpeed这样转向和加速过程里动画是连续过渡的不会突然切段。捕获动画从触发到消失之间要留出播放时间用协程管理生命周期比较清晰public IEnumerator CaptureSequence() { state FishState.Caught; Animator animator GetComponentAnimator(); animator.SetTrigger(caught); yield return new WaitForSeconds(0.25f); gameObject.SetActive(false); FishPool.Instance.Release(this); }让鱼自己管理受击函数好处是动画播放、回池整套逻辑都在FishAgent内部完成NetEffect不需要关心鱼的动画状态只需要设置状态和加分。5.2 玩家靠近时的逃跑逻辑鱼AI做到“被玩家近距离惊扰就逃跑”就够期末展示。状态机维持三个状态Cruise巡航、Flee逃跑、Caught被捕获。逃跑逻辑的关键是远离玩家方向的计算public float fleeRadius 8f; public float fleeDuration 2f; void Update() { if (state FishState.Caught) return; if (state FishState.Cruise) { float distance Vector3.Distance(transform.position, Camera.main.transform.position); if (distance fleeRadius) { state FishState.Flee; fleeTimer fleeDuration; Animator animator GetComponentAnimator(); animator.SetBool(flee, true); } } else if (state FishState.Flee) { fleeTimer - Time.deltaTime; if (fleeTimer 0f) { state FishState.Cruise; PickNewTarget(); Animator animator GetComponentAnimator(); animator.SetBool(flee, false); } } }Flee状态下的移动方向和Cruise不同不要再用PickNewTarget产生的随机点而是让鱼沿玩家反方向加速。做法是把目标点替换成当前朝向的反向延长位置原第3章的移动逻辑会自动让鱼转向并加速离开。5.2.1 fleeRadius的边界设置fleeRadius取8表示以玩家相机为球心、半径8的球体内鱼会逃跑。小于4鱼群毫无反应难度过低大于12几乎所有鱼都在跑渔网失去目标。Flee持续时间设2到3秒太短会出现鱼回头游进渔网的现象。逃跑到边界时还需要限制鱼的移动范围最简单的方法是在FishAgent里加一个半公开的HomeCenter字段PickNewTarget生成的点如果距离原点超过20就沿原点方向压回18。这个限制要放在移动逻辑外否则鱼会在边界反复折返。6. 期末项目交付前源程序结构、性能验证与打包要点6.1 用Profiler定位帧率瓶颈在unity3d里打开Window Analysis ProfilerPlay状态下跑两分钟重点看CPU Usage里PlayerLoop的Physics.Simulate和Animation.Update各占多少。捕鱼项目最常见的瓶颈有两个鱼群同时开启实时阴影Shadow Casting开销飙升渔网粒子特效没有限制总量。对策是把鱼Prefab的Cast Shadows设为Off节省0.5到1毫秒的CPU时间。第4章最简版本的渔网是Instantiate加Destroy期末场景同屏渔网不会超过6个GC影响有限可以保留如果改成子弹式射击就需要为渔网单独做对象池。验证标准以20条鱼加撒网20次为基准编辑器里帧率不低于60fps真机不低于40fps就可以提交。6.2 打包前检查和Android构建参数构建前清理输出目录是机械操作但经常被忽略rm -rf Build/ mkdir -p Build/Android在File Build Settings里切到Android平台包名填com.school.unity3dfishMinimum API Level选Android 7.0Scripting Backend用IL2CPPTarget Architecture勾选ARM64。Texture Compression选ASTC后包体更小且不会出现部分机型纹理偏红的问题。构建完成后用命令行看Unity日志adb logcat -s Unity重点看两类信息着色器编译警告和GC Alloc。日志中出现“Unable to find shader”时说明水面材质引用了旧版Shader直接换成Standard的Transparent模式即可。过检项标准鱼停止移动后动画是否回到IdlePlay模式观察2分钟渔网碰到鱼是否只加一次分单网捕获5条分数差扣一个分数值真机帧率20条鱼加水面透明特效不低于40FPS工程目录无未使用素材Assets总大小不超过1GB最后补一个常用调试技巧在NetThrower里临时加一行Debug.Log($net at {hit.point})真机跑一次后通过logcat看坐标是否符合海底平面高度。如果落点浮在海床上方0.5米以上回到ScreenToSeaPlane检查seaLevel是相对高度还是世界高度统一用transform.position.y作参照就能解决。本文还有配套的精品资源点击获取
返回列表