这篇总览可以按四组主题阅读:
- Unity 基础、生命周期与交互。
- Unity UI、存档与资源管理。
- Unity 架构、渲染与性能。
- Unity 网络、运行时与项目系统。
先理解对象生命周期、坐标空间、物理更新、资源生命周期和性能分析,再学习具体框架封装。
1. GameObject、Component 与 MonoBehaviour
本章从「GameObject 与 gameObject」和「MonoBehaviour」两方面说明 GameObject、Component 与 MonoBehaviour。
1.1 GameObject 与 gameObject
写法 | 含义 |
GameObject | UnityEngine 中的类,表示场景对象和组件容器 |
gameObject | Component 提供的属性,返回当前组件所在的 GameObject |
Transform | 变换组件类型 |
transform | Component 提供的快捷属性,返回当前对象的 Transform |
GameObject 本身通常只负责:
- 名称、Tag、Layer、激活状态;
- 容纳 Component;
- 建立父子层级。
对象的渲染、碰撞、音频和脚本行为均由组件提供。Unity 的核心设计思想是组合优于继承:给 GameObject 组合不同组件,而不是建立很深的继承树。
1.2 MonoBehaviour
继承
MonoBehaviour 后,脚本才能作为组件挂载到 GameObject,并接收 Unity 的生命周期回调。常用成员:
注意:
- 生命周期函数由 Unity 按名称调用,不建议手动调用;
- 生命周期函数通常可以写成
private;
- 不要把构造函数当作 Unity 组件初始化入口,应使用
Awake、OnEnable或Start。
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 OnDisable 与 OnDestroy
OnDisable适合取消事件订阅、停用输入;
OnDestroy适合释放该对象拥有的资源;
- 不要把关键存档只放在
OnApplicationQuit或OnDestroy,移动端进程可能被系统直接终止。
2.2 enabled、activeSelf 与 activeInHierarchy
本节从「enabled」「SetActive」和「三个状态的区别」等方面说明 enabled、activeSelf 与 activeInHierarchy。
2.2.1 enabled
只控制当前
Behaviour 组件。禁用后:
Update、FixedUpdate、LateUpdate、OnGUI等常规回调停止;
- 状态切换会触发
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°与1°等价;
- 同一个旋转有多组等价欧拉角;
- 逐帧读取、修改并写回
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 叉积
三维叉积:
结果是垂直于
a、b 所在平面的向量,方向遵循右手定则,模长等于平行四边形面积:用途:
- 求法线;
- 判断旋转方向;
- 计算面积。
二维通常使用标量叉积:
- 大于 0:从
a到b为逆时针方向;
- 小于 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/Rigidbody 与 Collider2D/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
基本流程:
- 创建 Input Actions Asset;
- 建立 Action Map;
- 添加 Action;
- 设置 Action Type 和 Control Type;
- 绑定键盘、鼠标、手柄或 XR 输入;
- 在代码中启用、读取或订阅回调。
推荐使用
InputActionReference,减少字符串查找:两种使用方式:
- 轮询:
ReadValue<T>()、WasPressedThisFrame();
- 事件:
started、performed、canceled。
应避免每帧通过字符串反复查找 Action。
6.2 协程
协程基于
IEnumerator 与 yield,由 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 上的
RectMask2D 或 Mask 完成。大量列表项应使用虚拟列表:
- 只创建可见区域及少量缓冲项;
- 滚动时复用单元格;
- 不为几千条数据实例化几千个 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 存档框架分层
推荐组件:
- SaveData:纯数据结构;
- Serializer:JSON 或二进制转换;
- Storage:文件读写;
- SaveManager:协调保存和加载;
- Version Migration:旧存档升级;
- Validation:校验字段和版本;
- Backup:备份与损坏恢复。
9.7 文件路径与原子写入
可写存档一般放在:
安全写入流程:
- 写入临时文件;
- Flush 并关闭;
- 校验成功;
- 替换正式文件;
- 保留一份备份。
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。图 1:40 FPS 与 30 FPS 的画面示例
图 2:40 FPS 下的微卡顿示意
图 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 三次握手
- Client → Server:SYN,携带客户端初始序列号;
- Server → Client:SYN + ACK,确认客户端并提供服务端初始序列号;
- Client → Server:ACK。
它用于同步双方序列号并确认连接建立能力。SYN Flood 利用服务端半连接资源,可通过 SYN Cookie、限速、防火墙等缓解。
17.5 TCP 四次挥手
TCP 是全双工,两个方向分别关闭:
- 主动方发送 FIN;
- 被动方 ACK;
- 被动方发送自己的 FIN;
- 主动方 ACK,并进入 TIME_WAIT。
TIME_WAIT 用于:
- 让最后 ACK 有机会重传;
- 避免旧连接中的延迟报文污染新连接。
具体系统参数和保活时间因操作系统而异,不应死记“2 小时、75 分钟、10 次”作为所有平台固定值。
17.6 TCP 可靠性机制
- 校验和;
- 序列号;
- 累积确认;
- 超时重传;
- 快速重传;
- 滑动窗口;
- 接收端流量控制;
- 拥塞控制;
- 乱序重组;
- 重复报文丢弃。
发送窗口受接收窗口和拥塞窗口共同限制。
TCP 滑动窗口示意图
18. C# 运行时补充
本章从「托管代码与非托管资源」「IDisposable 与 using」和「unsafe」等方面说明 C# 运行时补充。
18.1 托管代码与非托管资源
托管内存由 GC 管理,但 GC 只能按可达性回收托管对象,不能保证及时释放:
- 文件句柄;
- Socket;
- 数据库连接;
- 原生内存;
- ComputeBuffer;
- NativeArray;
- Unity 原生引擎资源。
因此需要确定性释放。
18.2 IDisposable 与 using
离开作用域时会调用
Dispose(),即使抛出异常也能执行。类长期持有资源时:
Unity 特有注意:
Texture2D、Mesh、Material等通常不实现 IDisposable;
- 运行时创建的 UnityEngine.Object 用
Destroy;
NativeArray用Dispose;
ComputeBuffer/GraphicsBuffer按 API Release 或 Dispose;
OnDestroy中释放对象拥有的资源。
18.3 unsafe
unsafe 允许指针和指针运算,但:- 不是使用 IDisposable 的前提;
- 不是访问非托管资源的唯一方法;
- 会绕过部分类型和内存安全;
- 应限制在性能已验证且边界明确的底层代码。
18.4 Dictionary
Dictionary<TKey, TValue> 基于哈希表:平均查找、插入和删除为 O(1),最坏情况可能退化。
注意:
- Key 的
Equals与GetHashCode必须一致;
- 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 分配。