Skip to content

Unity DOTS 架构概述

经过前面五个部分的理论洗礼与极简框架手写演练,你现在已经完全具备了挑战当今游戏工业界最高性能标准的技术储备。

在本书的最后一个部分,我们将视角转向目前全球最受关注、生态也相对最成熟的商业化 ECS 解决方案之一:Unity DOTS (Data-Oriented Technology Stack,面向数据技术栈)

长久以来,Unity 引擎的核心逻辑是建立在 GameObjectMonoBehaviour 体系之上的。这套体系极度偏向于面向对象编程(OOP),拥有易学易用、组件即插即用的优势,但也饱受性能噩梦的困扰(对象随意散落堆内存、Update 虚函数调用的巨大开销、多线程极度困难等)。

为了应对未来游戏对同屏海量单位、复杂物理模拟的需求,Unity 官方经历数年时间,从最底层的 C# 编译器开始,重构重写了一整套完全基于数据导向设计(DOD)的新底层架构。这套架构的合集就被称为 DOTS

DOTS 并不是一个单一的插件,而是包含了三个最为核心的技术支柱的宏大技术栈:

  1. Entities (ECS 架构本体):负责内存的结构化管理。
  2. C# Job System (多线程任务系统):负责安全地压榨多核 CPU。
  3. Burst Compiler (Burst 编译器):负责将 C# 转化为极限性能优化的机器码。

这是 DOTS 中最对应我们本教程前五部分内容的模块。 在 Unity Entities 中,摒弃了 GameObject,取而代之的是纯粹的整数 ID Entity。 抛弃了类组成的 MonoBehaviour,取而是基于 unmanaged struct 构建的 IComponentData

关键差异点:原型模式(Archetype)的工业落地 还记得我们在第三部分讨论的数据结构吗?Unity ECS 的底层强制采用了性能最狂暴的原型模式(Archetype)。 它在非托管内存中划出了一块块严格 16KB 大小的内存块(Chunk)。确保当你编写 System 遍历 PositionVelocity 时,拿到的数据无论跨多少个组件,在物理地址上永远都是连贯对齐的。

在传统 Unity 开发中,如果你想开个子线程(Thread)去计算逻辑然后修改场景物体,基本等于自寻死路,因为所有的 Unity API 几乎都是线程不安全的。

DOTS 引入了 C# Job System。它最大的革命性在于:提供了一个对开发者极其友好的、免锁(Lock-Free)的多线程开发通道。

得益于 Unity ECS 在底层将数据分割得明明白白。Job System 不需要庞大的锁机制,它只会做一件事: 静态分析你的各个任务声明了哪些数据的读写权限,如果发现 Task A 和 Task B 没有写入冲突,它就自动将这两个任务丢进工作线程池里并行狂奔。如果发现有冲突,它就自动在底层帮你构建依赖关系(Dependency),让 Task B 等 Task A 算完再执行。

它让你这个从未写过多核心并发代码的新手,也能轻松写出安全吃满 16 核心 32 线程的疯狂代码。

如果说 Entities 解决了内存对齐,Job System 解决了多核并行,那么 Burst Compiler 就是将单核性能榨干的终极魔法。

Burst 是一个基于 LLVM 构建的定制数学/数学相关 C# 编译器。 由于在 DOTS 开发中,你编写的代码完全排除了复杂的引用类型,全部是扁平的 Struct、NativeArray 连续容器以及简单的数学运算。Burst 编译器拿到这些“纯净”的代码后,它可以毫无后顾之忧地进行极其激进的汇编级优化。

自动 SIMD 向量化加速:Burst 可以自动识别连续的数组运算,并将你的代码翻译为针对目标平台(例如 AVX2, ARM Neon)优化的矢量指令(SIMD指令集)。在某些极端数学计算场景下,仅仅为你的代码加上 [BurstCompile] 标签,性能就能凭空暴涨 10 倍以上。

Unity DOTS 经历了多年的预览版(Preview)打磨,已经在最新的 Unity 2022/2023 LTS 版本中正式进入了 1.0 Production Ready 阶段。包括《夜族崛起》(V Rising)、《都市:天际线 2》(Cities: Skylines II) 在内的现象级大作,其惊人的同屏实体运转数量,背后都是 DOTS 栈的功劳。

理论结合实践。从下一章《开发环境配置》起,我们将真正打开 Unity 引擎,引入 Entities 包,一步步敲开商业级 ECS 的性能宝库。