这篇总览可以按四组主题阅读:
  1. Unity 基础、生命周期与交互。
  1. Unity UI、存档与资源管理。
  1. Unity 架构、渲染与性能。
  1. Unity 网络、运行时与项目系统。
 
💡
先理解对象生命周期、坐标空间、物理更新、资源生命周期和性能分析,再学习具体框架封装。

1. GameObject、Component 与 MonoBehaviour

本章从「GameObject 与 gameObject」和「MonoBehaviour」两方面说明 GameObject、Component 与 MonoBehaviour。

1.1 GameObjectgameObject

写法
含义
GameObject
UnityEngine 中的类,表示场景对象和组件容器
gameObject
Component 提供的属性,返回当前组件所在的 GameObject
Transform
变换组件类型
transform
Component 提供的快捷属性,返回当前对象的 Transform
GameObject 本身通常只负责:
  • 名称、Tag、Layer、激活状态;
  • 容纳 Component;
  • 建立父子层级。
对象的渲染、碰撞、音频和脚本行为均由组件提供。Unity 的核心设计思想是组合优于继承:给 GameObject 组合不同组件,而不是建立很深的继承树。

1.2 MonoBehaviour

继承 MonoBehaviour 后,脚本才能作为组件挂载到 GameObject,并接收 Unity 的生命周期回调。
常用成员:
注意:
  • 生命周期函数由 Unity 按名称调用,不建议手动调用;
  • 生命周期函数通常可以写成 private
  • 不要把构造函数当作 Unity 组件初始化入口,应使用 AwakeOnEnableStart

2. 生命周期与启用状态

本章从「常见执行顺序」「enabled、activeSelf 与 activeInHierarchy」和「OnGUI」等方面说明生命周期与启用状态。

2.1 常见执行顺序

简化顺序如下:
实际执行还包含输入、动画、物理模拟、渲染回调等阶段。
2.1.1 Awake
适合:
  • 获取本对象组件;
  • 初始化不依赖其他对象启动顺序的数据;
  • 建立内部状态。
2.1.2 OnEnable
对象或组件进入启用状态时调用,可能执行多次。适合:
  • 订阅事件;
  • 启用 InputAction;
  • 重置每次启用都需要恢复的状态。
2.1.3 Start
组件第一次启用并即将开始更新前调用一次。适合依赖其他对象已完成 Awake 的初始化,但仍不要依赖不同对象 Start 的默认相对顺序。
2.1.4 FixedUpdate
按固定时间步长调度,服务于物理模拟,它不是“每帧一次”而是当:
  • 帧率高时,一个渲染帧可能没有 FixedUpdate
  • 帧率低时,一个渲染帧可能补做多次 FixedUpdate
物理力、动态刚体速度和 Rigidbody.MovePosition 一般在这里处理。
2.1.5 Update
每个渲染帧调用。适合:
  • 输入采样;
  • 非物理逻辑;
  • 计时;
  • 普通状态更新。
需要按“每秒速度”移动时乘 Time.deltaTime
2.1.6 LateUpdate
在该帧的 Update 之后执行,常用于:
  • 摄像机跟随;
  • 角色移动完成后的附属对象修正;
  • 需要读取本帧最终 Transform 状态的逻辑。
2.1.7 OnDisableOnDestroy
  • OnDisable 适合取消事件订阅、停用输入;
  • OnDestroy 适合释放该对象拥有的资源;
  • 不要把关键存档只放在 OnApplicationQuitOnDestroy,移动端进程可能被系统直接终止。

2.2 enabledactiveSelfactiveInHierarchy

本节从「enabled」「SetActive」和「三个状态的区别」等方面说明 enabled、activeSelf 与 activeInHierarchy。
2.2.1 enabled
只控制当前 Behaviour 组件。
禁用后:
  • UpdateFixedUpdateLateUpdateOnGUI 等常规回调停止;
  • 状态切换会触发 OnEnable / OnDisable
  • 已启动协程通常不会因为仅禁用 MonoBehaviour 而自动停止。
2.2.2 SetActive
会停用整个 GameObject:
  • Renderer、Collider、Rigidbody、脚本等组件失效;
  • 该 GameObject 上的协程停止;
  • 子对象的有效激活状态也可能受影响。
2.2.3 三个状态的区别
属性
含义
activeSelf
当前对象自身设置的本地激活状态
activeInHierarchy
考虑父对象之后,当前对象在层级中的实际激活状态
enabled
单个 Behaviour 组件是否启用

2.3 OnGUI

OnGUI 属于 IMGUI 即时模式界面:
特点:
  • 一个渲染帧内可能因不同 GUI 事件调用多次;
  • 适合编辑器工具、调试信息和简单原型;
  • 正式运行时 UI 通常使用 UGUI 或 UI Toolkit;
  • 组件 enabled == false 时不会调用。

3. Transform、坐标空间与移动

本章从「世界空间与局部空间」「方向向量」和「Translate」等方面说明 Transform、坐标空间与移动。

3.1 世界空间与局部空间

世界空间
局部空间
position
localPosition
rotation
localRotation
世界方向
相对父对象方向
lossyScale(只读近似世界缩放)
localScale
父对象变化会影响子对象的世界位置、旋转和缩放。

3.2 方向向量

静态方向:
transform.forward 会随物体旋转变化,Vector3.forward 不会。

3.3 Translate

默认相对自身坐标系移动:
显式指定世界空间:
相对于另一个 Transform:
translation 表示位移量,不是速度。若 speed 的单位是“单位/秒”,必须乘时间增量。

3.4 Rotate

Transform.Rotate(axis, angle) 默认使用 Space.Self,并不是默认绕世界轴:

3.5 Transform 与物理对象

对于动态 Rigidbody,不应在每帧直接改 transform.position 来绕过物理系统。常见方式:
或者设置线速度、施加力。直接改 Transform 可能造成穿透、抖动或碰撞结果不稳定。

4. 欧拉角、四元数与向量数学

本章从「欧拉角」「Quaternion」和「点积」等方面说明欧拉角、四元数与向量数学。

4.1 欧拉角

欧拉角用三个角表示旋转,Unity Inspector 中通常显示为:
  • X:Pitch,俯仰;
  • Y:Yaw,偏航;
  • Z:Roll,滚转。
Unity 内部使用四元数保存 Transform 旋转,但将旋转转换为欧拉角进行编辑时,仍会遇到:
  • 万向锁;
  • 角度跳变,例如 359° 等价;
  • 同一个旋转有多组等价欧拉角;
  • 逐帧读取、修改并写回 eulerAngles 容易产生误差和不连续。
欧拉角适合人工输入和简单单轴控制;复杂组合与插值优先使用 Quaternion。

