简介:这份资源是面向Unity初学者与高校学生的期末大作业完整工程,基于Unity2021版本制作,主题为第三人称漫游精美场景,适合需要提交课程设计或想系统练习3D游戏开发的学习者。项目围绕角色控制器、3D场景搭建、光照材质、UI系统与输入管理展开,包含山谷、房屋、桌椅等模型元素,并实现鼠标控制小狐狸移动、血条显示以及菜单按钮交互,覆盖从场景布置到交互逻辑的完整流程。压缩包共约2000个文件,以cs脚本、png贴图、fbx模型、prefab预制体、unity场景与mat材质为主,另有anim动画、shader着色器、uss/uxml界面文件及mp3音效等,整体约113MB,目录结构清晰,便于按模块查阅与二次修改。目前已有2994人学习下载,读者可借此掌握第三人称漫游的实现思路、场景美化技巧与UGUI交互方法,是兼顾参考与练手的实用工程。
1. 从一份 Unity 2021 期末作业说起:第三人称漫游到底要交付什么
每年期末季,问得最多的一类问题就是「unity期末作业-第三人称漫游精美场景模型,unity2021版本」这种题目到底怎么落地。它听起来像课程作业,实际考的是三件事:角色能不能被稳定操控、摄像机能不能舒服地跟随、场景能不能撑住「精美」两个字。很多人一上来就去找 unity 人物模型资源,结果模型拖进场景发现没有 Avatar、动画不生效、摄像机穿墙、帧率掉到 20,最后交上去的只是一个能走但很别扭的 Demo。
这篇笔记面向两类人:一类是刚学完 UGUI 和基础脚本、要交作业的学生;另一类是想用 Unity 2021 快速搭一个可演示的第三人称漫游原型、后面再往数字孪生或仿真实验方向扩的从业者。我会按「场景怎么搭 → 角色怎么控 → 摄像机怎么跟 → 性能怎么保 → 怎么验收」的顺序讲,参数给到能直接抄,坑也标出来。整套方案在 Unity 2021.3 LTS 上验证过,不依赖任何付费插件,用内置的 Character Controller 和 Cinemachine 就能跑通。
2. 场景搭建与资源导入:把「精美」拆成可执行的清单
2.1 先定场景规模,再决定资源规格
「精美场景模型」最容易翻车的地方,是一开始就往里塞高模。第三人称漫游的视角离地面很近,玩家会盯着墙面、地面、植被看,所以真正影响观感的不是模型面数,而是贴图分辨率、法线细节和光照。我的做法是先定漫游范围:一个 200m × 200m 的封闭区域,用 Terrain 做地形,建筑和道具控制在 30 个以内。
资源规格上,Unity 2021 对移动端和 PC 端的要求差别很大。如果是 PC 演示,单模型 5 万面以内、贴图 2K 足够;如果要打包到手机,单模型压到 1 万面、贴图 1K,并且必须做 LOD。下面这张表是我常用的资源验收标准:
| 资源类型 | PC 演示 | 移动端 | 检查项 |
|---|---|---|---|
| 建筑模型 | ≤5 万面 | ≤1 万面 | 是否有 LOD Group |
| 植被 | 用 Terrain 笔刷 | 用 GPU Instancer 或合并 | Draw Call 是否合并 |
| 贴图 | 2K,ASTC 压缩 | 1K,ASTC 6x6 | Mipmap 是否开启 |
| 材质 | Standard / URP Lit | URP Lit | 是否共用材质球 |
导入模型时有个高频问题:材质变成紫红色。这几乎都是渲染管线不匹配导致的——模型用的是 Built-in 材质,而项目建的是 URP。解决办法是 Edit → Rendering → Materials → Convert Selected Built-in Materials to URP,全选转换一遍。如果转换后还是紫红,检查 Shader 是否被项目裁剪掉了,在 Graphics Settings 里把 Always Included Shaders 补上。
2.2 用 ProBuilder 快速搭白盒,再替换精模
直接摆精模效率极低,我一般先用 ProBuilder 搭白盒,把动线、房间比例、楼梯高度定下来。ProBuilder 在 Package Manager 里搜「ProBuilder」安装即可,免费。搭白盒时注意:第三人称角色的肩宽约 0.5m,门洞至少留 1.2m,楼梯每级高度不超过 0.3m,否则 Character Controller 的 Step Offset 会卡住。
白盒确认后,用 Prefab 替换。替换时保持 Transform 的 Position 和 Rotation 不变,只换 Mesh。这一步建议用脚本批量做,避免手动拖拽出错:
// 批量把选中物体的 Mesh 替换为指定 Prefab 的 Mesh // 挂在编辑器脚本目录下使用 using UnityEngine; using UnityEditor; public class ReplaceMeshTool : EditorWindow { public GameObject targetPrefab; [MenuItem("Tools/批量替换 Mesh")] static void Open() => GetWindow<ReplaceMeshTool>("替换 Mesh"); void OnGUI() { targetPrefab = (GameObject)EditorGUILayout.ObjectField("目标 Prefab", targetPrefab, typeof(GameObject), false); if (GUILayout.Button("替换选中物体") && targetPrefab != null) { foreach (var go in Selection.gameObjects) { var src = targetPrefab.GetComponentInChildren<MeshFilter>(); var dst = go.GetComponent<MeshFilter>(); if (src != null && dst != null) { Undo.RecordObject(dst, "Replace Mesh"); dst.sharedMesh = src.sharedMesh; // 用 sharedMesh 避免材质实例化 } } } } }这段脚本的关键点是sharedMesh,用mesh会为每个物体生成独立网格实例,内存直接翻倍。参数上,targetPrefab选你要替换成的精模,选中场景里所有白盒物体后点按钮即可。替换完记得检查 Collider,白盒的 BoxCollider 通常比精模的 MeshCollider 性能好,能保留就保留。
2.3 光照与后处理:让场景「看起来贵」的两个开关
场景精美度一半靠光照。Unity 2021 默认是实时 GI,但期末作业这种静态场景,用 Baked GI 更划算。Window → Rendering → Lighting,把 Lighting Mode 设为 Baked Indirect,然后点 Generate Lighting。烘焙前把不需要参与烘焙的物体设为 Contribute GI 关闭,否则烘焙时间会很长。
后处理用 URP 自带的 Volume 就行。加一个 Global Volume,Override 里加 Bloom、Color Adjustments、Vignette。Bloom 的 Threshold 设 1.0、Intensity 设 0.3 左右,太高会糊。Color Adjustments 的 Post Exposure 拉 0.2,Saturation 拉 10,画面立刻通透。注意后处理对性能有影响,移动端要把 HDR 关掉,Bloom 的 Downscale 设为 Quarter。
3. 第三人称角色控制:Character Controller 的参数怎么调才不飘
3.1 为什么不用 Rigidbody 而用 Character Controller
第三人称漫游的核心需求是「指哪走哪、不滑不飘」。Rigidbody 受物理引擎影响,在斜坡和台阶上容易打滑,还要处理摩擦力、阻力一堆参数。Character Controller 是运动学控制器,移动完全由代码控制,配合Move()和SimpleMove()能做出很跟手的手感。代价是要自己处理重力,但代码量很小。
角色模型导入后,先检查 Rigby 的 Avatar 是否配置正确。在 Inspector 里选 Rig → Animation Type → Humanoid,然后 Configure 一下,确保骨骼映射没有红叉。如果模型是 Generic 的,动画也能用,但 Mixamo 的动画建议转 Humanoid,方便复用。
3.2 移动脚本:把速度、重力、转向拆成独立参数
下面这份脚本是我在多个漫游项目里复用的版本,基于 Character Controller,支持相机相对移动和鼠标转向:
using UnityEngine; [RequireComponent(typeof(CharacterController))] public class ThirdPersonController : MonoBehaviour { [Header("移动参数")] public float walkSpeed = 3.5f; // 行走速度,漫游场景 3~4 比较自然 public float runSpeed = 7f; // 奔跑速度,按住 Left Shift 触发 public float rotationSmoothTime = 0.12f; // 转向平滑时间,越小越跟手 [Header("物理参数")] public float gravity = -15f; // 重力,比真实值大一点手感更实 public float jumpHeight = 1.2f; // 跳跃高度,按空格触发 private CharacterController controller; private Transform cameraTransform; private float verticalVelocity; private float turnSmoothVelocity; void Start() { controller = GetComponent<CharacterController>(); cameraTransform = Camera.main.transform; } void Update() { // 1. 读取输入 float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical"); Vector3 inputDir = new Vector3(h, 0, v).normalized; // 2. 相机相对方向 Vector3 moveDir = Vector3.zero; if (inputDir.magnitude >= 0.1f) { float targetAngle = Mathf.Atan2(inputDir.x, inputDir.z) * Mathf.Rad2Deg + cameraTransform.eulerAngles.y; float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref turnSmoothVelocity, rotationSmoothTime); transform.rotation = Quaternion.Euler(0, angle, 0); moveDir = Quaternion.Euler(0, targetAngle, 0) * Vector3.forward; } // 3. 速度与重力 float speed = Input.GetKey(KeyCode.LeftShift) ? runSpeed : walkSpeed; if (controller.isGrounded) { verticalVelocity = -2f; // 贴地,避免悬空 if (Input.GetButtonDown("Jump")) verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity); } verticalVelocity += gravity * Time.deltaTime; // 4. 合并移动 Vector3 velocity = moveDir * speed + Vector3.up * verticalVelocity; controller.Move(velocity * Time.deltaTime); } }逻辑上分四步:读输入、算相机相对方向、处理重力、合并移动。参数里walkSpeed和runSpeed的比值建议 1:2,差距太小没有奔跑感,太大容易穿模。rotationSmoothTime设 0.1~0.15,低于 0.1 转向会抖,高于 0.2 会感觉角色在漂。gravity用 -15 而不是 -9.8,是因为游戏里的重力通常要夸张一点,跳跃落地才干脆。
3.3 动画状态机:Idle、Walk、Run 的过渡条件
动画用 Animator 做混合树最省事。新建一个 Blend Tree,类型选 1D,参数用Speed,把 Idle、Walk、Run 三个动画按 0、3.5、7 的阈值摆进去。然后在脚本里把controller.velocity.magnitude传给 Animator:
// 在 ThirdPersonController 的 Update 末尾追加 float currentSpeed = new Vector3(controller.velocity.x, 0, controller.velocity.z).magnitude; animator.SetFloat("Speed", currentSpeed, 0.1f, Time.deltaTime);SetFloat的第三个参数是阻尼时间,0.1 秒能让动画过渡更顺。注意controller.velocity是包含重力的,所以要把 y 分量去掉再取模,否则跳跃时动画会乱切。如果动画出现滑步,检查 Walk 动画的播放速度和walkSpeed是否匹配——一般动画自带的位移速度要和脚本速度对齐,不对齐就调 Animator 的 Speed 倍率。
4. 摄像机跟随:Cinemachine 三分钟搞定,但参数要改
4.1 用 Cinemachine 搭基础跟随
Unity 2021 里 Cinemachine 是官方包,Package Manager 搜「Cinemachine」安装。场景里右键 → Cinemachine → Create FreeLook Camera,它会自动生成一个 FreeLook 相机,绑定 Look At 和 Follow 到角色。FreeLook 自带三个轨道(Top、Middle、Bottom),默认参数就能用,但有几个地方必须改。
第一,把 Follow 的 Binding Mode 设为 World Space,否则角色旋转时相机会跟着转,晕。第二,Lens 的 Field of View 设 60,太大边缘会畸变,太小视野憋屈。第三,Orbital Transposer 的 X Axis 里,Speed 设 300 左右,Input Axis Name 保持「Mouse X」。Y Axis 的 Speed 设 2,Range 设 -30 到 70,限制俯仰角,防止相机翻到地下。
4.2 相机穿墙的三种处理方案
第三人称漫游最影响体验的就是相机穿墙。Cinemachine 自带一个 Cinemachine Collider 扩展,加上去之后相机会自动避开障碍物。在 FreeLook 的 Extensions 里 Add Extension → Cinemachine Collider,参数这样设:
| 参数 | 建议值 | 说明 |
|---|---|---|
| Collide Against | Default | 检测层,别包含角色本身 |
| Minimum Distance From Target | 0.5 | 离角色最近距离 |
| Camera Radius | 0.3 | 相机碰撞半径 |
| Damping Into Collision | 0.2 | 进入碰撞的缓冲 |
| Damping From Collision | 0.5 | 离开碰撞的缓冲 |
如果场景里有大量薄墙,Cinemachine Collider 可能会抖动,这时候改用射线检测手动推相机。思路是从角色头部向相机方向发一条 SphereCast,打到墙就把相机拉到碰撞点前面。这种方案代码多一点,但更可控,适合有复杂室内场景的作业。
4.3 鼠标锁定与灵敏度
漫游场景一般要锁定鼠标,按 Esc 解锁。在脚本里加:
void Update() { if (Input.GetKeyDown(KeyCode.Escape)) { Cursor.lockState = CursorLockMode.None; Cursor.visible = true; } if (Input.GetMouseButtonDown(0)) { Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; } }灵敏度不要写死在 Cinemachine 里,而是通过CinemachineFreeLook.m_XAxis.m_MaxSpeed动态调。给玩家一个设置面板,滑条范围 100~500,默认 300。这样不同人用不同鼠标都能找到舒服的手感。
5. 避坑与排查:期末作业最容易翻车的 5 个点
5.1 角色移动时抖动或卡顿
现象:角色在平地上走得很顺,一上斜坡或台阶就开始抖,甚至卡住不动。 原因:Character Controller 的 Slope Limit 和 Step Offset 默认值不适合当前场景。Slope Limit 默认 45 度,Step Offset 默认 0.3,如果台阶高度超过 0.3 就会卡。 解决:在 Character Controller 组件里把 Step Offset 调到 0.35~0.4,Slope Limit 根据场景调,室内楼梯多就设 50。另外检查角色胶囊体的 Height 和 Radius,Height 至少 1.8,Radius 0.3,太小会掉进地面缝隙。
5.2 相机跟随延迟严重
现象:角色已经转弯了,相机过一秒才转过来,操作像在开船。 原因:Cinemachine 的 Damping 参数太大,或者 Follow 的 Binding Mode 设成了 Lock To Target With World Up 但角色旋转太快。 解决:把 FreeLook 的 X Axis 和 Y Axis 的 Damping 都降到 0.2 以下,Follow Damping 设 0.1。如果还是延迟,检查脚本里rotationSmoothTime是不是设太大了,0.12 是上限。
5.3 场景烘焙后光照全黑
现象:点 Generate Lighting 之后,场景要么全黑,要么只有实时阴影没有间接光。 原因:Lighting Settings 里 Auto Generate 没关,或者场景物体没有标记为 Static,或者 Lightmap 分辨率设太低。 解决:先关掉 Auto Generate,手动烘焙。把静态物体勾上 Static,Lightmap Resolution 设 20~40 texels per unit,太高烘焙慢,太低有噪点。如果还是黑,检查 Directional Light 的 Mode 是不是 Baked,以及 Environment Lighting 的 Source 是不是 Skybox。
5.4 打包到手机后画面拉伸
现象:在编辑器里比例正常,打包到手机后 UI 和画面被拉伸变形。 原因:Canvas 的 Canvas Scaler 没设对,或者相机的 Aspect Ratio 没适配。 解决:Canvas Scaler 的 UI Scale Mode 设为 Scale With Screen Size,Reference Resolution 设 1920×1080,Match 设 0.5。相机方面,如果用的是固定 FOV,横屏和竖屏切换时会变形,建议在脚本里根据Screen.width / Screen.height动态调 FOV,竖屏时把 FOV 调大。
5.5 帧率突然掉到 20 以下
现象:场景搭完跑起来只有 20 多帧,打开 Profiler 发现 Draw Call 好几百。 原因:材质球没有共用,每个物体一个材质实例;或者植被没有合并,每个草一个 Draw Call。 解决:检查材质球,相同贴图的物体用同一个材质。植被用 Terrain 的 Detail 或者 GPU Instancer 合并。另外把 Camera 的 Far Clip Plane 从 1000 降到 200,远景用 Skybox 代替。如果用了实时阴影,把 Shadow Distance 降到 50,阴影分辨率设 1024。
6. 验收与进阶:怎么证明这份作业「能打」
6.1 用 Profiler 和 Frame Debugger 做一次体检
交作业前跑一遍 Profiler,重点看三项:CPU 的Camera.Render耗时、Batch Count、SetPass Calls。PC 端 Batch Count 控制在 200 以内,移动端 100 以内。如果超标,用 Frame Debugger 逐帧看是哪个物体在打断合批。常见原因是不同材质球交替渲染,把相同材质的物体在 Hierarchy 里排在一起能缓解。
6.2 加一个「漫游路径录制」功能,演示时不用手操
答辩或演示时手动走容易翻车,我一般会加一个路径录制:按 R 开始记录角色位置,再按 R 停止,按 P 回放。核心是用List<Vector3>存位置,回放时用Vector3.Lerp插值:
using System.Collections.Generic; using UnityEngine; public class PathRecorder : MonoBehaviour { public float recordInterval = 0.1f; // 采样间隔 private List<Vector3> path = new List<Vector3>(); private bool isRecording, isPlaying; private float timer; private int playIndex; void Update() { if (Input.GetKeyDown(KeyCode.R)) { isRecording = !isRecording; if (isRecording) path.Clear(); } if (Input.GetKeyDown(KeyCode.P)) { isPlaying = true; playIndex = 0; } if (isRecording) { timer += Time.deltaTime; if (timer >= recordInterval) { path.Add(transform.position); timer = 0; } } if (isPlaying && playIndex < path.Count - 1) { transform.position = Vector3.Lerp(path[playIndex], path[playIndex + 1], Time.deltaTime * 10f); if (Vector3.Distance(transform.position, path[playIndex + 1]) < 0.1f) playIndex++; } } }recordInterval设 0.1 秒,10Hz 采样足够平滑,数据量也小。回放时把 Character Controller 关掉,否则会和transform.position冲突。这个功能在演示时非常实用,路径固定,镜头稳定,评委一看就知道你考虑过交付场景。
6.3 从作业到作品:三个可以继续挖的方向
如果这份作业想拿高分或者当作品集,可以在三个方向加码。一是加一个简单的 UI 数字滚轮效果,显示当前坐标或帧率,用 UGUI 的 Scroll Rect 加 Mask 就能做,网上有现成思路。二是把场景做成数字孪生风格的俯视图,加一个正交相机切换,按 Tab 切换视角,这在仿真实验类题目里很加分。三是做包体优化,把贴图压缩格式从 RGBA32 改成 ASTC,Build 时勾上 Strip Engine Code,包体能小一半。
我自己的习惯是,每次交作业前把项目从零打包一次,在一台没装 Unity 的机器上跑一遍。这一步能暴露 90% 的「在我电脑上好好的」问题。希望帮到你。
本文还有配套的精品资源,点击获取