Unity 之所以流行,不只是因为编辑器好用,更因为它把"资源管理、渲染、脚本执行、物理、音频"这些复杂系统封装成了一套可组合的工作流。理解 Unity,最好同时看两条线:一条是开发过程,即一个项目如何从资源到可运行游戏;另一条是底层原理,即引擎在运行时如何调度、渲染和执行脚本。
一、Unity 开发过程:从资源到运行
Unity 开发通常不是单纯写代码,而是围绕编辑器、资源和运行时系统协作展开。
-
创建项目
新建项目时会选择渲染管线和平台类型,例如 URP、HDRP、Built-in,以及 2D、3D、XR 等模板。模板决定了默认资源、渲染设置和依赖包。
-
导入与处理资源
模型、贴图、音频、预制体、场景文件进入 Unity 后,会被 Asset Pipeline 导入并生成内部资产。编辑器会根据资源类型进行压缩、序列化、依赖分析和平台适配。
-
搭建场景
场景由 GameObject 组成,每个 GameObject 挂载若干 Component,例如 Transform、Renderer、Collider、AudioSource、脚本组件。场景本质上是对象层级和组件配置的描述。
-
编写脚本
Unity 脚本通常使用 C#。脚本本身继承自
MonoBehaviour,通过生命周期方法参与引擎调度,例如Awake、OnEnable、Start、Update、FixedUpdate、LateUpdate、OnDisable、OnDestroy。 -
配置物理、动画与 UI
物理系统依赖 Collider 和 Rigidbody;动画系统依赖 Animator、AnimationClip 和状态机;UI 系统依赖 Canvas、RectTransform 和事件系统。这些系统各自有更新节奏,不会完全等同于普通脚本更新。
-
调试与优化
可以使用 Console、Inspector、Profiler、Frame Debugger 等工具查看性能瓶颈。常见优化包括减少 Draw Call、降低 GC 分配、控制物理查询频率、优化资源大小和加载方式。
-
打包发布
构建时会进行资源打包、代码编译、平台适配和裁剪。不同平台会生成不同产物,例如 Windows 上的可执行文件,Android 上的 APK 或 AAB。
二、Unity 运行时架构
Unity 运行时大致可以分为几层:编辑器层、引擎核心层、脚本层和平台层。
表格
下载为表格
导出为图片
| 层级 | 作用 | 常见内容 |
|---|---|---|
| 编辑器 | 资源管理、场景编辑、调试 | Inspector、Hierarchy、Project、Console |
| 引擎核心 | 渲染、物理、音频、动画、资源管理 | Native C/C++ 模块 |
| 脚本层 | 游戏逻辑、状态控制、事件处理 | C# / MonoBehaviour / DOTS |
| 平台层 | 适配不同操作系统和设备 | Windows、Android、iOS、WebGL 等 |
编辑器主要用于开发阶段,而构建后的玩家端不再包含完整编辑器。运行时真正执行的是引擎核心与脚本层协同后的结果。
三、资源管线:Asset Pipeline 做了什么
Unity 的资源并不是原封不动地进入运行时。导入资源时,编辑器会完成几类工作:
- 格式转换:把原始文件转换成引擎可用格式,例如贴图压缩、模型导入、音频转码。
- 序列化:将资源保存为 Unity 可识别的资产形式,记录依赖关系。
- 元数据生成 :每个资源通常伴随
.meta文件,用于记录 GUID、导入设置和引用关系。 - 平台适配:不同平台可能使用不同压缩格式和纹理规格。
- 增量导入:资源变化时只重新处理受影响部分,提高编辑效率。
运行时加载资源时,Unity 会通过 AssetBundle、Addressables 或直接引用等方式管理内存和依赖。资源卸载也需要注意,否则容易出现内存泄漏或引用丢失。
四、脚本执行:MonoBehaviour 生命周期
Unity 脚本最常见的入口是 MonoBehaviour。它不是普通 C# 类,而是由引擎托管的对象,生命周期由引擎控制。
典型顺序如下:
Awake:对象初始化,通常用于设置内部状态。OnEnable:对象启用时调用。Start:第一次更新前调用,适合依赖其他对象完成初始化后执行。Update:每帧调用,适合处理输入、动画状态、非物理逻辑。FixedUpdate:按固定时间步长调用,适合物理计算。LateUpdate:在Update之后调用,适合摄像机跟随、最终位置修正。OnDisable:对象禁用时调用。OnDestroy:对象销毁时调用。
需要注意的是,Update 的频率取决于帧率,FixedUpdate 的频率由物理时间步长决定。如果逻辑依赖物理结果,不应放在 Update 中直接操作 Rigidbody;如果逻辑依赖最终画面状态,则更适合放在 LateUpdate。
五、渲染管线:帧是如何画出来的
Unity 渲染可以理解为:每一帧收集可见对象,提交绘制命令,再由图形 API 交给 GPU 执行。
常见渲染流程包括:
-
剔除
判断哪些对象在摄像机视野内,剔除不可见对象,减少渲染负担。
-
排序
根据材质、着色器、透明度、渲染队列等对绘制对象排序。
-
批处理与合批
相同材质和条件的对象可能被合并,减少 Draw Call。
-
着色器执行
GPU 执行顶点着色器、片元着色器等,计算光照、阴影、颜色和特效。
-
后处理
对最终画面进行色调映射、景深、泛光、抗锯齿等处理。
Unity 支持多种渲染管线:
- Built-in Render Pipeline:传统管线,兼容性强。
- URP:通用渲染管线,适合移动端、PC 和多平台项目。
- HDRP:高清渲染管线,适合高品质 PC 和主机项目。
渲染管线的选择会影响材质、着色器、光照和后处理的实现方式。
六、物理、动画与音频系统
物理系统通常基于离散时间步长运行。Rigidbody 负责动力学模拟,Collider 负责碰撞检测,PhysX 等底层库提供碰撞求解和约束计算。因为物理更新需要稳定性,所以它不完全跟随帧率,而是使用固定时间步长。
动画系统中,Animator 根据状态机、参数和 AnimationClip 驱动骨骼或属性。动画更新通常在特定阶段执行,可能与 Update 不完全同步。
音频系统负责加载音频资源、管理播放实例、处理空间音频和混音。音频播放通常由独立线程或子系统处理,避免阻塞主线程。
七、多线程与主线程
Unity 的大多数 API 只能在主线程调用。脚本中的 Update、Start、OnTriggerEnter 等回调都运行在主线程。如果直接在子线程中访问 GameObject、Transform、Instantiate 等接口,通常会报错。
但这不代表 Unity 没有多线程能力。渲染、物理、音频、资源加载等子系统可以在内部使用多线程。开发者也可以通过 Job System、Burst Compiler、AsyncOperation、Addressables 异步加载等方式提高并行效率。
八、代码执行:IL2CPP 与 Mono
Unity 脚本在编辑器中通常以托管方式运行,便于快速迭代。构建发布时,很多平台会使用 IL2CPP。
IL2CPP 的大致流程是:
- C# 代码编译为中间代码。
- IL2CPP 将中间代码转换为 C++ 代码。
- C++ 代码再由平台编译器编译为原生代码。
这样做的好处是性能更好、平台兼容性更强,也更容易与原生平台集成。缺点是调试和热更新能力会受到限制,具体取决于平台和技术方案。
九、打包流程
构建项目时,Unity 会执行一系列步骤:
- 收集被引用的场景和资源。
- 编译脚本代码。
- 处理着色器变体。
- 打包资源为平台可用格式。
- 生成平台工程或最终可执行文件。
- 进行签名、压缩、裁剪和优化。
资源是否被打包,取决于是否被场景引用、是否被 Addressables/AssetBundle 标记,以及构建配置中的裁剪规则。未使用资源可能被排除,从而减小包体。
十、从开发视角理解 Unity
可以把 Unity 看作一个由编辑器驱动、以组件为核心、由引擎核心调度多子系统的游戏运行时。开发者主要面对的是 GameObject、Component、Asset、Scene、Script 这些高层概念;而引擎底层负责把这些概念转换成渲染命令、物理模拟、动画更新、音频播放和平台可执行文件。
学习 Unity 时,建议先掌握开发流程:资源导入、场景搭建、脚本生命周期、UI、动画、物理和打包。之后再逐步深入底层:Asset Pipeline、渲染管线、IL2CPP、Job System、Addressables 和性能优化。这样既能快速做出东西,也能在遇到性能问题时知道该从哪一层排查。