4.2 Quaternion

四元数用于表示三维旋转。表示有效旋转时通常应为单位四元数,不应直接手改 x/y/z/w
常用 API:
4.2.1 组合顺序
result 作用于向量时,先应用右侧 rotX,再应用左侧 rotY。四元数乘法不满足交换律。
4.2.2 平滑旋转
这种写法是逐渐逼近目标,不保证恒定角速度。要求恒定最大角速度时:

4.3 点积

三维点积:
结果是标量。
用途:
  • 判断夹角;
  • 计算投影;
  • 判断目标在前方还是后方。
归一化向量时:
  • dot = 1:同向;
  • dot = 0:垂直;
  • dot = -1:反向。

4.4 叉积

三维叉积:
结果是垂直于 ab 所在平面的向量,方向遵循右手定则,模长等于平行四边形面积:
用途:
  • 求法线;
  • 判断旋转方向;
  • 计算面积。
二维通常使用标量叉积:
  • 大于 0:从 ab 为逆时针方向;
  • 小于 0:顺时针方向;
  • 等于 0:共线。

4.5 点是否在三角形内

叉积同侧法:
若三角形退化为共线点,应先单独判断面积是否接近 0。

4.6 抛体运动

在恒定重力下:
已知起点 start、终点 target 和飞行时间 duration,可求初速度:
这是假设重力恒定、忽略空气阻力的理想模型。

5. 物理系统与 CharacterController

本章从「Collider」「Rigidbody」和「Kinematic」等方面说明物理系统与 CharacterController。

5.1 Collider

Collider 定义物理碰撞形状,不负责渲染。
常见类型:
  • BoxCollider;
  • SphereCollider;
  • CapsuleCollider;
  • MeshCollider;
  • 由多个基础碰撞器组成的 Compound Collider。
物理形状不必与可视网格完全相同。基础碰撞体通常比复杂 MeshCollider 更稳定、开销更低。
5.1.1 三种 Collider 配置
配置
组成
静态碰撞体
Collider,无 Rigidbody
动态碰撞体
Collider + Rigidbody,isKinematic == false
运动学碰撞体
Collider + Rigidbody,isKinematic == true
静态碰撞体不是“质量无限大的刚体”,而是没有 Rigidbody、由物理系统视为静态环境几何体的 Collider

5.2 Rigidbody

动态 Rigidbody 可受到:
  • 重力;
  • 力和冲量;
  • 扭矩;
  • 碰撞响应;
  • 关节约束。
常用属性和方法:

5.3 Kinematic

isKinematic == true 时:
  • 力、重力和碰撞冲量不再驱动该 Rigidbody;
  • 位置由动画或脚本控制;
  • 它仍可通过碰撞或关节影响动态刚体;
  • 常用于移动平台、动画驱动对象和布娃娃切换。
不能把 Kinematic 简单理解为“完全不参加物理”。它不由物理力驱动,但仍属于物理场景中的刚体对象。

5.4 Collision 与 Trigger

本节从「普通碰撞」和「Trigger」两方面说明 Collision 与 Trigger。
5.4.1 普通碰撞
两个非 Trigger Collider 接触时才可能产生 OnCollision...,且要收到碰撞回调至少一方应为动态物理体,即带有 isKinematic == false 的 Rigidbody。
两个静态 Collider 之间不会发生动态碰撞;两个 Kinematic Rigidbody 之间通常也不会触发 OnCollision 回调。
5.4.2 Trigger
勾选 Is Trigger 后:
  • 检测重叠;
  • 不产生阻挡和碰撞冲量;
  • 使用 OnTriggerEnter/Stay/Exit
  • 每一对参与者中至少一方需要 Rigidbody,Kinematic 也可以。
3D Physics 与 2D Physics 是两套独立系统,Collider/RigidbodyCollider2D/Rigidbody2D 不能混用事件。

5.5 物理更新建议

  • 输入可在 Update 采样;
  • 将结果缓存后,在 FixedUpdate 驱动 Rigidbody;
  • 使用 Time.fixedDeltaTime 处理固定步长运动;
  • 需要平滑显示时使用 Rigidbody 插值;
  • 高速物体根据需要选择合适的 Collision Detection 模式;
  • 使用 Layer Collision Matrix 排除不需要的碰撞对。

5.6 CharacterController

CharacterController 是专为可控角色准备的胶囊形控制组件:
  • 由脚本明确提供位移;
  • 不按普通 Rigidbody 的质量、力和冲量运动;
  • 内置斜坡限制、台阶偏移等角色移动能力;
  • 使用 OnControllerColliderHit 处理部分碰撞信息。
5.6.1 Move
CharacterController.Move 接收的是本次位移量,不会自动施加重力:
5.6.2 CharacterController 与 Rigidbody 的选择
需求
更适合
精确、响应快的第一/第三人称控制
CharacterController 或自定义运动学控制器
受爆炸、推力、碰撞冲量影响
Rigidbody
车辆、箱子、碎片、布娃娃
Rigidbody
需要自动台阶与斜坡限制
CharacterController
高度定制的角色物理
Rigidbody 或自研运动控制器
需要按玩法、联网和物理交互需求来选择。

6. Input System、协程、Task 与线程

本章从「新 Input System」「协程」和「Task」等方面说明 Input System、协程、Task 与线程。

6.1 新 Input System

基本流程:
  1. 创建 Input Actions Asset;
  1. 建立 Action Map;
  1. 添加 Action;
  1. 设置 Action Type 和 Control Type;
  1. 绑定键盘、鼠标、手柄或 XR 输入;
  1. 在代码中启用、读取或订阅回调。
推荐使用 InputActionReference,减少字符串查找:
两种使用方式:
  • 轮询:ReadValue<T>()WasPressedThisFrame()
  • 事件:startedperformedcanceled
应避免每帧通过字符串反复查找 Action。

6.2 协程

协程基于 IEnumeratoryield,由 Unity 主循环分阶段恢复,默认运行在主线程,不提供 CPU 并行。
常见等待对象:
注意:
  • yield return null 一般在下一帧的协程阶段继续;
  • WaitForSeconds 使用缩放时间,受 Time.timeScale 影响;
  • 协程不适合执行长时间 CPU 计算,否则仍会卡主线程;
  • 仅禁用 MonoBehaviour 通常不会停止已启动协程;
  • 停用 GameObject 会停止其上的协程。

6.3 Task

Task 是 .NET 的异步操作抽象:
  • I/O 异步不一定占用工作线程;
  • Task.Run 通常在线程池执行;
  • async/await 使异步控制流更易读;
  • Unity API 大多只能在主线程调用。
生产代码应优先返回 Task,避免滥用 async void;事件回调除外。还应使用 CancellationToken 处理对象销毁后的取消。

