简介这份资源是面向Unity 3D初学者与课程作业需求者的第一人称狩猎游戏完整工程适合正在学习游戏开发、需要参考综合案例的学生或独立开发者。项目以第一人称视角为核心玩家手持斧头猎杀野猪与僵尸等怪物并配有背景音乐与打击音效覆盖场景搭建、角色动画、交互设计、音频处理与第一人称控制器等关键技术点。压缩包共1506个文件约515.01MB包含34个cs脚本、34个shader、24个prefab、46个anim动画、18个controller、164个wav与22个mp3音频、153个png贴图及56个fbx模型等完整呈现了从资源到逻辑的工程结构。目前已有835人学习下载。通过该工程读者可参考第一人称控制、怪物行为、攻击判定与音频管理的实现方式并借助动画与预制体组织思路快速理解Unity 3D综合项目的开发流程。1. 狩猎游戏作业从零搭出一个能跑能打的 Unity 3D 原型很多人做 Unity 3D 作业第一反应是去搜“unity 下载”“unity 安装”装完打开引擎就懵了——场景是空的角色不会动猎物不会跑弓箭射出去像扔了个纸片。狩猎游戏这个题目表面看是做个打猎小游戏实际考的是三件事角色控制、AI 行为、命中判定。这三块任何一块塌了作业就变成“在草原上散步模拟器”。我做过不止一个狩猎类原型也帮人改过不少翻车工程。最常见的死法是动物用刚体推着走撞到树就飞天箭用碰撞体检测速度快了直接穿模摄像机跟随写成硬绑定角色一转身镜头就晕。这篇不聊虚的按“能跑能打”的标准把狩猎游戏拆成可复现的步骤。适合正在赶 Unity 3D 作业、想一次把交互闭环做通的人也适合已经写了一半但动物行为像抽风、射击判定像玄学的同学。2. 狩猎游戏的核心交互闭环角色、猎物、射击三件套狩猎游戏和普通跑酷、平台跳跃最大的区别在于玩家不是单纯移动而是在“搜索—接近—瞄准—射击—反馈”这条链上循环。链条上任何一环断了玩家立刻失去目标感。所以动手之前先把这三个模块的职责分清楚。2.1 角色控制用 CharacterController 还是 Rigidbody这是第一个选型分叉。CharacterController 自带胶囊碰撞和斜坡处理移动逻辑写起来干净适合狩猎游戏里玩家需要稳定走位、不被物理弹飞的场景。Rigidbody 方案更自由但你要自己处理摩擦力、重力、斜坡滑动作业周期短的话容易翻车。我一般会这样选方案适用场景代价CharacterController玩家角色、需要稳定移动不参与物理推挤需手动处理碰撞Rigidbody CapsuleCollider需要被动物撞飞、物理互动多参数调不好会滑步、抖动NavMeshAgentAI 猎物、自动寻路不适合玩家直接控制狩猎游戏里玩家角色用 CharacterController猎物用 NavMeshAgent这是最稳的组合。下面是最小可跑的移动脚本using UnityEngine; public class HunterMove : MonoBehaviour { public float walkSpeed 3.5f; public float runSpeed 7f; public float gravity -9.81f; public Transform cameraPivot; private CharacterController controller; private Vector3 velocity; private float currentSpeed; void Start() { controller GetComponentCharacterController(); } void Update() { // 输入水平/垂直轴对应 WASD float h Input.GetAxis(Horizontal); float v Input.GetAxis(Vertical); // 移动方向基于摄像机朝向避免“按 W 往世界前方跑”的割裂感 Vector3 move cameraPivot.forward * v cameraPivot.right * h; move.y 0; move.Normalize(); // 按住左 Shift 跑步否则走路 currentSpeed Input.GetKey(KeyCode.LeftShift) ? runSpeed : walkSpeed; controller.Move(move * currentSpeed * Time.deltaTime); // 重力累积保证贴地 if (controller.isGrounded velocity.y 0) velocity.y -2f; velocity.y gravity * Time.deltaTime; controller.Move(velocity * Time.deltaTime); // 角色朝向跟随移动方向 if (move ! Vector3.zero) transform.rotation Quaternion.Slerp( transform.rotation, Quaternion.LookRotation(move), Time.deltaTime * 10f); } }逻辑说明cameraPivot是摄像机的父物体或朝向参考移动方向基于它而不是世界坐标这样玩家按 W 永远是“往镜头前方走”。controller.isGrounded判断贴地避免重力无限累积。Quaternion.Slerp让转身有过渡不会瞬间扭头。参数说明walkSpeed给 3.5 左右runSpeed给 7 左右差距太小跑步没意义太大容易冲过头。gravity用 -9.81 就行不用改。Time.deltaTime必须乘否则帧率一变速度就乱。2.2 猎物 AINavMeshAgent 巡逻与逃跑的状态切换猎物不能只会站着。狩猎游戏里动物至少要有两个状态巡逻和逃跑。巡逻时沿路径点走玩家进入警戒范围后切换到逃跑跑出一段距离再回到巡逻。用 NavMeshAgent 做这件事比手写寻路省太多时间。先烘焙 NavMesh把地形和障碍物标记为 Navigation Static打开 Window AI Navigation点 Bake。烘焙完成后给猎物挂 NavMeshAgent 和下面这个脚本using UnityEngine; using UnityEngine.AI; public class PreyAI : MonoBehaviour { public Transform[] patrolPoints; public float alertRadius 12f; public float fleeDistance 20f; public float patrolSpeed 1.5f; public float fleeSpeed 5f; private NavMeshAgent agent; private Transform player; private int patrolIndex; private enum State { Patrol, Flee } private State state State.Patrol; void Start() { agent GetComponentNavMeshAgent(); player GameObject.FindGameObjectWithTag(Player).transform; GoToNextPatrolPoint(); } void Update() { float dist Vector3.Distance(transform.position, player.position); switch (state) { case State.Patrol: agent.speed patrolSpeed; if (!agent.pathPending agent.remainingDistance 0.5f) GoToNextPatrolPoint(); if (dist alertRadius) state State.Flee; break; case State.Flee: agent.speed fleeSpeed; // 逃跑方向远离玩家同时尽量保持在地图内 Vector3 dir (transform.position - player.position).normalized; Vector3 target transform.position dir * fleeDistance; agent.SetDestination(target); if (dist alertRadius * 1.5f) state State.Patrol; break; } } void GoToNextPatrolPoint() { if (patrolPoints.Length 0) return; agent.SetDestination(patrolPoints[patrolIndex].position); patrolIndex (patrolIndex 1) % patrolPoints.Length; } }逻辑说明状态机只有两个状态够用且好调。巡逻时用remainingDistance判断是否到达路径点到了就切下一个。玩家距离小于alertRadius时切逃跑逃跑目标是“当前位置 远离玩家方向 × fleeDistance”。距离超过alertRadius * 1.5f时切回巡逻这个 1.5 倍是迟滞区间防止在边界反复横跳。参数说明alertRadius给 12 左右太小动物像瞎太大玩家还没靠近就跑了。fleeSpeed要明显大于玩家走路速度但小于玩家跑步速度否则追不上。patrolSpeed给 1.5 左右看起来像在觅食。2.3 射击判定射线检测比碰撞体更可靠箭矢用 Rigidbody Collider 做物理飞行速度快了必然穿模这是血泪经验。狩猎游戏里射击判定用射线检测Raycast最稳从摄像机中心发一条射线命中什么就处理什么箭的视觉表现另做。using UnityEngine; public class Shooting : MonoBehaviour { public Camera fpsCamera; public float range 100f; public int damage 1; public LayerMask hitLayers; void Update() { if (Input.GetButtonDown(Fire1)) { Shoot(); } } void Shoot() { Ray ray fpsCamera.ScreenPointToRay( new Vector3(Screen.width / 2f, Screen.height / 2f, 0f)); RaycastHit hit; if (Physics.Raycast(ray, out hit, range, hitLayers)) { Debug.Log(命中 hit.collider.name); PreyHealth health hit.collider.GetComponentPreyHealth(); if (health ! null) { health.TakeDamage(damage); } // 命中点生成特效或箭矢模型 // Instantiate(arrowPrefab, hit.point, Quaternion.LookRotation(hit.normal)); } } }逻辑说明射线从屏幕中心发出不是从枪口这样准星和命中点一致。hitLayers用来过滤层只检测猎物和地形不检测草、粒子这些干扰物。命中后取PreyHealth组件扣血没这个组件就只打印日志。参数说明range给 100 够用狩猎游戏不需要狙击千米外。damage根据猎物血量调一般鹿 2 到 3 箭兔子 1 箭。hitLayers在 Inspector 里勾选别用 Default否则会打到自己的碰撞体。3. 把狩猎游戏跑起来场景搭建、输入配置与 UI 反馈上一章把三个核心脚本写完了但直接扔进空场景还是跑不起来。这一章把场景、输入、UI 串成一条能实际操作的流水线。按顺序做别跳步。3.1 地形与 NavMesh 烘焙的四个必调参数新建场景后先建地形GameObject 3D Object Terrain。用笔刷随便刷点起伏不用精细作业重点是交互不是美术。然后放几棵树或石头作为障碍物给它们加 Collider标记为 Navigation Static。打开 Navigation 窗口Bake 之前看这四个参数参数建议值作用Agent Radius0.5猎物能通过的缝隙宽度Agent Height2.0头顶障碍物高度Max Slope45能爬的最大坡度Step Height0.4能跨过的台阶高度Agent Radius 太小猎物会贴着树走看起来像穿模太大窄路走不过去。Max Slope 超过 45 度猎物会爬上陡坡不符合狩猎场景。Bake 完后场景里会出现蓝色网格那就是可行走区域。注意如果烘焙后猎物不动先检查 NavMeshAgent 的Warp是否把角色放到了蓝色区域外。用NavMesh.SamplePosition可以强制吸附到最近可行走点。3.2 输入管理用 Input Manager 还是新 Input SystemUnity 有两套输入系统。老 Input Manager 开箱即用Input.GetAxis直接能跑。新 Input System 更灵活但作业周期短的话配置 Action 会多花时间。我一般作业用老系统够用。如果非要用新 Input System记得在 Package Manager 里装完后Edit Project Settings Player Active Input Handling 改成 Both 或 Input System Package。改完 Unity 会重启别慌。移动输入已经在 HunterMove 里用GetAxis处理了。射击用Input.GetButtonDown(Fire1)默认对应鼠标左键。如果要用手柄在 Input Manager 里加个 Joystick Button 0 映射到 Fire1 就行。3.3 准星、血量与命中反馈三件套 UI没有 UI 的射击游戏像在黑匣子里打靶。最少做三个元素准星、猎物血量、命中提示。准星用一个 Image 放在 Canvas 中心16×16 像素的十字或圆点。猎物血量用 World Space Canvas 挂在猎物头顶或者用 Screen Space 的 Slider 跟随。命中提示用简单的文字或图标闪一下。using UnityEngine; using UnityEngine.UI; public class PreyHealth : MonoBehaviour { public int maxHealth 3; public Image healthBarFill; public GameObject hitEffect; private int currentHealth; void Start() { currentHealth maxHealth; UpdateUI(); } public void TakeDamage(int amount) { currentHealth - amount; UpdateUI(); if (hitEffect ! null) Instantiate(hitEffect, transform.position, Quaternion.identity); if (currentHealth 0) Die(); } void UpdateUI() { if (healthBarFill ! null) healthBarFill.fillAmount (float)currentHealth / maxHealth; } void Die() { // 播放死亡动画或直接销毁 Destroy(gameObject, 0.5f); } }逻辑说明healthBarFill是 Slider 的 Fill 区域 Image用fillAmount控制显示比例。hitEffect在命中点生成粒子给玩家即时反馈。Die里延迟 0.5 秒销毁留时间播动画。参数说明maxHealth鹿给 3兔子给 1。fillAmount是 0 到 1 的浮点数别传整数。Destroy的延迟时间根据动画长度调没动画就给 0。3.4 摄像机跟随第三人称狩猎视角的阻尼设置狩猎游戏常用第三人称越肩视角。摄像机不能硬绑在角色头上否则转身就晕。用LateUpdate做平滑跟随using UnityEngine; public class CameraFollow : MonoBehaviour { public Transform target; public Vector3 offset new Vector3(0.5f, 1.8f, -3.5f); public float smoothTime 0.15f; public float mouseSensitivity 2.5f; private Vector3 velocity Vector3.zero; private float yaw; private float pitch; void Start() { Cursor.lockState CursorLockMode.Locked; } void LateUpdate() { yaw Input.GetAxis(Mouse X) * mouseSensitivity; pitch - Input.GetAxis(Mouse Y) * mouseSensitivity; pitch Mathf.Clamp(pitch, -30f, 60f); Quaternion rotation Quaternion.Euler(pitch, yaw, 0); Vector3 desiredPos target.position rotation * offset; transform.position Vector3.SmoothDamp( transform.position, desiredPos, ref velocity, smoothTime); transform.rotation rotation; } }逻辑说明LateUpdate保证在角色移动之后执行避免摄像机抖动。yaw和pitch分别累积鼠标水平、垂直输入pitch用Clamp限制角度防止翻过头。SmoothDamp做位置平滑smoothTime越大越跟手但越飘。参数说明offset的 z 给负值摄像机在角色后方。smoothTime给 0.15 左右太小会抖太大像开船。mouseSensitivity给 2.5看个人习惯。4. 狩猎游戏作业避坑从动物飞天到箭矢穿模的排查清单这一章全是翻车记录。每个坑我都实际踩过按“现象 → 原因 → 解决”写对着查能省一晚上。4.1 猎物被推着走撞树后飞天现象给猎物加了 Rigidbody玩家一撞动物直接弹到天上。原因NavMeshAgent 和 Rigidbody 同时控制位置物理系统和寻路系统打架。Agent 把角色往目标点推Rigidbody 又按物理规则弹开叠加后就飞了。解决猎物只用 NavMeshAgent不加 Rigidbody。如果必须要有物理碰撞把 Rigidbody 设为 Kinematic勾选Use Gravity关掉让 Agent 全权控制移动。玩家和猎物之间的碰撞用 Trigger 处理别用物理推挤。4.2 射线检测打不中猎物现象准星明明对着鹿开枪没反应。原因猎物的 Collider 不在hitLayers里或者 Collider 是 TriggerPhysics.Raycast默认不检测 Trigger。解决检查猎物的 Layer确保在hitLayers中勾选。如果是 Trigger Collider用Physics.Raycast的重载加QueryTriggerInteraction.Collide。另外确认射线起点是摄像机不是角色胸口否则近距离会打到自己。4.3 NavMesh 烘焙后猎物原地转圈现象Agent 有目标点但角色原地打转不前进。原因目标点不在 NavMesh 上或者 Agent 的stoppingDistance大于路径长度。还有一种情况是SetDestination调用太频繁每帧都设Agent 来不及计算路径。解决用NavMesh.SamplePosition把目标点吸附到最近可行走区域。SetDestination只在状态切换时调用别放Update里每帧调。检查stoppingDistance给 0.5 左右太大就会在远处停下。4.4 摄像机穿墙看到地形背面现象角色靠近墙壁时摄像机穿到墙里画面全是地形背面。原因摄像机没有碰撞检测直接按 offset 计算位置遇到障碍物就穿进去。解决从角色头部向摄像机目标位置发一条射线如果命中障碍物把摄像机拉到命中点前面。代码大概是这样RaycastHit hit; Vector3 dir desiredPos - target.position; if (Physics.SphereCast(target.position, 0.2f, dir.normalized, out hit, dir.magnitude)) { desiredPos hit.point - dir.normalized * 0.2f; }SphereCast比Raycast好因为摄像机有体积用球体检测更自然。0.2f 是摄像机半径根据实际调。4.5 打包后射击没声音、UI 不显示现象编辑器里跑得好好的Build 出来准星没了音效也不响。原因UI 的 Canvas 没设成 Screen Space Overlay或者 AudioSource 的Play On Awake关了但代码里没调Play()。还有一种常见情况是场景没加到 Build Settings 里。解决Canvas 的 Render Mode 设成 Screen Space OverlaySort Order 给 0。音效在Shoot()里显式调audioSource.PlayOneShot(clip)。Build Settings 里把当前场景拖进去别只靠编辑器里打开。5. 让狩猎游戏更像样三个提升手感的具体技巧作业能跑之后差距就在手感上。下面三个技巧是我反复调出来的不复杂但效果明显。5.1 用 Mathf.PerlinNoise 做动物不规则巡逻直线巡逻看起来像机器人。用 Perlin 噪声给路径点加偏移动物走起来就有随机感Vector3 basePos patrolPoints[patrolIndex].position; float noiseX Mathf.PerlinNoise(Time.time * 0.3f, 0f) - 0.5f; float noiseZ Mathf.PerlinNoise(0f, Time.time * 0.3f) - 0.5f; Vector3 offset new Vector3(noiseX, 0, noiseZ) * 3f; agent.SetDestination(basePos offset);Mathf.PerlinNoise返回 0 到 1减 0.5 变成 -0.5 到 0.5乘 3 就是 ±1.5 米的偏移。Time.time * 0.3f控制变化速度太快像抽搐太慢没效果。这个技巧也适合做摄像机轻微晃动但狩猎游戏里慎用容易晕。5.2 命中不同部位给不同伤害打头一箭毙命打腿要追半天这种反馈让狩猎更有层次。在猎物身上挂多个 Collider分别标记 Tag 或 Layer部位Tag伤害倍率头部Head3躯干Body1腿部Leg0.5射击脚本里根据hit.collider.tag乘伤害float multiplier 1f; if (hit.collider.CompareTag(Head)) multiplier 3f; else if (hit.collider.CompareTag(Leg)) multiplier 0.5f; int finalDamage Mathf.RoundToInt(damage * multiplier); health.TakeDamage(finalDamage);Tag 要在 Inspector 里提前建好别用未定义的字符串。CompareTag比快而且 Tag 不存在时会报错方便排查。5.3 用 LayerMask 区分可射击与不可射击物体场景里东西一多射线容易打到草、石头、自己的箭。用 LayerMask 过滤是最干净的做法。在 Project Settings Tags and Layers 里建几个层Prey、Terrain、Obstacle、Player。射击脚本的hitLayers只勾 Prey 和 Terrain。如果运行时需要动态改 LayerMask用位运算int layerMask (1 LayerMask.NameToLayer(Prey)) | (1 LayerMask.NameToLayer(Terrain));1 n是把 1 左移 n 位对应第 n 层的掩码。多个层用|合并。这个写法比在 Inspector 里勾选灵活适合做武器切换或关卡配置。最后说个习惯每次改完参数先在小场景里跑 30 秒看动物会不会卡住、箭会不会穿模、摄像机有没有穿墙。我吃过亏一次改了alertRadius忘了调回来演示时动物隔着半张地图就开始跑全场尴尬。希望帮到你。本文还有配套的精品资源点击获取