简介:这是一份面向Unity初学者与高校学生的期末作业级项目资源,基于Unity2021版本开发,主题为第三人称漫游精美场景。项目围绕角色控制器、3D场景搭建、光照材质、UI系统与输入管理展开,适合正在学习Unity3D游戏开发、需要完成课程大作业或想通过完整案例巩固基础的人群。压缩包共约2000个文件,以cs脚本、png贴图、fbx模型、prefab预制体、unity场景、mat材质、anim动画及uss/uxml界面文件为主,整体约113MB,覆盖代码、美术资源与工程配置。资源内含山谷、房屋、桌椅等场景模型,并实现鼠标控制小狐狸移动、血条显示、菜单与按钮交互等功能,可帮助读者理解CharacterController移动逻辑、UGUI事件绑定、PBR材质与全局光照等关键知识点。目前已有2994人学习下载,适合作为课程设计参考或Unity入门实战练习。
1. 期末作业里的第三人称漫游:为什么你的场景总像“纸片人逛贴图城”
每年一到期末,总有人拿着 Unity 2021 的第三人称漫游作业来找我,开口就是“场景模型挺精美的,但跑起来像在贴图城里滑冰”。问题不在模型面数,也不在贴图分辨率,而在于第三人称漫游是一套完整的交互系统:摄像机跟随、角色移动、场景碰撞、光照氛围、性能预算,缺一个都会让“精美”变成“能看不能玩”。这篇笔记就围绕 Unity 2021 版本,把第三人称漫游从场景搭建到打包提交的完整路径拆开讲清楚。适合正在做期末作业的学生,也适合第一次接触第三人称控制器的开发者。你不需要高配电脑,也不需要买昂贵素材,按下面的步骤走,能做出一个能跑、能看、能交作业的第三人称漫游场景。
2. 场景模型与第三人称控制器:先让角色站住,再谈精美
2.1 场景模型的导入与单位对齐
Unity 2021 默认单位是 1 单位等于 1 米,但很多同学从外部下载的模型是厘米单位,导入后要么巨大要么极小。常见做法是先在建模软件里确认导出单位,或者在 Unity 的模型导入设置里调整 Scale Factor。模型导入后,检查它的包围盒是否合理:一个正常角色高度在 1.7 到 1.8 个单位之间,门框高度在 2 到 2.2 个单位之间。如果角色比门还高,后面做碰撞和摄像机跟随会非常痛苦。
场景模型的组织方式也很关键。我一般会建一个空物体叫 Environment,把所有静态场景模型拖进去,然后统一设置 Static 标记。这样做的好处是 Unity 可以对这些物体做静态合批和光照烘焙。注意,不是所有物体都能标记为 Static,比如需要移动的门、可拾取的道具,标记后反而会出问题。标记完成后,在 Lighting 窗口里生成光照贴图,场景的阴影和氛围会立刻提升一个档次。
2.2 用 CharacterController 搭最小可用的第三人称移动
Unity 自带的 CharacterController 是期末作业里最稳的选择,不需要配置刚体,也不容易穿墙。下面是一个最小可用的第三人称移动脚本,挂在角色物体上即可。
using UnityEngine; [RequireComponent(typeof(CharacterController))] public class ThirdPersonMove : MonoBehaviour { public float moveSpeed = 5f; // 移动速度,单位米/秒 public float rotateSpeed = 10f; // 角色转向速度 public float gravity = -9.81f; // 重力加速度 public Transform cameraTransform; // 摄像机 Transform private CharacterController controller; private Vector3 velocity; void Start() { controller = GetComponent<CharacterController>(); } void Update() { // 1. 读取输入 float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); // 2. 计算摄像机方向上的移动向量 Vector3 camForward = cameraTransform.forward; Vector3 camRight = cameraTransform.right; camForward.y = 0; camRight.y = 0; camForward.Normalize(); camRight.Normalize(); Vector3 move = camForward * v + camRight * h; move = Vector3.ClampMagnitude(move, 1f); // 3. 移动角色 controller.Move(move * moveSpeed * Time.deltaTime); // 4. 角色朝向移动方向 if (move.magnitude > 0.1f) { Quaternion targetRot = Quaternion.LookRotation(move); transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, rotateSpeed * Time.deltaTime); } // 5. 重力 if (controller.isGrounded && velocity.y < 0) { velocity.y = -2f; } velocity.y += gravity * Time.deltaTime; controller.Move(velocity * Time.deltaTime); } }这段代码的逻辑很直白:先读输入,再把输入映射到摄像机的前方和右方,然后移动角色并让角色转向移动方向,最后处理重力。参数方面,moveSpeed 建议设在 4 到 6 之间,太低像散步,太高容易穿模。rotateSpeed 设在 8 到 12 之间,转向太慢会感觉迟钝,太快会像陀螺。gravity 用默认的 -9.81 就行,但注意 CharacterController 的 isGrounded 有时候会误判,如果角色在地面上下抖动,把 velocity.y 的复位值改成 -2f 而不是 0,能压住抖动。
2.3 摄像机跟随:别让镜头变成“晕车模拟器”
第三人称漫游的摄像机跟随是翻车重灾区。很多同学直接用 LookAt 加固定偏移,结果角色一转身镜头就穿墙,或者上下坡时镜头疯狂抖动。常见做法是用一个空物体作为摄像机父级,放在角色头顶偏后的位置,然后用脚本控制这个空物体的旋转和位置。
using UnityEngine; public class ThirdPersonCamera : MonoBehaviour { public Transform target; // 角色 Transform public float distance = 4f; // 摄像机与角色的距离 public float height = 1.8f; // 摄像机高度偏移 public float mouseSensitivity = 2f; public float minPitch = -30f; public float maxPitch = 60f; private float yaw = 0f; private float pitch = 0f; void LateUpdate() { // 鼠标控制旋转 yaw += Input.GetAxis("Mouse X") * mouseSensitivity; pitch -= Input.GetAxis("Mouse Y") * mouseSensitivity; pitch = Mathf.Clamp(pitch, minPitch, maxPitch); // 计算摄像机位置 Quaternion rotation = Quaternion.Euler(pitch, yaw, 0); Vector3 offset = rotation * new Vector3(0, 0, -distance); Vector3 targetPos = target.position + Vector3.up * height + offset; // 简单防穿墙:从角色到摄像机打射线 RaycastHit hit; if (Physics.Linecast(target.position + Vector3.up * height, targetPos, out hit)) { targetPos = hit.point + hit.normal * 0.3f; } transform.position = targetPos; transform.LookAt(target.position + Vector3.up * height); } }这段脚本挂在主摄像机上,target 拖角色。LateUpdate 保证摄像机在角色移动之后更新,避免画面撕裂。distance 建议 3 到 5,height 建议 1.5 到 2。防穿墙用 Linecast 从角色头部打到摄像机目标位置,如果中间有墙,就把摄像机拉到碰撞点前面一点。注意 hit.normal * 0.3f 这个偏移量,太小会穿墙,太大会让镜头突然跳。如果场景里有很多小物件,Linecast 会频繁触发,可以把小物件的碰撞层排除掉。
3. 场景精美度与性能预算:别让期末作业变成显卡杀手
3.1 光照烘焙与实时阴影的取舍
Unity 2021 的光照系统对新手很友好,但也很容易踩坑。场景模型导入后,如果全部用实时阴影,帧率会掉得厉害,尤其是笔记本。常见做法是把静态场景物体标记为 Static,然后在 Lighting 窗口里烘焙光照贴图。烘焙参数里,Lightmap Resolution 建议设在 20 到 40 texels per unit,太高会爆内存,太低会有噪点。Lightmap Padding 设在 2 到 4,避免贴图接缝处出现黑边。
实时阴影只留给角色和少数动态物体。在 Quality 设置里,把 Shadow Distance 调到 30 到 50 之间,太远会浪费性能,太近角色走到远处阴影就消失。Shadow Resolution 用 Medium 或 High 就够了,Very High 在期末作业里基本看不出差别,但帧率会明显下降。如果场景里有大量植被,建议关闭它们的阴影投射,只保留接收阴影,能省不少性能。
3.2 材质与贴图的优化习惯
很多同学从 Asset Store 下载的模型自带 4K 贴图,直接拖进场景,结果显存爆了。Unity 2021 默认会把贴图的 Max Size 设成 2048,但你可以手动改成 1024 甚至 512。对于期末作业这种近距离漫游场景,1024 的贴图完全够用。在贴图导入设置里,把 Generate Mip Maps 打开,把 Compression 设成 Normal Quality,能减少显存占用。
材质方面,Standard Shader 是万能选择,但不要滥用高光。如果场景是卡通风格,可以换成 URP 的 Lit Shader 或者自己写一个简单的 Shader。注意,Unity 2021 默认创建的是 Built-in Render Pipeline 项目,如果你用了 URP 的 Shader,材质会变成紫红色。这是新手最常见的翻车现场之一。解决办法是在 Package Manager 里安装 Universal RP,然后创建 URP Asset 并赋值到 Graphics 设置里。但如果你不想折腾,就老老实实用 Standard Shader。
3.3 用 Profiler 找到性能瓶颈
Unity 的 Profiler 窗口是期末作业的后悔药。打开 Window > Analysis > Profiler,运行场景,看 CPU 和 GPU 的耗时。如果 CPU 里 Rendering 占比很高,说明 Draw Call 太多,需要合批。如果 GPU 里 Shadow 占比高,说明阴影太吃性能。如果 GC Alloc 每帧都在涨,说明代码里有频繁的堆分配,比如在 Update 里 new Vector3 或者字符串拼接。
一个常见的优化手段是开启 Static Batching 和 Dynamic Batching。Static Batching 对标记为 Static 的物体自动生效,Dynamic Batching 对小于 900 个顶点的小物件生效。但注意,Dynamic Batching 在 Unity 2021 里对阴影投射物体不太友好,可能会增加 CPU 开销。如果场景里有很多小物件,比如石头、草丛,建议用 GPU Instancing 或者手动合并网格。
4. 避坑与排查:期末作业里最容易翻车的 5 个地方
4.1 角色穿墙或掉出地图
现象:角色走到墙角或者斜坡时,直接穿过去,或者掉到地面以下。原因通常是 CharacterController 的 Skin Width 设得太小,或者碰撞体没有覆盖整个角色。解决方法是把 Skin Width 设在 0.01 到 0.05 之间,把 Min Move Distance 设成 0。另外检查场景模型的碰撞体,很多下载的模型只有 Mesh Renderer 没有 Mesh Collider,需要手动添加。如果地面是 Terrain,确保 Terrain Collider 存在。
4.2 摄像机抖动或穿墙
现象:角色移动时摄像机上下抖动,或者镜头穿到墙里面。原因可能是摄像机脚本放在 Update 里而不是 LateUpdate,或者防穿墙的射线起点不对。解决方法是把摄像机逻辑放在 LateUpdate,射线起点用角色头部位置而不是脚底。如果抖动依然存在,检查角色的 CharacterController 是否在移动时产生了垂直速度,可以在摄像机脚本里对 Y 轴做平滑处理。
4.3 材质变成紫红色
现象:导入的模型或者自己写的 Shader 显示为紫红色。原因是 Shader 与当前渲染管线不匹配。Unity 2021 默认是 Built-in Pipeline,如果你用了 URP 或 HDRP 的 Shader,就会这样。解决方法是统一渲染管线:要么把项目升级到 URP,要么把材质换成 Standard Shader。升级 URP 时注意,所有材质都需要重新赋值,光照设置也会变,建议在项目初期就决定好。
4.4 光照贴图出现黑边或接缝
现象:烘焙后的场景在物体接缝处有黑边,或者光照贴图看起来有块状噪点。原因是 Lightmap Padding 太小,或者 UV 重叠。解决方法是把 Lightmap Padding 调到 4 以上,在建模软件里检查 UV 是否展开且不重叠。如果问题依旧,把 Lightmap Resolution 调高一点,但不要超过 60,否则烘焙时间会很长。
4.5 打包后画面拉伸或操作失灵
现象:在编辑器里跑得好好的,打包到手机或 PC 后画面拉伸,或者鼠标键盘输入没反应。原因是分辨率设置和输入系统不匹配。Unity 2021 默认的 Input Manager 在打包后通常没问题,但如果你用了新的 Input System,需要在 Player Settings 里切换 Active Input Handling。画面拉伸是因为没有设置 Canvas Scaler 或者摄像机 Aspect Ratio。解决方法是把 Canvas 的 Render Mode 设为 Screen Space - Overlay,Canvas Scaler 设为 Scale With Screen Size,参考分辨率设成 1920x1080。
5. 从能跑到好交:一个让作业加分的小技巧
如果你已经按上面的步骤把第三人称漫游跑通了,但觉得场景还是“差点意思”,我建议你花半小时做一件事:给摄像机加一个轻微的呼吸感。具体做法是在摄像机脚本里,根据角色的移动速度,给摄像机的 FOV 或者位置加一个正弦波偏移。角色静止时 FOV 是 60,跑动时平滑过渡到 70,同时摄像机位置在垂直方向上有 0.05 个单位的正弦浮动。这个技巧在 3A 游戏里很常见,能显著提升漫游的沉浸感,而且代码量极小。
// 在 ThirdPersonCamera 的 LateUpdate 末尾加入 float speed = target.GetComponent<CharacterController>().velocity.magnitude; float targetFov = Mathf.Lerp(60f, 70f, speed / 6f); Camera.main.fieldOfView = Mathf.Lerp(Camera.main.fieldOfView, targetFov, Time.deltaTime * 5f); // 呼吸感 float breath = Mathf.Sin(Time.time * 2f) * 0.05f; transform.position += Vector3.up * breath;注意,呼吸感的幅度不要超过 0.1,否则会晕。FOV 的变化范围也不要超过 15 度,否则画面会像鱼眼。这个技巧不需要额外资源,也不吃性能,但能让你的期末作业在老师眼里“有点东西”。
最后说一个我自己的习惯:每次做完一个场景,我都会用 Profiler 跑一遍,把 Draw Call 控制在 200 以内,把帧率稳定在 60。如果做不到,就砍阴影、砍贴图分辨率、砍粒子特效。期末作业的评分标准里,“能跑”永远比“好看”重要。希望帮到你。
本文还有配套的精品资源,点击获取