6.4 Thread

Thread 是操作系统线程,适合:
  • 长期后台服务;
  • 明确的 CPU 并行计算;
  • 需要自主管理线程生命周期的场景。
代价:
  • 创建和切换开销较高;
  • 需要锁、并发容器或其他同步机制;
  • 不能直接在后台线程访问大多数 Unity 对象和 API。

6.5 对比

特性
Coroutine
Task
Thread
默认执行位置
Unity 主线程
主线程上下文、线程池或异步 I/O
独立线程
是否天然并行
可能
适合
延时、逐帧流程、动画序列
I/O、异步流程、后台计算
长时间独立并行工作
Unity API
可以
后台阶段通常不可以
通常不可以
取消方式
StopCoroutine / 状态控制
CancellationToken
自定义协作式取消
Job System + Burst 通常比手写 Thread 更适合 Unity 中大规模数据并行计算。

7. RectTransform、Canvas 与 UGUI

本章从「RectTransform」「Canvas 三种模式」和「Canvas Scaler」等方面说明 RectTransform、Canvas 与 UGUI。

7.1 RectTransform

所有 UGUI 元素使用 RectTransform。它继承自 Transform,并增加二维布局信息。
核心属性:
属性
含义
anchorMin/anchorMax
相对父 Rect 的归一化锚点
pivot
自身旋转、缩放和定位参考点
anchoredPosition
相对锚点参考位置的局部偏移
sizeDelta
相对锚点计算后的尺寸修正
offsetMin/offsetMax
拉伸布局中的边距
rect
当前计算得到的矩形
anchorMin == anchorMax 时,一般是固定锚点定位;不相等时可形成拉伸布局。
transform.position 仍然可以设置 UI 的世界坐标,并非“不能使用”,但屏幕 UI 的布局通常优先操作 anchoredPosition、锚点和布局组件。

7.2 Canvas 三种模式

本节从「Screen Space - Overlay」「Screen Space - Camera」和「World Space」等方面说明 Canvas 三种模式。
7.2.1 Screen Space - Overlay
  • 不需要指定 Camera;
  • UI 直接覆盖在屏幕空间;
  • 常用于 HUD、菜单、暂停界面;
  • 不参与普通场景相机的透视关系。
7.2.2 Screen Space - Camera
  • 指定 Camera;
  • UI 位于相机前方的平面;
  • Plane Distance 控制平面距离;
  • 可结合 Camera 的排序和效果;
  • 与场景对象的遮挡关系取决于相机、深度和排序设置。
7.2.3 World Space
  • Canvas 作为世界中的平面对象;
  • 位置、旋转、缩放与普通场景对象类似;
  • 常用于角色头顶血条、VR 面板、世界内显示屏。

7.3 Canvas Scaler

常用 Scale With Screen Size
  • Reference Resolution:设计参考分辨率;
  • Screen Match Mode:宽高匹配策略;
  • Match:在宽度和高度缩放之间插值。
适配不是只改 Reference Resolution,还要正确设置锚点、布局组和安全区。

7.4 EventSystem 工作流程

简化流程:
常见接口:
UI 点击顺序受 Canvas 排序、Sorting Layer、Order in Layer、渲染顺序、距离和 Raycaster 配置等因素影响。不要依赖未经验证的“固定内部排序公式”,不同渲染模式和版本会有差异。
不需要交互的 Graphic 应关闭 Raycast Target,减少射线检测候选项。

7.5 ScrollRect

典型结构:
常用设置:
  • Horizontal / Vertical;
  • Movement Type:Unrestricted、Elastic、Clamped;
  • Inertia;
  • Deceleration Rate;
  • Scroll Sensitivity;
  • Viewport;
  • Content;
  • Scrollbar。
ScrollRect 本身负责滚动和边界计算,裁剪通常由 Viewport 上的 RectMask2DMask 完成。
大量列表项应使用虚拟列表:
  • 只创建可见区域及少量缓冲项;
  • 滚动时复用单元格;
  • 不为几千条数据实例化几千个 GameObject。

7.6 Canvas Rebuild

UI 变化可能触发两类工作:
  • Layout Rebuild:尺寸、布局、ContentSizeFitter、LayoutGroup 等重新计算;
  • Graphic Rebuild:顶点、文本或材质数据重新生成。
不是“任一变化都必然重建整个 Canvas 的所有对象”,但频繁变化的 UI 确实可能扩大重建范围。
优化原则:
  • 动态与静态 UI 按实际重建热点拆分 Canvas;
  • 避免层层嵌套 LayoutGroup + ContentSizeFitter;
  • 批量修改后再统一刷新;
  • 控制频繁变化的文本;
  • 使用 Profiler 的 UI 模块验证,而不是无条件增加子 Canvas。

8. UI 框架设计

一个完整 UI 框架通常包含:
模块
责任
页面管理
打开、关闭、切换、缓存、返回栈
层级管理
HUD、普通页面、弹窗、提示、引导层
生命周期
Create、Open、Show、Hide、Close、Destroy
输入与事件
点击、返回键、遮罩、焦点
数据传递
页面参数、Presenter/ViewModel、事件绑定
资源管理
异步加载 Prefab、引用计数、释放
动画
打开、关闭、转场和可中断动画
异常保护
重复打开、防连点、加载失败、页面销毁竞态
一种基础结构:
页面基类示例:
应避免把 UIManager 做成“知道所有业务的超级类”。UIManager 负责页面基础设施,具体业务放在 Presenter、Controller、ViewModel 或业务系统中。
常见架构选择:
  • 小型项目:View + 页面脚本;
  • 中型项目:MVP / MVC + 事件;
  • 数据驱动 UI:MVVM / 响应式绑定;
  • 大型项目:页面路由、异步资源、分层事件和生命周期管理。

9. 序列化、ScriptableObject 与存档

本章从「Unity 序列化与通用存档序列化不是同一概念」「[Serializable] 与 [SerializeField]」和「PlayerPrefs」等方面说明序列化、ScriptableObject 与存档。

9.1 Unity 序列化与通用存档序列化不是同一概念

Unity 序列化主要用于:
  • Scene;
  • Prefab;
  • ScriptableObject;
  • Inspector 字段;
  • 编辑器资产与运行时对象状态重建。
存档序列化则是把运行时数据转换为 JSON、二进制等格式,写入用户存储。
两者相关,但不能混为一谈。

9.2 [Serializable][SerializeField]

