简介:基于Unity 3D开发的第一人称狩猎游戏完整项目,面向游戏开发学习者、课程作业或毕业设计参考。玩家扮演角色手持斧头猎杀野猪和僵尸,涵盖角色移动、攻击交互、怪物行为、背景音乐与打击音效等机制,并从模型、场景到逻辑构成完整闭环。压缩包共1506个文件、约515MB,包含806个meta元数据、164个wav音效、153个png贴图、74个mat材质、56个fbx模型、46个anim动画、34个cs脚本、34个shader着色器、24个prefab预制体、22个mp3音乐等;wav与mp3负责音频,png、tga提供贴图,fbx为三维模型,anim与controller驱动动画,cs与shader实现逻辑和渲染效果。已有836人学习,适合作为Unity进阶参考或课程综合项目使用。通过此项目可掌握第一人称控制器配置、场景搭建、动画状态机、音频管理及攻击判定等核心技能,项目目录完整清晰,便于逐模块对照学习与二次开发。
1. unity 3D作业-狩猎游戏:先想清楚“作业要展示什么能力”
如果你接手的是一个“unity 3D作业-狩猎游戏”题目,第一反应往往是从资源商店拖一个第一人称控制器、买几只动物模型、随便摆个地形就开始打枪。这个思路做出来的东西通常有个宿命:答辩时被老师问“动物AI是怎么设计的?”,你只能说“它站着不动让我打”。把狩猎游戏当作业交,真正的考核点不是“能开枪”,而是“猎物会像活物一样动”。所以动手前先想清楚,这版作业要展示的是你对 Unity 三大块的基本功:玩家控制与交互、动物状态机与寻路、任务与UI闭环。本文按这个顺序把一套能跑的狩猎游戏框架拆给你,附带我实际调参和翻车时攒下的避坑点。适合要把 Unity 3D 课程作业做成“能演示、能答辩、能扩展”的人。
2. 用 CharacterController 跑通玩家控制与第一人称射击
2.1 玩家控制:CharacterController 的移动与重力参数
在 Unity 里做第一人称玩家控制,常见做法是 CharacterController 和 Rigidbody 二选一。作业场景里我强烈建议用 CharacterController,因为它是运动学驱动,你不会被“刚体撞到桌角导致视角乱晃”这种玄学问题缠住;也适合课程设计里“从 A 点到 B 点触发任务”这种确定性玩法。Rigidbody 本身没有移动和跳跃的封装,写物理控制代码很容易把速度和加速度混成一团乱麻。
先建一个空物体,挂上 CharacterController 组件,再把 Camera 作为它的子物体并归零位置。以下是玩家移动和视角旋转的最小实现:
using UnityEngine; public class MinimalFPSController : MonoBehaviour { public float moveSpeed = 4.0f; // 普通移动速度,米/秒 public float sprintMultiplier = 1.6f; // 跑步倍率 public float jumpHeight = 1.2f; // 跳跃高度 public float mouseSensitivity = 2.0f; // 鼠标灵敏度 public Camera playerCamera; // 第一人称相机 private CharacterController controller; private float verticalVelocity = 0f; // 垂直方向速度累加 private float pitch = 0f; // 俯仰角累计值 void Start() { controller = GetComponent<CharacterController>(); // 锁鼠标,FPS 标准行为 Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; } void Update() { // 鼠标水平旋转控制身体 float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity; transform.Rotate(0f, mouseX, 0f); // 鼠标垂直旋转只控制相机,避免整个身体跟着低头造成模型倾斜 pitch -= Input.GetAxis("Mouse Y") * mouseSensitivity; pitch = Mathf.Clamp(pitch, -80f, 80f); playerCamera.transform.localRotation = Quaternion.Euler(pitch, 0f, 0f); // WASD 方向,注意 transform.right 是水平向右,transform.forward 是水平向前 float x = Input.GetAxis("Horizontal"); float z = Input.GetAxis("Vertical"); float speed = Input.GetKey(KeyCode.LeftShift) ? moveSpeed * sprintMultiplier : moveSpeed; Vector3 move = (transform.right * x + transform.forward * z) * speed; // 跳跃逻辑:只有在 grounded 时才允许触发 if (controller.isGrounded) { if (verticalVelocity < 0) verticalVelocity = -2f; // 轻轻压地板,避免悬空抖动 if (Input.GetButtonDown("Jump")) verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * Physics.gravity.y); } else { verticalVelocity += Physics.gravity.y * Time.deltaTime; // 自由落体 } move.y = verticalVelocity; controller.Move(move * Time.deltaTime); } }注意move乘了Time.deltaTime,但verticalVelocity没有,因为重力加速度本身已经按帧累加了。CharacterController 的Move方法每帧都会做碰撞检测,所以不用担心走到斜坡或台阶上被卡住。但要想站稳斜坡,记得在 Inspector 里把 Slope Limit 设为 45 度左右,Step Offset 设为 0.3 米。这两个参数是体力活,不调好就会遇到上坡像溜冰、下坡像跳崖的尴尬。
另外,作业里经常要求“让物体不随镜头放大缩小”,干这类需求的常见做法:把准星做成屏幕空间 UI,而不是场景里的 3D 物体。在 Canvas 下用 Image 组件,锚点放在中心即可。不要尝试把准星贴在枪口模型上,否则 FOV 一变就露馅。
2.2 武器挂点与射击判定:Raycast 从相机出发的边界
武器挂点建议直接用 Camera 的子物体,你在 Scene 视图里调整枪模在本地的位置和旋转,让它位于相机底部偏右,这是 FPS 游戏的标准做法。用代码动态挂载也行,但作业里手动拖拽更直观。
射击判定有一个经典分歧:射线从相机发射还是从枪口发射。我的建议是射线必须从相机中心发射,因为玩家看到的准星就是判定的基准。如果从枪口发射,射线击中点和准星中心会有横向偏差,近距离还好,打远距离目标时感觉像是在“描边”。
using UnityEngine; public class HunterShooting : MonoBehaviour { public Camera playerCamera; public GameObject hitParticle; // 命中特效预制体 public float maxRange = 100f; // 射击距离,狩猎场景拉满到 100 米 public int damagePerShot = 25; // 单发伤害 void Update() { if (Input.GetButtonDown("Fire1")) { Shoot(); } } void Shoot() { // 从相机屏幕中心向外发射射线 Ray ray = playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f)); if (Physics.Raycast(ray, out RaycastHit hit, maxRange)) { // 命中反馈:在命中点生成特效,1.5 秒后自动销毁 if (hitParticle != null) { GameObject impact = Instantiate(hitParticle, hit.point, Quaternion.LookRotation(hit.normal)); Destroy(impact, 1.5f); } // 判断击中动物,交给动物身上的脚本处理受伤 AnimalHealth animalHealth = hit.collider.GetComponentInParent<AnimalHealth>(); if (animalHealth != null) { animalHealth.TakeDamage(damagePerShot); } } // 后坐力:用代码把相机临时抬起 0.1 角度,会随 recoilRecover 恢复 StartCoroutine(Recoil()); } System.Collections.IEnumerator Recoil() { float elapsed = 0f; float duration = 0.1f; // 这里简化为直接修改 localPosition,实际做手感可以再加随机偏移 Vector3 originalPos = playerCamera.transform.localPosition; playerCamera.transform.localPosition = originalPos + new Vector3(0, -0.02f, 0); while (elapsed < duration) { playerCamera.transform.localPosition = Vector3.Lerp( originalPos + new Vector3(0, -0.02f, 0), originalPos, elapsed / duration); elapsed += Time.deltaTime; yield return null; } playerCamera.transform.localPosition = originalPos; } }这段代码的边界点在于GetComponentInParent<AnimalHealth>()。很多动物模型是多个 Collider 挂在子物体上,如果你直接GetComponent<AnimalHealth>(),子弹打到腿骨上就判定不了伤害,用InParent或者让 Collider 和主逻辑挂在同一个父物体上,是 GameObject 层级摆放的经验。
2.3 让准星有些反馈:命中提示与简单后坐力
作业里想要快速提升“手感”,三个字:做反馈。第一层是命中特效,第二层是动物血条,第三层是屏幕震动。命中特效用粒子系统做一个爆血点,把hitParticle做成预制体;血条用世界空间 Canvas;屏幕震动最简单的方法是让相机的位移、俯仰角加一点随机量,参考上面的 Recoil 代码结构,把位移改成随机方向即可。
别小看而这些,答辩时老师问“你这游戏的打击感怎么做出来的”,你能把“射线检测命中点、粒子朝向法线、相机随动反馈”讲清楚,比装一堆后处理插件更能体现基本功。
3. 猎物 AI:用状态机让动物“会跑、会躲、会回头”
3.1 动物状态机:待机、巡逻、逃跑的代码骨架
狩猎作业能否拿高分,一半看动物 AI。最容易实现又最能演示的系统是有限状态机(FSM)。我不建议用行为树插件,因为课程设计要展示你理解状态转移的编程能力,手写一个枚举状态机是最合适的分寸。
动物需要三种核心状态:Idle(待机)、Patrol(巡逻)、Flee(逃跑)。用一个枚举字段控制当前状态,再写一个统一的更新入口,每 0.2 秒判定一次状态转移,避免每帧都去做距离检测和随机点生成。
using UnityEngine; using UnityEngine.AI; public enum AnimalState { Idle, Patrol, Flee, Dead } public class AnimalAI : MonoBehaviour { public AnimalState state; public float walkSpeed = 2.0f; public float runSpeed = 8.0f; public float viewRadius = 15.0f; // 感知半径,单位米 public float viewAngle = 60.0f; // 视觉锥夹角 public float hearRadius = 50.0f; // 听到枪声的最大半径 public float patrolRadius = 20.0f; // 巡逻范围半径 private NavMeshAgent agent; private Vector3 homePosition; // 出生点,巡逻围绕此处 private float stateTimer = 0f; void Start() { agent = GetComponent<NavMeshAgent>(); homePosition = transform.position; state = AnimalState.Patrol; InvokeRepeating(nameof(UpdateState), 0f, 0.2f); } void UpdateState() { if (state == AnimalState.Dead) return; // 如果正在逃跑,检测是否已远离危险,足够安全就恢复巡逻 if (state == AnimalState.Flee) { if (Vector3.Distance(transform.position, homePosition) > 30f) } } }上面代码我先留了个半成品,方便讲下面的细节。因为逃跑时“目标点”应该是朝爆炸点反方向走,而不是回到出生点。如果直接让动物往固定路径跑,容易被地形挡住。所以逃跑状态里每一步都更新一次逃跑目标,朝向是从威胁源的反方向延伸 20 米。
3.2 感知系统:视觉锥与听觉半径的数值
动物“看到”玩家有两个触发条件:距离小于viewRadius,并且玩家位于动物 forward 方向的viewAngle锥形范围内。这一步最实用的调试办法是写一个OnDrawGizmosSelected方法,在 Scene 视图里把视觉锥画出来,调参时不用开着 Game 窗口反复试。
听觉相对简单,枪声是一个球形半径事件。你可以在 HunterShooting 的Shoot()方法里,用Physics.OverlapSphere把周围的动物拉出来,对它们调用一个HearGunshot(Vector3 source)接口。这样动物听到枪声后会朝反方向跑,而不是永远依赖视觉。这也是狩猎游戏里“打草惊蛇”机制的雏形。
3.3 动物移动:用 NavMeshAgent 还是直接 Translate
狩猎场景里的动物是在复杂地形上移动的,直接用 Transform.Translate 会撞到树和石头,看起来非常蠢。所以动物移动请老老实实用 NavMeshAgent。它有现成的寻路、避障和速度计算。从 NavMeshAgent 获取当前移动速度也很简单:agent.velocity.magnitude。这正好对应“unity物体速度怎么获取”这个常见搜索词,在动画状态切换时特别有用,比如根据速度切换走路和奔跑动画,用animator.SetFloat("Speed", agent.velocity.magnitude)。
NavMeshAgent 有几个必调参数。Acceleration调大一点,比如 8,动物起步才利落;Angular Speed调到 540 以上,转视角才不拖沓;Stopping Distance设 0.1 米,否则目标点还没到就模型原地踏步。但要注意Radius字段,它控制避障时的胶囊体大小,调太大会让两只动物互相躲闪过度,显得很慌乱。
4. 狩猎闭环:猎物体检、任务计分与 UI 反馈
4.1 猎物体检与掉落物:用 Collider 还是 Trigger
打到猎物后,玩家走过去“体检”并拾取战利品。体检通常用 Trigger 提示E键交互,不要用 Collider,因为 Collider 的OnCollisionEnter会发生在物理碰撞那一帧,不适合给玩家“在附近”的延迟选择空间。正确做法:在动物尸体上挂一个 Box Collider,勾选 Is Trigger,然后在更新逻辑里检测 Collider 范围内是否有玩家输入。
using UnityEngine; public class LootInteraction : MonoBehaviour { public string prompt = "按 E 采集猎物"; public GameObject collectVfx; public float interactRadius = 2.5f; public HuntMission missionTarget; // 关联任务 private bool playerInRange = false; void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) { playerInRange = true; // 这里触发 UI 提示显示 } } void OnTriggerExit(Collider other) { if (other.CompareTag("Player")) { playerInRange = false; // 隐藏 UI 提示 } } void Update() { if (playerInRange && Input.GetKeyDown(KeyCode.E)) { // 生成战利品掉落物 if (collectVfx != null) Instantiate(collectVfx, transform.position, Quaternion.identity); // 计数给任务管理器 GameMissionManager.Instance.OnTargetCollected(missionTarget); Destroy(gameObject); } } }注意掉落物不建议直接把整只动物模型保留,那是内存浪费,也是答辩时容易翻车的地方。常见做法是尸体在原地播放一个 2 秒的淡出动画后销毁,生成一个小的“战利品球”或者直接用 UI 计数。淡出可以用CanvasGroup或MeshRenderer.material改透明度,但最简单的是用Destroy(gameObject, 2f)配合已经做好的溶解 Shader 效果,这在作业里已经足够亮眼。
4.2 任务与计分:用 ScriptableObject 管理关卡配置
计分和任务系统不要写死数字到代码里,建议用 ScriptableObject 定义一个任务配置。这样你可以在 Project 窗口创建好几个不同的任务资产,分别设置目标动物、数量、分数,不用改代码。
using UnityEngine; [CreateAssetMenu(fileName = "HuntMission", menuName = "Hunt/Mission")] public class HuntMission : ScriptableObject { public string missionId; public string targetAnimalTag; // 用 Tag 区分野猪、鹿等 public int targetCount; public int scorePerTarget; }然后是任务管理器,用单例模式持有当前任务进度。单例在课程设计里是加分点,但要注意在场景切换时手动重置,否则容易留下脏数据。
using UnityEngine; public class GameMissionManager : MonoBehaviour { public static GameMissionManager Instance { get; private set; } public HuntMission currentMission; private int collectedCount = 0; private int totalScore = 0; void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); } public void OnTargetCollected(HuntMission mission) { if (mission == currentMission) { collectedCount++; totalScore += mission.scorePerTarget; // 刷新 UI 计分文本 } } public void ResetMission() { collectedCount = 0; totalScore = 0; } }4.3 UI 反馈:血条跟随与命中飘字
狩猎游戏里最常用到的是角色血条和伤害数字。角色血条建议用世界空间 Canvas,挂在动物头顶。但直接作为 Canvas 子物体有个坑:Canvas 的缩放比例会随着物体与相机距离变化,你需要给 Canvas 设置固定的世界空间大小,然后在 Update 里让血条始终朝向相机,即transform.rotation = Camera.main.transform.rotation。如果不写这一行,血条会横躺在地上,这就是很多新手作业里“血条是地板贴图”的原因。
伤害飘字更讲究,用 Screen Space Canvas,而不是世界空间。做法是在命中点用Camera.WorldToScreenPoint把世界坐标转屏幕坐标,再放到 Canvas 下的 Text 组件位置。最后用 DOTween 或手写协程把它往上移动 0.5 米并淡出。这里千万注意不要每帧生成新的 GameObject,飘字对象用对象池,否则猎物多的时候 GC 压力很大,那就是另一层性能坑了。
5. 狩猎游戏避坑指南:5个让我翻车的细节
5.1 现象:动物“瞬移”卡墙,或被树和石头顶在中间不动
原因:NavMeshAgent 的避障(RVO)和场景里的静态障碍物冲突。如果树用 Tree 组件随机生成,它不会自动参与 NavMesh 烘焙,动物会尝试从树中间走过去。RVO 只会避开其他 Agent,不会避开静态碰撞体,所以出现穿模。
解决:不要依赖运行时避障。把地形上的树和石头统一处理好:固定位置的手工摆放物体,在 Navigation 窗口里的 Baking 面板勾选静态,重新烘焙;用 Terrain 刷的树,请在 NavMesh Surface 组件上开启Collect Objects为 All 的选项,或把 Terrain 的树木烘焙成障碍物数据。另外把 NavMeshAgent 的Avoidance Priority调低,让动物互相让路而不是挤成一团。
5.2 现象:准星明明对着野猪,但子弹没伤害或命中特效飘在空气中
原因:这是射线起点和子弹速度的坑。我一个血泪经验是,Raycast 默认只在当前帧做瞬时检测,如果你的子弹飞行速度写成了传值计算但不发射真实射线,视觉上子弹打过去了判定却跟丢了。第二个常见问题是动物模型用的是 Mesh Collider,但地形用了 Terrain Collider,射线打在 Collider 边缘正好是两片三角面中间。
解决:射击射线用Physics.RaycastAll,并去检测多个碰撞体,取距离最近且可受击的那个。同时给动物身体主要部位(头、胸)添加简易的 Box Collider,不要只靠蒙皮 Mesh Collider,那在复杂模型上是又慢又容易出错。
5.3 现象:草丛一多,FPS 直接从 60 掉到 30,且 Profiler 显示 SetPass calls 爆炸
原因:草丛和树木作为独立 GameObject,每棵都是一个 Draw Call,而且没有合并材质。这是“粒子特效内存泄露unity”那一类性能问题在场景美术上的对应版,本质都是资源过度独立。
解决:地皮草地彻底开 GPU Instancing,草叶用 TerrainDetail,不刷具体 Mesh。如果非要建模草,请把它们制作成同一张 Atlas 图集,并使用 Shader 开启 Instancing。树用 LOD Group 加上 Cull Instance 选项。集成Unity 6 GPU Skins这种比较新的特性还需要谨慎,如果你的 Unity 版本不是 LTS 且显卡驱动较老,打开 GPU Skinning 反而闪退,所以在作业里不要在答辩前临时开这个。
5.4 现象:用 Time.timeScale 暂停游戏后,动物动画和射击射线错位
原因:Time.timeScale = 0会暂停所有 Time.deltaTime 驱动的东西,但 Input 事件和 LateUpdate 依然会跑。Animator 在 timeScale=0 时继续播放,导致模型动画和逻辑状态脱节,看起来像植物人还在抽搐。
解决:作业里暂停游戏不要直接改 timeScale,推荐用一个GameState开关。在 Update 开头判断枚举是否为 Pause,如果是就直接 return。这样物理、动画停得干净,UI 还能正常响应。如果有“子弹时间”需求再局部用localTimeScale去控制一组 Animator。
5.5 现象:存档后重新加载,动物位置全乱或任务进度丢失
原因:把动物的 homePosition 和活动状态写在了普通内存里,没有做序列化。也常见的做法是用了 DontDestroyOnLoad 单例,但忘了在重新开始时调用重置。
解决:给动物写一个SaveState数据结构,记录当前坐标、剩余血量、状态。按作业水平,用 JSON 在 PlayerPrefs 或本地文件存储即可,不需要引 SQLite。注意脚本热重载时颜色序列化是 Unity 的强项,但字典不序列化,所以别在动物 AI 里用 Dictionary 存状态,用数组或 List 更稳妥。
6. 验证与进阶:让狩猎作业从“能跑”到“能答辩”
把上述功能拼起来,你已经有一套带玩家控制、动物 AI、任务计数的可玩原型了。但要交出去答辩,还得过“能有效演示”这一关。我的经验是:花两天时间做功能,留一天专门调性能和体验。
第一,用 Profiler 盯住三个关键指标:SetPass calls、Draw Calls 和脚本耗时。狩猎场景最容易出问题的是远距离视野。把动物的可视距离viewRadius控制在 20 米内,视野外直接 LOD 到低模,不然 10 只动物加地形草就把带宽吃满了。
第二,让参数全暴露在 Inspector 里。动物 AI 的viewRadius、runSpeed,武器的damagePerShot、maxRange,任务表的targetCount,这些在演示时可以直接拖拽调。不要写死在代码常量里,这是评审眼里“工程能力”最直观的体现。我一般会把所有可调参数按角色分到组件上,并在Reset()方法里写默认值,这样误改了也能一键恢复。
第三,验证玩法闭环有没有“负反馈”。演示时最怕的是玩家站在出生点就能把远处动物射死,导致 AI 完全没机会表现。这时把maxRange调小到 40 米,或者给动物加一个“听到枪声立即逃跑”的逻辑,反而更能展示你做了完整的感知系统。这也是狩猎游戏区别于打靶游戏的核心设计点。
最后一个实用技巧:动画系统里把动物的Animator速度绑定到agent.velocity.magnitude,用animator.SetFloat("MoveSpeed", agent.velocity.magnitude)。这能让动物走路、奔跑、静止切换得自然,也是“unity物体速度怎么获取”搜索背后的真实需求。答辩时老师看到动物在坡道上顺畅上下且不做滑步,就知道你处理了速度与动画的同步,而不是只贴了张贴图模型。
做狩猎作业最大的价值是逼你把普通 CRUD 式开发思路换成系统思维:玩家、动物、任务、UI 四条线交织在一起。我曾因为偷懒把计分逻辑写死在动物上,结果加第二个任务时重写了一半代码,这算是血泪经验了。现在做这类题,我第一步永远是画状态流转图,哪怕用纸笔,也好过写完再拆功能。希望这篇笔记能帮你把作业做出“超出课时要求”的效果,祝你好运。
本文还有配套的精品资源,点击获取