Unity期末作业FPS游戏完整项目制作与报告撰写指南

📅 2026/8/27 22:09:23
Unity期末作业FPS游戏完整项目制作与报告撰写指南
简介Unity作为主流的3D游戏开发引擎被广泛用于交互应用与课程设计。第一人称射击游戏FPS是经典的游戏类型其核心机制涉及角色控制、视角旋转与碰撞检测。借助CharacterController组件可稳定实现角色移动而射线检测则能精准模拟子弹命中逻辑。此外NavMesh寻路让敌人具备逼近玩家的智能行为结合状态机设计形成完整的战斗循环。在工程实践中合理规划脚本目录、使用预制体与事件驱动可显著提升开发效率。基于这些通用技术完成一个可演示的Unity FPS项目并配套结构清晰的设计报告是期末课程设计的高性价比选择。本文围绕项目拆解、射击手感、敌人AI与报告撰写提供完整参考。 最近有不少同学在问Unity期末作业怎么做尤其是“第一人称射击游戏”这个题目几乎年年都有人选。我当初也做过一个完整项目从场景搭建到射击手感调优再到写设计报告前前后后折腾了小一个月。这篇就把整个项目的完整思路、源码结构和设计报告的写法都梳理一遍给正在做类似东西的人一个参考。先说清楚这东西是什么一个用Unity 3D做的第一人称射击游戏包含可运行的源码、完整场景、敌人AI、UI界面和一份设计报告目标是满足期末课程设计的要求同时让项目本身有可玩性。做这个项目需要的核心能力包括Unity场景搭建、C#脚本编写、射线检测、角色控制器、动画状态机、UI交互以及最基本的游戏循环设计。如果你正愁期末作业不知道做什么或者已经选了FPS题材但没头绪这篇文章可以直接当“抄作业”的模板。我会把项目分解成几个核心模块每一块都讲清楚为什么这么设计、代码怎么组织、踩过哪些坑最后再说设计报告怎么写得让老师觉得“这学生是真的做了东西”。1. 项目选题与整体设计思路1.1 为什么期末作业选FPS是最稳的选择期末课程设计有个很现实的矛盾老师要求“有完整游戏体验”但留给你做的时间只有两三周。选FPS这类的第一人称射击游戏其实是最讨巧的选择之一。第一它的核心机制非常明确移动、瞄准、射击、敌人反馈、计分这些都是最基础的游戏系统Unity官方文档和社区教程覆盖度高遇到问题基本都能搜到答案。第二它的视觉效果天然有冲击力3D场景加第一人称视角演示时比2D贪吃蛇、扫雷这类项目有面子得多。第三技术栈覆盖广从场景搭建到射线检测到UI绑定正好覆盖了Unity课程的主要内容老师想扣分都找不到明显短板。我见过不少同学选“MMORPG”或“开放世界”这种题目最后做出来就是一个角色在空场景里走来走去交互几乎没有反而得不偿失。期末作业的核心是“完整且能演示”不是“大而全”FPS在完整性和工作量之间找到了一个极好的平衡点。1.2 项目功能模块的拆解与优先级拿到题目之后第一件事不是打开Unity开始拖模型而是先在白纸上把功能模块列出来区分“必须有”和“可以没有”。我的划分是这么做的必须有这就是项目的骨架第一人称角色控制器移动、跳跃、视角旋转射击功能射线检测、子弹效果、枪口动画敌人系统生成、移动、攻击、死亡玩家生命值与受伤反馈计分/UI界面准星、血量条、分数显示、开始结束界面场景搭建有地面、障碍物、灯光至少看起来像个游戏加分项有时间再添加声音特效枪声、脚步声、背景音乐敌人AI改进巡逻、追击、攻击三种状态切换弹夹系统换弹动画、弹药数量显示小地图或击杀提示场景道具血包、弹药补给这个优先级排序非常关键。很多同学一开始就沉迷做关卡设计或者找高模素材最后核心玩法没做完提交前熬夜通宵。我建议先把“必须有”的清单全部跑通再回头加加分项。我当时的顺序是角色能走能跳 → 准星能打中物体 → 敌人出现并会死 → 玩家会受伤 → UI接通 → 加声音和细节。每一步都有可实测的成果不会到后面才发现大问题。1.3 用什么版本和渲染管线Unity版本选型上我强烈建议用Unity 2021 LTS或2022 LTS这类长期支持版。别用最新版期末周你不想被突然冒出的渲染管线迁移问题拖住时间。我当时用的是Unity 2021.3 LTS配套的内置渲染管线Built-in Render Pipeline虽然画面不如URP通用渲染管线精致但胜在稳定网上大部分教程和代码都基于内置管线遇到问题能直接抄答案。用内置渲染管线还有个好处对电脑配置要求低。期末机房那批机器大多显卡一般URP项目跑起来容易卡成PPT演示时观感会很差。项目能流畅运行本身就是拿分的一部分。2. 项目目录结构与源码模块划分2.1 从空场景开始的目录组织拿到Unity后先别急着拖方块把项目结构搭好再动手后面会舒服很多。我一般会按功能和资源类型双维度组织目录Assets/ ├── Scenes/ # 场景文件主场景、测试场景分开 ├── Scripts/ # C# 脚本 │ ├── Player/ # 玩家控制相关 │ ├── Enemy/ # 敌人AI相关 │ ├── Weapon/ # 武器系统相关 │ ├── UI/ # UI管理 │ └── GameManager/ # 游戏流程控制 ├── Prefabs/ # 预制体子弹、敌人、血包等 ├── Materials/ # 材质 ├── Textures/ # 贴图 ├── Audio/ # 音效和背景音乐 ├── Animations/ # 动画状态机相关 └── Models/ # 模型文件这套结构的核心逻辑是“脚本按功能分资源按类型分”。看起来是小事但期末作业提交时老师是要看“代码组织能力”的一个目录清爽的项目给老师的第一印象就是加分项。而且开发过程中经常要找一个脚本目录清晰能省大量时间。2.2 核心脚本的功能划分整个项目的脚本我拆成了十几个文件每个文件只干一件事。这里重点说下核心的几个PlayerController.cs负责角色移动和视角旋转复用Unity的CharacterController组件而非Rigidbody避免物理系统带来的莫名抖动问题。Weapon.cs管理射击、射速、准星扩散和子弹发射。EnemyAI.cs控制敌人AI的状态巡逻、追击、攻击通过距离判断切换状态。Health.cs所有有血量的对象共用玩家、敌人支持扣血、死亡事件。GameManager.cs控制游戏全局状态开始、进行中、结束、计分和UI更新。这种单一职责的设计不只是为了好看更重要的是排查问题时效率高。比如玩家不该掉血但一直掉血直接去查Health.cs就行不需要把整个项目翻一遍。期末答辩时老师问起来你也能清楚地说出每个脚本的作用这本身就是一种加分。2.3 预制体设计敌人的“复制粘贴”技巧敌人系统我强烈建议做成预制体Prefab而不是在场景里一个个摆GameObject。预制体的意思是做一个敌人的模板后续批量生成、统一修改都方便。比如我先做一个敌人的基本模型挂上EnemyAI.cs和Health.cs拖到Project窗口变成预制体之后在代码里通过Instantiate方法动态生成敌人即可。预制体带来的好处特别直观假设做了10个敌人突然想给敌人统一加一个“受击变红”的效果只需要修改预制体一次所有生成出来的敌人都跟着变。如果不做预制体你需要手动改10个场景里的敌人期末这种时间紧张的情况下一个小时就搭进去了。敌人生成点的管理也用空物体Empty Object加坐标的方式做简单直接不搞花活。3. 第一人称控制与射击手感的关键实现3.1 角色移动为什么用CharacterController而不是Rigidbody第一人称FPS的角色控制我在新手上路阶段踩过一个大坑用Rigidbody去控制角色移动结果角色经常被场景碰撞体卡住或者掉到地板下面手感非常难受。后来改用CharacterController组件所有问题一次性解决。CharacterController是Unity专门为“人形角色”设计的控制器它封装了碰撞检测和移动逻辑不依赖物理系统。简单理解它就是你直接在场景里推着一个小方块走路穿过障碍物时会自动被挡住不会出现穿透或弹飞的问题。代码也很简单public class PlayerController : MonoBehaviour { public float moveSpeed 6f; public float jumpHeight 1.2f; private CharacterController controller; private Vector3 velocity; void Start() { controller GetComponentCharacterController(); } void Update() { float x Input.GetAxis(Horizontal); float z Input.GetAxis(Vertical); Vector3 move transform.right * x transform.forward * z; controller.Move(move * moveSpeed * Time.deltaTime); } }这里有个新手常犯的错直接把move向量乘速度再赋值给controller.Move()忘了乘以Time.deltaTime。这个Time.deltaTime叫“帧间隔时间”不乘的话你的角色移动速度会随帧率变化——高配电脑上人飞出去低配电脑上原地踏步。Move()方法传入的是“本帧要移动的距离”所以必须用速度乘以时间转换成距离后传进去这是个底层设定别搞反。3.2 视角控制鼠标旋转的数学原理FPS的灵魂在第一人称视角鼠标移动旋转视角这功能看起来简单但要做到“顺滑不晕”需要处理好几个细节。视角旋转本质上是给摄像机挂在角色头顶叠加旋转角度水平方向绕世界Y轴转垂直方向绕摄像机自身的X轴转。代码结构我的做法是public class MouseLook : MonoBehaviour { public float mouseSensitivity 100f; public Transform playerBody; private float xRotation 0f; void Start() { Cursor.lockState CursorLockMode.Locked; // 锁定光标到屏幕中心 } void Update() { float mouseX Input.GetAxis(Mouse X) * mouseSensitivity * Time.deltaTime; float mouseY Input.GetAxis(Mouse Y) * mouseSensitivity * Time.deltaTime; xRotation - mouseY; xRotation Mathf.Clamp(xRotation, -80f, 80f); // 限制上下角度防止人物翻跟头 transform.localRotation Quaternion.Euler(xRotation, 0f, 0f); playerBody.Rotate(Vector3.up * mouseX); } }注意几个关键点上下旋转用的是localRotation只影响摄像机自身左右旋转用的是playerBody.Rotate控制整个人物转向。这样设计是为了确保角色前进方向永远和视线方向一致W键永远是“向前走”否则你枪口朝东但按W往北跑游戏逻辑就乱了。Mathf.Clamp(xRotation, -80f, 80f)也很重要不限制的话鼠标一直往上拉角色会仰头翻过来场景视角完全乱掉。还有个细节是Cursor.lockState。游戏开始后要把鼠标光标锁在屏幕中央不然鼠标移到屏幕边缘就会停住视角转到一半无法继续转。运行时按Esc键可以解锁这个在答辩演示时尤其有用方便你展示代码或切换窗口。3.3 射击判定用射线而非物理子弹很多人第一次做射击会想着生成一个子弹的Rigidbody往前飞碰到敌人再扣血。这个方案在FPS里效果并不好子弹飞行需要时间手感发“飘”而且物理计算开销大。行业内主流的FPS方案是“射线检测”也就是开枪瞬间从摄像机发射一条无限长的射线如果射线碰到了物体就认为命中了。核心代码思路如下public class Weapon : MonoBehaviour { public Camera playerCamera; public float range 100f; public float fireRate 0.15f; public ParticleSystem muzzleFlash; private float nextFireTime 0f; void Update() { if (Input.GetButtonDown(Fire1) Time.time nextFireTime) { nextFireTime Time.time fireRate; Shoot(); } } void Shoot() { muzzleFlash.Play(); Ray ray new Ray(playerCamera.transform.position, playerCamera.transform.forward); RaycastHit hit; if (Physics.Raycast(ray, out hit, range)) { Health targetHealth hit.collider.GetComponentHealth(); if (targetHealth ! null) { targetHealth.TakeDamage(20f); } // 在命中点生成一个子弹孔特效 Instantiate(bulletImpactPrefab, hit.point, Quaternion.LookRotation(hit.normal)); } } }这里有一个必须注意的点射线是从playerCamera发出的而不是从枪口模型发出。原因是摄像机代表玩家的视线如果从枪口发射射线准星瞄准敌人在屏幕上显示的是击中状态射线却打在旁边的墙上体验差距巨大。有些追求枪口特效一致性的项目会做“两条线”处理——枪口有拖尾特效穿过去但实际伤害判定仍用摄像机射线效果更佳。3.4 后坐力与准星扩散怎么做出“手感”FPS射击手感是一个很玄学的东西但拆解下来无非就是几个变量射速、后坐力、准星扩散、命中反馈。期末项目不需要做得多专业但加上简单的后坐力系统会让游戏质感立刻提升一个档次。我做的后坐力方案很简单每次开枪后让摄像机向上抬一点角度模拟枪口上跳同时准星扩散值增大过一段时间再恢复。这样玩家需要手动压低枪口就有了“压枪”的操作感。准星的UI图标是四根细线通过脚本改变它们与屏幕中心的距离就能实现动态扩散效果。void ApplyRecoil() { float recoilAmount 2f; xRotation - recoilAmount; // 复用MouseLook里的xRotation // 在SpreadController里增加准星扩散值 currentSpread 0.03f; currentSpread Mathf.Clamp(currentSpread, 0f, 0.2f); }这个后坐力数值不需要非常精确核心逻辑是先“跳一下”再“慢慢恢复”。数值太小玩家没感觉数值太大又控制不住。我的建议是先把基础射击调通再把后坐力数值加进去每次调整间隔在0.5度到2度之间试玩两分钟就能找到合适的数值。此外别忘了给敌人加受击反馈敌人被打中时身体变红一下或者闪一下白。没有这个反馈玩家会怀疑自己到底打中没有这是游戏体验的底线。4. 敌人AI与战斗循环的完整实现4.1 敌人AI的三种状态巡逻、追击、攻击一个稍微像样的FPS敌人不能只会站在原地挨打至少要能“发现玩家—冲向玩家—攻击玩家”。我用一个简单的状态机实现用枚举enum定义三种状态Update里根据条件切换状态public enum EnemyState { Patrolling, Chasing, Attacking }巡逻状态敌人在指定几个巡逻点之间来回移动视野范围用距离和角度判断没发现玩家时就一直散步。追击状态发现玩家距离小于一定值且玩家在敌人前方一定角度内转向玩家并快速靠近。攻击状态距离玩家足够近比如小于3米停下来播放攻击动画并给玩家造成伤害。状态切换的核心代码可以简化为void Update() { float distance Vector3.Distance(transform.position, player.position); switch (currentState) { case EnemyState.Patrolling: Patrol(); if (distance detectRange) currentState EnemyState.Chasing; break; case EnemyState.Chasing: ChasePlayer(); if (distance attackRange) currentState EnemyState.Attacking; else if (distance detectRange) currentState EnemyState.Patrolling; break; case EnemyState.Attacking: transform.LookAt(player.position); if (distance attackRange) currentState EnemyState.Chasing; break; } }这里需要注意LookAt的使用。敌人看向玩家时要保持自身直立不能像低头看地板一样整个人贴过去。我会把LookAt的目标Y轴固定为敌人自身的高度或者在LookAt之后手动重置rotation的x和z分量否则敌人会在斜坡或起伏地形上做出诡异的歪头动作。4.2 寻路方案直接走还是用NavMesh敌人移动路径的选择上我一开始图省事直接用transform.LookAt(player)加transform.Translate(Vector3.forward)让敌人直线走向玩家。结果测试时发现只要玩家站在箱子后面敌人就对着箱子撞完全没有绕路能力。后来改用Unity的导航网格NavMesh系统效果好了很多。NavMesh理解起来很直观预先烘焙场景中哪些地面是可以走的、哪些是障碍物不可走敌人就像跟着手机导航一样在地面上找一条可达路径走向玩家。使用步骤如下给地场景地面和障碍物标记Navigation Static。打开Window → AI → Navigation面板点击Bake生成导航网格。敌人身上挂NavMeshAgent组件在代码里设置agent.SetDestination(player.position)即可。完整状态下敌人代码大概长这样public class EnemyAI : MonoBehaviour { public Transform player; public float chaseSpeed 4f; private UnityEngine.AI.NavMeshAgent agent; private Animator animator; void Awake() { agent GetComponentUnityEngine.AI.NavMeshAgent(); } void Update() { // 追击状态设定目标点 agent.speed chaseSpeed; agent.SetDestination(player.position); animator.SetFloat(Speed, agent.velocity.magnitude); } }有一个重要的坑不要每帧调用SetDestination太多次。虽然SetDestination本身开销不大但如果目标点在移动频繁调用时路径计算量也上去了。我实际测试下来每帧调用问题不大但如果你有个场景里同时有20个敌人可以改成每0.2秒调用一次官方性能文档里也更推荐这种方式。为了期末作业偷懒点也可以接受但知道这个优化点能在答辩时多一个聊天的素材。4.3 敌人的伤害机制与玩家生命值关联敌我伤害的核心是一个通用Health.cs脚本挂在玩家和敌人上。它内部维护当前血量并提供TakeDamage(float amount)方法public class Health : MonoBehaviour { public float maxHealth 100f; private float currentHealth; public event System.Action OnDeath; // 死亡事件 void Start() { currentHealth maxHealth; } public void TakeDamage(float damage) { currentHealth - damage; if (currentHealth 0f) { Die(); } } void Die() { OnDeath?.Invoke(); Destroy(gameObject, 0.5f); } }这种用事件event而不是直接Destroy的设计有一个明显好处Health.cs不需要知道挂在自己身上的是玩家还是敌人只管扣血和广播死亡事件。玩家的死亡逻辑显示GameOver界面、锁定鼠标通过订阅OnDeath事件来处理敌人的死亡逻辑加分、播放死亡特效也通过订阅事件来处理互不干扰。攻击判定这块我采用了“玩家武器打敌人敌人攻击直接对玩家造成伤害”的方式没有做复杂的子弹飞行碰撞。敌人的攻击有一个1秒的前摇动画动画播放到一半时调用playerHealth.TakeDamage(10f)这样玩家有时间闪避不会感觉被偷袭到怀疑人生。5. 期末设计报告怎么写才能拿高分5.1 设计报告的核心结构和每章要点很多同学程序写得还行但设计报告写得像流水账最后分数反而没上去。这里分享我写设计报告的框架基本是按软件工程的标准流程过来但结合了游戏项目的特性项目概述用200字说清楚这是个什么游戏面向什么用户。重点写目标玩家在有限时间内击杀多少敌人即为胜利不写空话。需求分析列出功能需求玩家控制、射击、敌人、计分、UI和非功能需求帧率、稳定性。系统设计画系统架构图用Word自带的图形即可说明各模块之间的关系。这章是核心要能让老师一眼看懂你的项目不是“一个脚本写到底”的烂尾工程。详细设计选取2到3个核心功能射击、敌人AI、UI交互配核心代码片段逐行解释逻辑。测试与运行贴出测试用例表格功能项、测试步骤、预期结果、实测结果至少写8到10条。总结与心得写开发过程中遇到的问题和解决方式强调自己的思考。这一节老师特别爱看。5.2 如何把代码转化为报告里的“技术亮点”写报告不能把整个项目源代码贴进去老师没有时间看几千行代码。关键是提炼出2到3个“技术亮点”把其中一两段核心代码详细解释清楚就够。以射击功能为例报告里可以这样写“本游戏采用射线检测实现射击判定开枪瞬间从主摄像机发射物理射线。相比实例化子弹物体射线检测无需模拟子弹飞行过程能保证准星指向哪里就命中哪里大幅提升射击手感同时避免了子弹物理运算的性能开销。”这段话把这个技术方案背后的“为什么”说清楚了比单纯贴代码强得多。同理敌人的状态机设计、Health组件的通用性设计、事件驱动解耦都可以作为技术亮点来写。通常期末答辩老师会问“你遇到了什么问题怎么解决的”这正好是你报告里的亮点背书。5.3 目录和运行说明的重要性提交期末作业的时候必须要有“运行说明”这一项。别觉得这是小事我亲眼见过几个组提交的作品打不开老师当堂皱眉扣分。运行说明至少包含Unity版本号重要版本不一致经常打不开项目打开方式先打开Unity Hub点Add项目选择Assets外层的Project文件夹操作方式WASD移动、鼠标旋转、左键射击、Esc暂停/解锁鼠标删除掉的第三方资源/包名列表如果有的话如果你的项目里用了 Asset Store 或网上下载的免费模型、音效、插件运行说明里要标注出处。一方面是为了学术诚信另一方面是防止因为版权问题影响作业评审。很多老师对资源来源有要求自己在报告里主动写清楚反而是加分项。5.4 答辩时的加分话术期末答辩一般5分钟左右时间很短怎么把项目的亮点传达清楚很关键。我的建议是这样组织口述第一先说游戏整体情况一局怎么玩、目标是什么。30秒内讲完让老师快速建立印象。 第二选一个自己最熟悉、写得最完善的功能展开讲比如射击判定或敌人的AI状态机配合场景里demo一键演示。这里就是展现技术深度的地方。 第三主动提一个自己遇到的坑和解决过程比如“敌人总会卡在墙角后来用了NavMesh烘焙导航网格解决的”“射击手感飘后来加了后坐力和准星扩散系统调整好了”这比被动等老师提问要好得多。 第四简单说一句下一步可以怎么优化比如“目前敌人AI是基础的状态机后续可以加入A*寻路算法或更复杂的掩体系统”。展示出你是有思考的哪怕没实现也没关系。答辩的核心策略就是“引导老师问自己擅长的方向”把话题引到你准备好的内容上。这句话是我做项目的时候一个学长教的非常管用。6. 踩坑记录与常见问题排查6.1 角色穿墙和摔出地图的常见原因开发FPS过程中角色莫名穿墙或掉出地图是新手最经常遇到的问题。大多数情况下不是代码的问题而是场景碰撞体Collider设置不正确。常见原因有两个一是地面或墙壁模型没有添加Collider组件导致物理碰撞根本没有生效角色直接走过去。解决办法是选中场景中的墙面和地面在Inspector面板点Add Component添加Box Collider或Mesh Collider角色才不会被穿透。二是场景物体缩放Scale不均匀比如一个1:10缩放的箱子模型Box Collider的尺寸会变得特别离谱看起来在很远处就被挡住了。解决办法是用Unity自带的Cube、Plane等原始几何体来搭场景别直接导入外部模型当墙体。还有一个隐蔽的问题是CharacterController与刚体Rigidbody同时挂在一个物体上。两者会冲突导致角色运动不规律甚至崩溃。Unity官方建议CharacterController替代Rigidbody用于角色运动不同时使用。6.2 UI界面反复显示/隐藏时的空引用报错FPS游戏的UI涉及开始界面、游戏界面、结束界面等多个状态切换。新手容易直接把UI元素逐个获取引用然后写逻辑显示或隐藏。问题是某个界面还没被赋值时就开始调用系统会报“NullReferenceException”空引用异常。我后期统一做了UI管理器public class UIManager : MonoBehaviour { public GameObject startPanel; public GameObject gamePanel; public GameObject gameOverPanel; public Text scoreText; public void ShowStartPanel() { startPanel.SetActive(true); gamePanel.SetActive(false); gameOverPanel.SetActive(false); } // 类似方法切换其他界面 }所有UI切换都经过这个管理器避免在每个脚本里零散地操作UI。同时每次切换界面时必须把三个面板的SetActive全部设置一遍确保只有一个面板可见。这是一个非常实用的技巧可以避免很多隐藏Bug。6.3 构建打包时场景缺失导致运行黑屏开发阶段在编辑器里运行一切正常但在Build Run里打包出来只有一片黑屏这个问题也折磨了我好久。原因大多是场景没有添加到Build SettingsFile → Build Settings → Scenes In Build里。按照官方文档的说明如果打包时列表里只有默认的空场景或者你忘了把当前场景Add Open Scenes最终exe运行时就会加载不到任何场景自然就是黑屏。操作上在打包前要确保主场景在Build Setting列表中位置为索引0或者第一个被加载的。还有一个容易被忽略的点场景列表里如果包含临时调试用的空场景也可能会干扰打包建议调试场景可以保留在Asset里但不要加入Build Setting。6.4 射击时枪口特效闪现但无法击中敌人这是一个典型的“射线被自己挡住”的问题。当枪口特效在枪口处播放时如果特效粒子系统带着碰撞盒Collider出膛粒子就会挡住射线导致射线射程为0永远打不到远处的敌人。解决方式有两种一是把粒子特效的Collision模块关掉或者给特效物体挂上Ignore Raycast层二是射击射线用两个LayerMask参数过滤掉枪口特效int layerMask ~(1 LayerMask.NameToLayer(Ignore Raycast)); Physics.Raycast(ray, out hit, range, layerMask);~(1 LayerMask.NameToLayer(...))的意思是“取反”让射线穿过忽略某层只检测该层之外的物体。期末开发时图省事的话最简单的方案就是把枪口特效的Collider全部移除问题直接消失。6.5 常见问题速查表问题现象最常见原因快速解决方案角色穿墙或掉出地图物体缺少Collider选中物体添加对应碰撞盒鼠标转视角时角色不受控MouseLook旋转逻辑只改了相机用Transform.Rotate修改角色的Y轴枪口特效打不到敌人特效粒子带碰撞体挡住射线删除特效碰撞盒或使用LayerMask过滤射击后画面前后抖动后坐力数值过大或没有恢复机制降低后坐力并加入恢复逻辑打包后黑屏场景未加入Build SettingsFile → Build Settings中添加场景敌人完全不动NavMeshAgent碰撞或没有烘焙导航网格重新Bake Navigation检查agent面积UI按钮点了没反应没挂EventSystem或画布层级被遮挡检查场景中是否有EventSystem组件对象被销毁后仍然引用没有判空就直接访问对象使用事件机制或在调用前判空这个速查表可以直接搬进设计报告的“问题记录”章节老师看了会觉得你经验总结得很到位比空写一套“开发流程”更有说服力。7. 项目扩展与经验总结做完一个能跑、能打的FPS期末项目之后如果想继续往上加东西我建议按照性价比从高到低选择。最值得做的是加入音频管理器和简单的暂停菜单因为声音和“暂停”功能对完整度的提升非常大代码上也不过是AudioSource.PlayClipAtPoint和一个计时停止的小功能。再往后可以做存档系统用PlayerPrefs保存最高分或做一个简单的地图出生点随机化让每局体验都有变化。我在实际开发中一个很深的体会是Unity项目的坑多在“系统层级”而不是单个代码问题。比如UI和输入系统版本冲突、插件版本不兼容、场景烘焙异常这些问题是读再多官方文档都不一定能一下子定位的。期末作业最关键的时间管理方式是“先跑通最小的完整闭环”——只要角色能走、能开枪打死一个敌人这个项目就已经成功了一半。后面所有加内容都是在给这个闭环“贴金”而不是重新造轮子。最后再说一个从课程设计里积累出来的小技巧提交前至少录一个3分钟的操作演示视频。很多老师查作业时不会实际跑项目尤其一个班几十个人每个人都现场运行不现实。视频里从头展示游戏流程配上操作说明能省去大量解释成本。这个视频还可以直接放进设计报告的附录里《运行演示二维码》或提交时附带文件细节做到这个程度就算代码还有些瑕疵老师也不好意思给低分。本文还有配套的精品资源点击获取