本节从「[Serializable]」「[SerializeField]」和「[SerializeReference]」等方面说明[Serializable] 与 [SerializeField]。
9.2.1 [Serializable]
标记自定义 class 或 struct,使其可作为嵌套数据被 Unity 序列化。
9.2.2 [SerializeField]
让非 public 字段参与 Unity 序列化并显示在 Inspector:
Unity 常规序列化字段通常要求:
  • 非 static;
  • 非 const;
  • 非 readonly;
  • public 或带 [SerializeField]
  • 类型属于 Unity 支持的序列化类型。
属性默认不会像字段一样参与 Unity 序列化。
9.2.3 [SerializeReference]
适合:
  • 多态数据;
  • 共享托管引用;
  • 接口或抽象基类的具体实例。
但它会增加数据和管理复杂度。

9.3 PlayerPrefs

PlayerPrefs 只能直接保存:
  • int
  • float
  • string
适合:
  • 音量;
  • 画质档位;
  • 语言;
  • 是否看过教程。
不适合:
  • 复杂角色存档;
  • 大型背包;
  • 敏感数据;
  • 需要防篡改的数据。
PlayerPrefs 在本地未加密,不应把“二进制”或“PlayerPrefs”理解为安全机制。
不要高频调用 Save(),磁盘写入可能造成卡顿。

9.4 ScriptableObject

ScriptableObject 是可序列化的 Unity 资产类型,适合:
  • 武器、技能和角色配置;
  • 关卡元数据;
  • 对话内容;
  • 共享只读配置;
  • 编辑器工具状态;
  • 数据驱动事件或策略资产。
关键点:
  • 它不是 MonoBehaviour,不能挂在 GameObject 上;
  • 多个对象可引用同一份资产,减少重复配置;
  • ScriptableObject 并非天生不可变;
  • Standalone Player 运行时对资产实例的修改只存在于内存,不会自动写回项目资产;
  • 玩家存档仍应写入 persistentDataPath 等可写位置。

9.5 JSON、XML 与二进制

格式
优点
缺点
常见用途
JSON
可读、易调试、跨平台
体积较大,类型能力有限
存档、配置、接口
XML
结构和验证能力强
冗长
老系统、工具链
自定义二进制 / Protobuf / MessagePack
紧凑、解析快、可定义版本
调试不直观,版本迁移更复杂
大型存档、网络协议
注意:
  • 二进制只是“不方便直接阅读”,不等于加密或安全;
  • JsonUtility 简单高效,但对 Dictionary、多态和部分复杂类型支持有限;
  • 需要复杂 JSON 时可使用 Newtonsoft.Json 等方案,并结合平台裁剪测试。

9.6 存档框架分层

推荐组件:
  1. SaveData:纯数据结构;
  1. Serializer:JSON 或二进制转换;
  1. Storage:文件读写;
  1. SaveManager:协调保存和加载;
  1. Version Migration:旧存档升级;
  1. Validation:校验字段和版本;
  1. Backup:备份与损坏恢复。

9.7 文件路径与原子写入

可写存档一般放在:
安全写入流程:
  1. 写入临时文件;
  1. Flush 并关闭;
  1. 校验成功;
  1. 替换正式文件;
  1. 保留一份备份。

9.8 保存时机

常见策略:
  • 关键节点保存;
  • 暂停、切场景、进入后台时保存;
  • 脏标记 + 防抖;
  • 定时保存;
  • 手动存档槽。
不要每次金币变化都立即写磁盘,也不要只依赖退出时保存。

9.9 内存与持久化存储

  • RAM:运行时工作区,速度快,断电丢失;
  • 持久化存储:SSD、闪存等,速度较慢,重启后仍存在;
  • 序列化:内存对象 → 可存储格式;
  • 反序列化:存储格式 → 内存对象。

10. 资源管理、AssetBundle 与热更新

本章从「资源生命周期」「Direct Reference」和「Resources」等方面说明资源管理、AssetBundle 与热更新。

10.1 资源生命周期

资源管理不只是“加载”,还包括:
常见问题:
  • 重复加载同一资源;
  • 依赖资源未释放;
  • 异步加载完成时调用者已销毁;
  • AssetBundle 卸载时机错误;
  • Addressables Handle 未 Release;
  • 运行时创建的 Texture、Mesh、Material 未 Destroy。

10.2 Direct Reference

把 Prefab、Sprite 等直接拖到序列化字段中:
优点:
  • 类型安全;
  • 依赖关系明确;
  • 不用字符串路径;
  • 适合固定内容。
缺点是内容通常随 Player 构建发布,不适合独立热更新。

10.3 Resources

Resources 系统会在构建时收集所有 Resources 文件夹内的资产,并允许:
限制:
  • 文件夹中的资源即使未被引用,也会进入 Player;
  • 大量资源会增加构建、启动索引和内存管理成本;
  • 不支持远程内容;
  • 修改内容通常要求重新构建 Player;
  • 路径是字符串,错误只能运行时发现。
适合原型、小项目或少量启动必需内容,不适合大型热更新资源体系。

10.4 StreamingAssets

Assets/StreamingAssets 中的文件会基本按原样复制到 Player,运行时通过:Application.streamingAssetsPath 访问。
特点:
  • Player 运行时通常只读;
  • 适合 JSON、数据库、视频、插件数据、预置 AssetBundle;
  • Android 和 Web 平台常返回 URL,需使用 UnityWebRequest;
  • 不要把 .prefab.asset.unity 直接当普通文件放进去并期待运行时加载。
StreamingAssets 不是安全存储,也不会自动加密。

10.5 persistentDataPath

Application.persistentDataPath 是运行时可写目录,适合:
  • 存档;
  • 下载后的远程资源;
  • 缓存;
  • 用户生成内容;
  • 热更新版本清单。

10.6 AssetBundle

AssetBundle 可将资源及依赖构建为平台相关包,在运行时加载。
分组原则:
  • 按生命周期:同时加载和同时卸载的资源放在一起;
  • 按更新频率:高频更新与稳定资源分开;
  • 公共依赖单独打包,避免重复;
  • 避免过大包导致一次加载过多;
  • 避免过多极小包导致请求、文件句柄和管理开销;
  • 不同目标平台的 AssetBundle 通常不能混用。
压缩:
  • 默认构建通常使用 LZMA;
  • ChunkBasedCompression 使用 LZ4,适合运行时随机访问和本地加载;
  • UncompressedAssetBundle 才是无压缩;
  • 选择应基于下载体积、加载耗时和存储空间测试。
卸载:
引用仍在使用时调用 Unload(true) 会造成资源丢失或对象异常。

10.7 Addressables

Addressables 建立在 AssetBundle 等底层能力之上,提供:
  • 地址定位;
  • 依赖计算;
  • 异步加载;
  • 本地与远程组;
  • Catalog;
  • 引用计数;
  • 内容更新流程。

10.8 热更新

