Unity 底层运行机制与数据模型

——从 Native 机器码、Mono/IL2CPP 到编辑器与运行时内存


1. Unity 编辑器的本质

1.1 Unity.exe 是什么

  • Unity 编辑器(Unity.exe / Unity.app)是一个 Native 程序

  • 也就是说:

    • 已经由 C/C++ 编译为目标平台的机器码
    • 操作系统直接加载并执行
  • CPU 只执行机器码

  • Unity 编辑器不是:

    • C++ 源码
    • C# 源码
    • IL
      而是 机器码 + 数据段

结论:当你双击 Unity.exe,本质上是在执行一段已经编译好的 Native 机器码。


2. Unity 的双层技术架构

Unity 从一开始就采用了Native + Managed 的混合架构

2.1 Native 层(C++)

职责:

  • 引擎核心
  • 渲染(Graphics / GPU 提交)
  • 物理(PhysX)
  • SceneView / Inspector / Gizmo
  • 资源管理
  • 编辑器 UI

特点:

  • 编译期生成机器码
  • 直接被 CPU 执行
  • 性能高、控制底层

2.2 Managed 层(C#)

职责:

  • 游戏逻辑(MonoBehaviour)
  • 编辑器扩展(EditorWindow / CustomInspector)
  • 工具脚本
  • 用户逻辑层

执行方式:

  • C# → IL
  • IL → Mono VM / IL2CPP Runtime 解释或 AOT

CPU 并不直接执行 IL
CPU 执行的是 Mono VM / IL2CPP 的 Native 机器码


3. Mono、IL、IL2CPP 的真实关系

3.1 C# 是如何变成可执行逻辑的

流程(编辑器内):

.cs(文本)
 ↓
C# 编译器
 ↓
IL(DLL)
 ↓
Mono VM(Native)
 ↓
CPU 执行 Mono VM 的机器码
  • IL 是 数据

  • Mono VM 是 Native 程序

  • Mono VM 负责:

    • 类型系统
    • 对象内存布局
    • 字段访问
    • 方法调用

3.2 为什么 Unity 需要 IL2CPP

IL2CPP 并不是为了“跨平台 IL”,而是为了解决:

  • iOS 等平台 禁止 JIT
  • Mono JIT 性能和平台限制
  • 安全性与反编译难度

IL2CPP 流程:

IL
 ↓
IL2CPP(转为 C++)
 ↓
平台 C++ 编译器
 ↓
Native 机器码

IL2CPP VM 本身是 C++ 写的 Native Runtime


4. 编辑器态 vs 运行态(关键分界)

4.1 这是两个“状态”,不是两套引擎

维度 编辑器态(Edit Mode) 运行态(Play Mode)
是否运行逻辑
是否 Update
是否可保存
数据是否权威
所在进程 Unity.exe Unity.exe

Play Mode 并不会启动新进程


4.2 编辑器态内存

  • 反序列化后的 .unity / .prefab / .asset
  • Hierarchy 中的 GameObject
  • Inspector 中的字段值
  • 权威数据源
  • 可以序列化回硬盘

4.3 运行态内存

  • 编辑器态场景的 完整克隆
  • 仅存在于 Play Mode 生命周期
  • 所有修改都是 临时的
  • 退出 Play Mode 时整体丢弃

5. Inspector 修改数据的本质

5.1 Inspector 修改了什么

  • ❌ 不是修改脚本
  • ❌ 不是修改机器码
  • ✅ 修改的是:

内存中组件实例的字段二进制数据

例如:

  • Transform.position
  • MonoBehaviour.publicField

5.2 为什么 Inspector 的值会覆盖脚本默认值

  • 脚本中的字段初始化值:

    • 只在 组件第一次创建时生效
  • Inspector:

    • 直接写内存
    • 保存时序列化

结论(非常重要):

Unity 使用的是 Hierarchy / 内存中的字段值,而不是脚本源码中的默认值。


6. Unity 的对象文件体系

6.1 Unity 的“对象文件”类型

文件 作用
.unity 场景序列化
.prefab GameObject 模板
.asset ScriptableObject / 资源
.mat 材质
.anim 动画
.controller Animator Controller
.meta GUID / 依赖信息

它们的共同点:

  • 序列化数据
  • 不是对象
  • 不是机器码
  • 不能直接运行

7. 实例化(Instantiate)的真正含义

7.1 Instantiate ≠ 读文件

Instantiate 的本质是:

内存级对象克隆

过程:

  1. 已有一个内存对象(Prefab 或原对象)

  2. Unity:

    • 复制其可序列化字段数据
    • 复用类型信息(Mono VM 类型表)
    • 分配新的内存
  3. 得到独立实例 (Clone)


7.2 为什么不是简单读二进制

因为:

  • 每个实例必须有独立字段内存
  • 类型系统必须绑定 Mono VM
  • 需要参与 Update / Physics / Render
  • 资源文件只是“快照”,不是“活对象”

8. Play Mode 为什么不保存修改

8.1 根因总结(一句话)

Play Mode 修改的是运行时克隆内存,而不是编辑器权威数据源。

8.2 Play Mode 内部机制

编辑器态内存(权威)
  ↓ 克隆
运行态内存(临时)
  • 所有运行时修改:

    • 发生在临时内存
    • 没有序列化权限
  • 退出 Play Mode:

    • 临时内存整体销毁
    • 编辑器态回滚

9. Native / Managed 与 Edit / Play 的正确关系

9.1 四象限模型(非常重要)

                执行方式
            ┌─────────────────┐
            │   Native   | Managed │
┌───────────┼─────────────────┤
│ 编辑器态  │ Editor C++ | Editor C# │
│ (Edit)    │ SceneView  | Inspector │
├───────────┼─────────────────┤
│ 运行态    │ Runtime C++| Game C#   │
│ (Play)    │ Renderer   | MonoBehav │
└───────────┴─────────────────┘

Edit / Play 是状态维度
Native / Managed 是实现维度

它们是 正交的,不是上下层关系


posted @ 2026-01-20 10:44  高山仰止666  阅读(3)  评论(0)    收藏  举报