本节从「资源热更新」和「代码热更新」两方面说明热更新。
10.8.1 资源热更新
更新:
  • Prefab;
  • 纹理;
  • 音频;
  • 配置;
  • 场景;
  • AssetBundle / Addressables 内容。
基本流程:
10.8.2 代码热更新
代码热更新受平台和 AOT 限制,特别是 iOS。必须同时考虑:
  • 商店政策;
  • IL2CPP;
  • 元数据和泛型补充;
  • 安全审计;
  • 崩溃回滚;
  • 版本兼容。
资源热更新和代码热更新不是同一问题。

10.9 运行时资源释放

  • Destroy(instance):销毁实例;
  • Destroy(material/texture/mesh):释放运行时创建的 UnityEngine.Object;
  • Resources.UnloadUnusedAssets():扫描并卸载未引用资源,可能较重,应在可控过渡阶段使用;
  • Addressables:Release Handle;
  • AssetBundle:按生命周期 Unload;
  • NativeArray、ComputeBuffer 等:按 API 调用 Dispose / Release。

11. 架构与常用设计模式

本章从「分层思路」「组件模式」和「观察者模式」等方面说明架构与常用设计模式。

11.1 分层思路

一种常见分层:
分层是为了控制依赖方向和职责。

11.2 组件模式

Unity 最基础的模式:
每个组件负责单一功能,通过接口或事件协作。

11.3 观察者模式

C# 常用 event + Action
订阅者:
优点是降低发布者对订阅者的直接依赖。风险是忘记退订、事件链难追踪和过度全局化。

11.4 UnityEvent、委托与 event

类型
特点
Action / Delegate
纯代码回调,开销低,类型明确
event Action
外部只能订阅和退订,只有声明类可触发
UnityEvent
可序列化,可在 Inspector 绑定,适合设计配置
UnityEvent 适合低频交互和编辑器配置;高频热路径优先普通 C# 委托并通过 Profiler 验证。

11.5 状态模式 / FSM

适合:
  • 角色 Idle、Run、Jump、Attack;
  • 敌人 Patrol、Chase、Attack、Dead;
  • 游戏 Menu、Playing、Paused、GameOver。
状态应封装进入、更新、退出:
不要让任意状态直接知道所有其他状态,转移条件应集中管理或通过状态机协调。

11.6 AI:FSM 与行为树

本节从「FSM」和「行为树」两方面说明 AI:FSM 与行为树。
11.6.1 FSM
适合状态数量较少、转换清晰的 AI。
11.6.2 行为树
常见节点:
  • Selector;
  • Sequence;
  • Parallel;
  • Condition;
  • Action;
  • Decorator。
行为树便于复用和组合复杂行为,但仍需处理:
  • 中断;
  • 节点状态;
  • 黑板数据;
  • Tick 频率;
  • 动画和寻路异步完成。
游戏 AI 通常还包括:
  • 感知;
  • 决策;
  • NavMesh 寻路;
  • 路径跟随;
  • 动画同步;
  • 群体行为。

11.7 对象池

用于频繁生成和回收的对象:
  • 子弹;
  • 特效;
  • 飘字;
  • 敌人;
  • ScrollRect 单元格。
对象池减少 Instantiate/Destroy、原生对象创建和托管分配,但对象重置必须完整:
Unity 提供 UnityEngine.Pool.ObjectPool<T>,可优先使用标准实现。

11.8 单例模式

单例提供唯一实例和全局访问点,但也会:
  • 隐藏依赖;
  • 增加测试难度;
  • 制造初始化顺序问题;
  • 形成“超级管理器”。
真正全局且唯一的系统才考虑单例,如应用级音频或存档协调器。可替代方案:
  • 构造/字段注入;
  • 场景上下文;
  • Service Locator;
  • ScriptableObject 服务引用;
  • 显式传参。
不要默认把每个 Manager 都做成单例。

11.9 策略模式

把可替换算法抽象为接口:
适合:
  • 伤害公式;
  • 寻路策略;
  • AI 决策;
  • 移动方式;
  • 排序和目标选择。

11.10 MVC、MVP 与 MVVM

本节从「MVC」「MVP」和「MVVM」等方面说明 MVC、MVP 与 MVVM。
11.10.1 MVC
  • Model:数据和业务规则;
  • View:显示;
  • Controller:处理输入并协调 Model 与 View。
Controller 并不是自动“保证同步”的魔法,仍需事件、通知或绑定机制。
11.10.2 MVP
  • View 暴露接口;
  • Presenter 处理表现逻辑;
  • 适合 UGUI 页面测试和解耦。
11.10.3 MVVM
  • ViewModel 暴露可观察状态和命令;
  • View 通过绑定更新;
  • 适合响应式 UI,但需要绑定框架和生命周期管理。

11.11 ECS

ECS:
  • Entity:标识;
  • Component:数据;
  • System:处理具有特定组件组合的实体。
Unity Entities 通常按 Archetype Chunk 组织数据,以提高批量遍历和缓存局部性。ECS 适合大量同构实体和数据并行,不代表所有项目都应改写为 ECS。

12. 渲染、合批与图形优化

本章从「Render Queue」「Draw Call、Batch 与 SetPass」和「合批方案」等方面说明渲染、合批与图形优化。

12.1 Render Queue

常见队列:
队列
典型值
用途
Background
1000
背景
Geometry
2000
不透明几何
AlphaTest
2450
裁剪透明
Transparent
3000
半透明
Overlay
4000
最后覆盖
通常:
  • 不透明物体倾向于前到后排序,利用深度测试减少过度绘制;
  • 半透明物体通常需要后到前排序以正确混合;
  • 透明材质常关闭 ZWrite,但具体取决于 Shader;
  • UI 的最终顺序还受 Canvas 和 UGUI 规则控制,不应简单等同于普通 3D Transparent 队列。
不是 GPU 自己“遍历场景并决定所有桶”,而是 Unity 的渲染系统在 CPU 侧生成、排序并提交渲染命令,GPU 执行命令。

12.2 Draw Call、Batch 与 SetPass

  • Draw Call:CPU 向图形 API 提交一次绘制;
  • Batch:Unity 统计的绘制批次;
  • SetPass Call:切换 Shader Pass、状态或材质相关 GPU 状态。
减少 Draw Call 的主要目的通常是降低 CPU 提交开销,但如果项目 GPU 已经是瓶颈,只减少 Draw Call 未必改善帧率。

12.3 合批方案

本节从「Static Batching」「Dynamic Batching」和「GPU Instancing」等方面说明合批方案。
12.3.1 Static Batching
  • 适合不移动的静态网格;
  • 减少绘制提交;
  • 可能增加内存,因为合批数据需要额外存储。
12.3.2 Dynamic Batching
  • 仅适用于满足特定限制的小网格和 Shader;
  • 限制随渲染管线和平台变化;
  • 现代硬件上 CPU 合并成本可能得不偿失;
  • 不能死记固定“300 顶点”规则,应查看当前版本文档和 Profiler。
12.3.3 GPU Instancing
适合大量:
  • 相同 Mesh;
  • 相同 Material / Shader 变体;
  • 仅实例属性不同的对象。
CPU 提交实例数据,GPU 通过 Instance ID 读取每个实例的矩阵和属性。仍可能因可见性、批次上限、材质差异和阴影 Pass 拆成多次绘制。
12.3.4 SRP Batcher
SRP Batcher 用于兼容 Shader 的 SRP 项目,主要减少 CPU 端在不同材质之间设置 Shader 常量的成本。
关键区别:
  • GPU Instancing:一次绘制多个相同 Mesh 实例;
  • SRP Batcher:优化兼容 Shader/材质之间的状态切换;
  • SRP Batcher 不等于把所有对象合成一个 Draw Call。

12.4 UGUI 合批

稳定理解即可:
  • Canvas 是 UI 几何收集和重建的重要边界;
  • 相邻绘制元素需具有兼容的材质、纹理、裁剪与渲染状态;
  • 中间插入不同材质、Mask、特殊 Shader 等会打断批次;
  • 不同 Canvas 通常不能相互合批;
  • Hierarchy 顺序影响 UI 绘制顺序;
  • 图集可减少纹理切换。
渲染排序的内部实现可能随 Unity 版本和渲染管线变化,不应依赖固定的材质 ID、纹理 ID 或纹理尺寸顺序。

12.5 Sprite Atlas

图集将多张 Sprite 打包进较少的纹理:
优点:
  • 减少纹理切换;
  • 提高 UGUI / SpriteRenderer 合批机会;
  • 集中管理平台压缩和尺寸。
限制:
  • 不保证“十张图必然只产生一次 Draw Call”;
  • 材质、裁剪、Canvas、层级和 Shader 仍可能拆批;
  • 图集过大可能增加加载和内存常驻;
  • 应按使用场景和生命周期分图集。
UV 是网格顶点在纹理上的采样坐标,通常归一化到 0~1。

12.6 Mipmap

Mipmap 是同一纹理的多级缩小版本。
优点:
  • 减少远处纹理闪烁和锯齿;
  • 改善纹理缓存和采样效率。
代价:
  • 完整 2D Mipmap 链通常额外占用约三分之一纹理存储;
  • UI、像素画或永远按原尺寸显示的纹理未必需要;
  • 3D 场景纹理一般应保留。

12.7 Lightmap

Lightmap 把静态或混合照明结果烘焙到纹理,减少运行时光照计算。
优点:
  • 可获得间接光和稳定阴影;
  • 大幅减少实时光照开销。
代价:
  • 占用纹理内存;
  • 烘焙时间长;
  • 动态物体仍需 Light Probe、实时灯光或其他方案;
  • 场景变化后需重新烘焙。

12.8 高低模与 LOD

典型流程:
LOD 应控制:
  • 三角形数;
  • Shader 复杂度;
  • 阴影;
  • 屏幕占比;
  • 切换突变。

12.9 纹理压缩

应根据目标平台选择:
  • Desktop:BC 系列;
  • Android:ASTC 或 ETC2;
  • iOS:ASTC,旧设备可能涉及 PVRTC;
  • Web:根据目标浏览器和构建配置测试。
压缩影响:
  • 包体;
  • 显存;
  • 带宽;
  • 解码支持;
  • 画质。
不能只看安装包大小,应在真机检查运行时格式是否发生解压回退。

12.10 Shader 简化

常见方向:
  • 减少不必要纹理采样;
  • 控制透明和 Overdraw;
  • 避免昂贵分支和复杂数学;
  • 使用合适精度;
  • 减少 Shader Variant;
  • 使用适合移动端的光照模型;
  • 通过 Frame Debugger 和 GPU Profiler 验证。

13. Profiler

本章围绕「先定位瓶颈」展开,先明确核心概念与适用边界。

13.1 先定位瓶颈

常用工具:
工具
用途
Profiler
CPU、GPU、Rendering、Memory、Physics、UI
Frame Debugger
逐条查看渲染事件、批次和材质切换
Memory Profiler Package
内存快照、资源引用和泄漏分析
Physics Debugger
碰撞器和物理场景可视化
RenderDoc / 平台 GPU 工具
深入 GPU 帧分析
Profile Analyzer
多帧数据对比与统计
优化顺序:

14. 帧率 —— FPS、Frame Time 与 Frame Pacing

FPS 是单位时间呈现的帧数,Frame Time 是每帧耗时。
理论预算:
目标帧率
每帧预算
30 FPS
33.33 ms
60 FPS
16.67 ms
90 FPS
11.11 ms
120 FPS
8.33 ms
高平均 FPS 不等于流畅。若帧时间序列为:
用户仍会明显感到卡顿。应关注:
  • 平均值;
  • P95 / P99;
  • 最大帧时间;
  • CPU 主线程;
  • Render Thread;
  • GPU 时间;
  • Present / VSync;
  • Frame Pacing 稳定性。
Time.deltaTime 是上一帧的时间增量,不应机械理解为始终精确等于 1 / 显示 FPS
40 FPS 与 30 FPS 画面对比
40 FPS 与 30 FPS 画面对比
图 1:40 FPS 与 30 FPS 的画面示例
40 FPS 下的微卡顿示意
40 FPS 下的微卡顿示意
图 2:40 FPS 下的微卡顿示意
33 ms 帧时长与显示节奏示意
33 ms 帧时长与显示节奏示意
图 3:33 ms 帧时长与显示节奏示意

15. 性能优化

本章从「CPU 优化」「物理优化」和「GPU 优化」等方面说明性能优化。

15.1 CPU 优化

常见方向:
  • 避免 Update 中无意义轮询;
  • 将低频逻辑降低更新频率;
  • 缓存组件引用;
  • 避免频繁 Find、字符串路径和反射;
  • 使用对象池;
  • 批量处理;
  • 对大量同构数据使用 Job System + Burst;
  • 减少主线程同步等待;
  • 异步加载资源和场景。
使用协程不会降低 CPU 总计算量,只是改变执行分布。

15.2 物理优化

  • 使用基础 Collider;
  • 简化 MeshCollider;
  • 调整 Layer Collision Matrix;
  • 降低不必要的射线和 Overlap 查询;
  • 使用 NonAlloc API 时正确管理缓冲区;
  • 控制动态刚体和关节数量;
  • 调整 Fixed Timestep 前先验证游戏手感和稳定性;
  • 避免移动大量静态 Collider;
  • 对休眠刚体和 Collision Detection 做合理配置。

15.3 GPU 优化

  • 降低分辨率或使用动态分辨率;
  • 减少透明 Overdraw;
  • 控制实时灯光和阴影;
  • 使用 LOD 和 Occlusion Culling;
  • 压缩纹理;
  • 简化 Shader;
  • 控制后处理;
  • 合理使用 Instancing、SRP Batcher;
  • 降低粒子屏幕覆盖面积;
  • 在移动端关注填充率和带宽。

15.4 UI 优化

  • 动静分离,但不要过度切 Canvas;
  • 关闭无用 Raycast Target;
  • 使用 Sprite Atlas;
  • 控制 Layout Rebuild;
  • 复用列表项;
  • 避免大面积透明层叠;
  • RectMask2D 通常比需要模板测试的复杂 Mask 更直接,但仍应实测;
  • 频繁文本更新应关注字体图集、网格重建和格式化分配。
“把静态文本改成图片”不是通用最佳实践,会牺牲本地化、清晰度、可访问性和内存,应按场景评估。

15.5 资源内存

  • 控制纹理 Max Size;
  • 检查运行时纹理格式;
  • 音频按长度选择 Decompress On Load、Compressed In Memory 或 Streaming;
  • 场景切换后确认旧资源引用已断开;
  • Addressables 成对 Release;
  • 不要在每帧调用 UnloadUnusedAssets
  • 运行时创建 Material 时警惕实例化和泄漏;
  • 用内存快照比较前后差异。

16. Unity GC

Unity 使用 Boehm-Demers-Weiser GC。现代 Unity 默认可使用增量 GC:
  • 将标记工作分散到多帧;
  • 减少单次长暂停;
  • 不会减少 GC 的总工作量;
  • 引用变化多时写屏障会有额外成本;
  • 极端情况下可能回退到完整收集。
注意:
  • Unity 的 GC 是非压缩式;
  • 不应把现代 .NET 的 Gen 0 / Gen 1 / Gen 2 搬迁式模型直接套到 Unity;
  • Unity 使用 Boehm-Demers-Weiser GC,不采用标准 .NET 的分代移动式回收模型。
减少 GC 的原则:
  • 热路径避免临时分配;
  • 复用 List、数组和对象;
  • 避免每帧 LINQ、字符串拼接、闭包和装箱;
  • 使用对象池;
  • 使用 StringBuilder 处理大量拼接;
  • 预估集合容量;
  • 用 Profiler 的 GC.Alloc 找到真实分配点。
不要把 struct 当成“无脑零分配”。结构体放在哪里取决于上下文,大结构体复制、装箱和集合使用也可能产生额外成本。

17. 联网同步与传输层基础

本章从「状态同步」「帧同步」和「TCP 与 UDP」等方面说明联网同步与传输层基础。

17.1 状态同步

服务器计算权威状态,客户端接收快照或增量:
优点:
  • 服务器权威;
  • 反作弊和一致性控制较直接;
  • 客户端逻辑不必完全确定性。
挑战:
  • 状态量大;
  • 需要快照压缩;
  • 延迟下需要插值、客户端预测和服务器纠正;
  • 大量实体需兴趣管理。

17.2 帧同步

主要同步输入,每个模拟端执行相同逻辑:
要求:
  • 确定性数学;
  • 固定逻辑步长;
  • 一致随机种子与调用顺序;
  • 容器遍历顺序可控;
  • 浮点差异处理;
  • 丢包、迟到输入和断线策略;
  • 周期性哈希校验。
优点:
  • 输入数据量小;
  • 回放可只记录输入和种子;
  • 大量单位场景可能更有优势。
挑战:
  • 确定性要求高;
  • 传统锁步容易被最慢玩家拖住;
  • 客户端包含核心模拟,反作弊更复杂;
  • 现代动作游戏常结合输入延迟、预测和 Rollback,而不是纯等待锁步。
同步模型不决定传输协议。状态同步可以用 UDP,锁步也可以使用可靠 UDP、TCP、QUIC 或自定义传输层,需根据可靠性、顺序性和延迟需求选择。

17.3 TCP 与 UDP

特性
TCP
UDP
连接
面向连接
无连接
数据形式
字节流
数据报
可靠性
确认、重传、排序、流控、拥塞控制
应用层自行保证
消息边界
不保留
保留
队头阻塞
协议本身没有
常见用途
登录、聊天、文件、低频可靠消息
实时状态、语音、快速输入
UDP 不是自动“更快且适合所有实时游戏”。使用 UDP 往往还要自己处理:
  • 序列号;
  • ACK;
  • 重传;
  • 分片;
  • 拥塞控制;
  • 加密;
  • NAT;
  • 超时和乱序。

17.4 TCP 三次握手

  1. Client → Server:SYN,携带客户端初始序列号;
  1. Server → Client:SYN + ACK,确认客户端并提供服务端初始序列号;
  1. Client → Server:ACK。
它用于同步双方序列号并确认连接建立能力。SYN Flood 利用服务端半连接资源,可通过 SYN Cookie、限速、防火墙等缓解。

17.5 TCP 四次挥手

TCP 是全双工,两个方向分别关闭:
  1. 主动方发送 FIN;
  1. 被动方 ACK;
  1. 被动方发送自己的 FIN;
  1. 主动方 ACK,并进入 TIME_WAIT。
TIME_WAIT 用于:
  • 让最后 ACK 有机会重传;
  • 避免旧连接中的延迟报文污染新连接。
具体系统参数和保活时间因操作系统而异,不应死记“2 小时、75 分钟、10 次”作为所有平台固定值。

17.6 TCP 可靠性机制

  • 校验和;
  • 序列号;
  • 累积确认;
  • 超时重传;
  • 快速重传;
  • 滑动窗口;
  • 接收端流量控制;
  • 拥塞控制;
  • 乱序重组;
  • 重复报文丢弃。
发送窗口受接收窗口和拥塞窗口共同限制。
TCP 滑动窗口示意图
TCP 滑动窗口示意图
TCP 滑动窗口示意图

18. C# 运行时补充

本章从「托管代码与非托管资源」「IDisposable 与 using」和「unsafe」等方面说明 C# 运行时补充。

18.1 托管代码与非托管资源

托管内存由 GC 管理,但 GC 只能按可达性回收托管对象,不能保证及时释放:
  • 文件句柄;
  • Socket;
  • 数据库连接;
  • 原生内存;
  • ComputeBuffer;
  • NativeArray;
  • Unity 原生引擎资源。
因此需要确定性释放。

18.2 IDisposable 与 using

离开作用域时会调用 Dispose(),即使抛出异常也能执行。
类长期持有资源时:
Unity 特有注意:
  • Texture2DMeshMaterial 等通常不实现 IDisposable;
  • 运行时创建的 UnityEngine.Object 用 Destroy
  • NativeArrayDispose
  • ComputeBuffer / GraphicsBuffer 按 API Release 或 Dispose;
  • OnDestroy 中释放对象拥有的资源。

18.3 unsafe

unsafe 允许指针和指针运算,但:
  • 不是使用 IDisposable 的前提;
  • 不是访问非托管资源的唯一方法;
  • 会绕过部分类型和内存安全;
  • 应限制在性能已验证且边界明确的底层代码。

18.4 Dictionary

Dictionary<TKey, TValue> 基于哈希表:
平均查找、插入和删除为 O(1),最坏情况可能退化。
注意:
  • Key 的 EqualsGetHashCode 必须一致;
  • Key 在加入字典后,不应改变参与哈希计算的字段;
  • 预估数量时设置初始容量;
  • Dictionary 不保证线程安全;
  • 并发写使用锁或 ConcurrentDictionary;
  • 不要依赖具体运行时中的固定负载因子、是否使用质数、插入链表头尾等实现细节,这些可能随 .NET / Unity 运行时版本变化。

18.5 Delegate、Action 与 event

区别:
  • Delegate / Action 持有者可重新赋值和 Invoke;
  • event 外部只能 +==,只能由声明它的类型触发;
  • event 更适合发布订阅封装。
事件泄漏通常不是“内存无法被 GC 识别”,而是发布者仍持有订阅者委托引用,使订阅者仍然可达。生命周期不一致时必须退订。

19. 项目结构、动画、射线与常用系统

本章从「Unity 结构划分」「Prefab 与场景实例」和「射线检测」等方面说明项目结构、动画、射线与常用系统。

19.1 Unity 结构划分

游戏程序通常由数据、规则、输入、画面、声音和资源共同组成。游戏引擎把渲染、物理、动画、音频、输入、资源导入和编辑器等常用能力整合在一起,使开发者可以把主要精力放在游戏逻辑上。
Unity 项目可以先理解成下面这条结构:
  • Scene:一个游戏场景,例如主菜单、关卡或结算界面。
  • GameObject:场景中的对象,本身更像组件的容器。
  • Component:附加在 GameObject 上的功能,例如 Transform、Collider、Rigidbody、Camera 和自定义脚本。
  • Prefab:可重复实例化的对象模板。
  • Asset:模型、贴图、音频、材质、动画和脚本等项目资源。
Unity 的核心设计思想是组合优先。玩家并不是通过一个巨大类完成所有能力,而是由移动、生命值、攻击、动画和音效等多个组件共同组成。

19.2 Prefab 与场景实例

Prefab 是 Project 面板中的资源模板,场景实例是 Hierarchy 中已经生成的对象。
常见错误是:脚本需要一个 Prefab,却从 Hierarchy 拖入了运行时会被销毁的场景实例。场景切换或对象销毁后,保存的引用就会失效。
判断方式:
  • 需要“创建新对象”时,通常引用 Project 中的 Prefab。
  • 需要“控制场景里现有对象”时,才引用 Hierarchy 中的实例。
  • 跨场景长期存在的管理器,需要明确处理生命周期,而不是随意依赖某个场景对象。

19.3 射线检测

摄像机点击检测:
使用 LayerMask 可以减少无意义检测,并避免把地面、UI、玩家和交互物混在一起判断。

19.4 鼠标点击移动

注意:点击移动失败时,要检查地面是否参与 NavMesh、角色是否真正位于可行走区域、Agent 半径和高度是否合理。

19.5 可交互物品

可以先定义统一接口:
拾取物实现:
这样玩家、鼠标射线或 VR 控制器只需要寻找 IInteractable,不必为每种物品写一套判断。

19.6 Animator 的基本组成

常见概念:
  • Animation Clip:单个动画片段。
  • Animator Controller:动画状态和转换关系。
  • Animator:运行控制器的组件。
  • Parameter:控制状态切换的参数。
  • Blend Tree:根据速度或方向混合多个动画。
  • Avatar:角色骨骼映射。
移动参数示例:

19.7 Root Motion

开启 Root Motion 后,角色位移可以由动画本身驱动。关闭时,位移由脚本、CharacterController 或 Rigidbody 驱动。
选择原则:
  • 强调动作位移真实性,例如翻滚、攀爬、处决,可以考虑 Root Motion。
  • 普通角色移动、网络同步和精确控制,通常让代码负责位移更容易管理。

19.8 NavMesh 与 NPC 跟随

常见问题:
  • NPC 原地抖动:停止距离过小,目标点持续变化。
  • 角色不移动:不在 NavMesh 上,Agent 被停止,速度为 0,或目标不可达。
  • 跟随路径奇怪:NavMesh 烘焙区域、障碍物或 Agent 参数不合理。
  • 动画脚滑:Agent 速度与 Animator 参数没有同步。

19.9 场景切换

场景名必须加入 Build Settings。大型场景可以使用异步加载:
跨场景对象不要随意全部设为 DontDestroyOnLoad,否则重复进入场景时可能产生多个管理器。

19.10 音频

常见组件:
  • AudioClip:音频资源。
  • AudioSource:播放声音。
  • AudioListener:接收声音,通常挂在主摄像机上。
  • AudioMixer:统一管理音量分组和效果。
PlayOneShot 空引用通常不是 API 本身的问题,而是 AudioSource 或 AudioClip 没有赋值。

19.11 粒子系统

粒子适合命中特效、魔法、烟雾和环境效果。优化时注意:
  • 最大粒子数;
  • 生命周期;
  • 发射频率;
  • 透明 Overdraw;
  • 碰撞模块;
  • 是否需要在屏幕外继续模拟;
  • 是否可以对象池复用。

19.12 对话与任务

对话系统至少包含:
不要直接把所有台词写在 NPC 脚本的 if 中。可以使用 ScriptableObject、JSON 或专门的对话数据结构。

19.13 背包

基础数据结构:
系统职责应拆开:
  • Inventory:保存物品数据。
  • Pickup:处理场景拾取。
  • InventoryView:刷新 UI。
  • ItemUseService:执行物品效果。
  • SaveManager:持久化。
避免让每个格子直接修改玩家属性、场景物体和存档文件。

20. 总结

Unity 开发的主线不是记忆 API,而是理解对象、资源与状态的生命周期。输入、物理、UI、渲染、网络和存档最终都要落实到明确的更新时机、所有权和释放策略。
遇到性能或一致性问题时,应先用 Profiler 和可复现步骤定位瓶颈,再决定是否调整架构、资源加载、批处理或 GC 分配。

21. 参考资料