Product roadmap

关于 DOTS:实体组件系统

LUCAS MEIJER / UNITY TECHNOLOGIESContributor
Mar 8, 2019|9 Min
关于 DOTS:实体组件系统
为方便起见,此网页已进行机器翻译。我们无法保证翻译内容的准确性或可靠性。如果您对翻译内容的准确性有疑问,请参阅此网页的官方英文版本。

这是有关我们的新 数据导向技术堆栈(DOTS)的几篇文章之一,分享了我们如何以及为何达到今天的水平以及下一步的发展方向的一些见解。

在我的上一篇文章中,我讨论了 HPC# 和 Burst 作为 Unity 未来发展的低级基础技术。我喜欢将我们堆栈的这个级别称为“游戏引擎引擎”。任何人都可以使用这个堆栈来编写游戏引擎。我们可以。我们将。你也可以。不喜欢我们的吗?编写您自己的,或者根据您的喜好修改我们的。

Unity 的组件系统

我们构建的下一层是一个新的组件系统。Unity 一直以组件的概念为中心。您向 GameObject 添加 Rigidbody 组件,它就会开始下落。您向 GameObject 添加一个 Light 组件,它就会开始发光。添加一个 AudioEmitter 组件,GameObject 将开始产生声音。

对于程序员和非程序员来说,这是一个非常自然的概念,并且易于构建直观的 UI。我实际上对这个概念的传承程度感到非常惊讶。非常好,我们想保留它。

我们实现组件系统的方式已经过时了。它是以面向对象的思维方式编写的。组件和游戏对象是“重度 C++”对象。创建/销毁它们需要互斥锁来修改 id->objectpointers 的全局列表。所有游戏对象都有一个名字。每个都获得一个指向 C++ 的 C# 包装器对象。该 C# 对象可以位于内存中的任何位置。C++对象也可以位于内存中的任何位置。大量缓存未命中。我们会尽力减轻症状,但能做的只有这么多。

有了以数据为导向的思维方式,我们可以做得更好。从用户的角度来看,我们可以保留相同的优良属性(添加 Rigidbody 组件,物体就会下落),同时还可以通过新的组件系统获得惊人的性能和并行性。

这个新的组件系统是我们的实体组件系统(ECS)。粗略地说,您今天对 GameObject 所做的操作与在新系统中对 Entity 所做的操作相同。组件依然称为组件。那么有什么不同呢?数据布局。

让我们看看一些常见的数据访问模式
以传统方式在 Unity 中编写的典型组件可能如下所示:

轨道类:MonoBehaviour
{
public Transform _objectToOrbitAround;

void Update()
{
//请忽略这道数学题,这都是错的,这不是重点:)
var currentPos = GetComponent<Transform>().position;
var targetPos = _objectToOrbitAround.position;
GetComponent<RigidBody>().velocity += SomehowSteerTowards(currentPos,targetPos)
}
}

这种模式一再出现。一个组件必须在同一个游戏对象上找到一个或多个其他组件并在其上读取/写入一些值。

这其中存在很多问题:

  • Update() 方法被调用来获取单个轨道分量。下一个 Update() 调用可能是针对完全不同的组件,这可能会导致下次为另一个 Orbit 组件运行此帧时该代码被从缓存中逐出。
  • Update() 必须使用 GetComponent() 去找到它的 Rigidbody。(它可以被缓存,但你必须小心 Rigidbody 组件不被破坏)。
  • 我们正在操作的其他组件位于内存中完全不同的位置。

ECS 使用的数据布局识别出这是一种非常常见的模式,并优化内存布局以使此类操作快速进行。

ECS 数据布局

ECS 在内存中将具有完全相同组件集的所有实体分组在一起。它将这样的集合称为原型。原型的一个例子是:“位置、速度、刚体和碰撞器”。ECS 以 16k 的块为单位分配内存。每个块只包含单个原型实体的组件数据。

在 ECS 领域中,您不必让用户的 Update 方法在运行时针对每个 Orbit 实例搜索要操作的其他组件,而是必须静态声明“我想对同时具有 Velocity 和 Rigidbody 以及 Orbit 组件的所有实体运行一些操作。为了找到所有这些实体,我们只需找到与特定“组件搜索查询”匹配的所有原型。每个原型都有一个 Chunk 列表,其中存储了该原型的实体。我们循环遍历所有这些块,并且在每个块内,我们对紧密打包的内存进行线性循环,以读取和写入组件数据。在每个实体上运行相同代码的线性循环也为 Burst 提供了可能的矢量化机会。

在许多情况下,这个过程可以很容易地分成几个作业,使得操作 ECS 组件的代码以接近 100% 的核心利用率运行。

ECS 为您完成所有这些工作,您只需要提供想要在每个实体上运行的代码。(如果您愿意的话,您可以手动进行块迭代。)

当您从实体添加或删除组件时,它会切换原型。我们将其从当前块移动到新原型的块,然后交换前一个块的最后一个实体以“填补空缺”。

在 ECS 中,您还可以静态声明您打算对组件数据执行的操作。ReadOnly 或 ReadWrite。通过承诺(该承诺已经验证)仅从 Position 组件读取,ECS 可以更有效地调度其作业。其他想要从 Position 组件读取的作业无需等待。

这种数据布局还使我们能够解决长期存在的困扰,即加载时间和序列化性能。为大场景加载/流式传输 ECS 数据只不过是从磁盘加载原始字节并按原样使用它们而已。

这就是“Megacity”演示版在手机上只需几秒钟就能加载的原因。

快乐的“意外”

虽然实体可以完成当今游戏对象所做的事情,但由于它们非常轻量,因此可以做更多的事情。事实上,实体到底是什么?在这篇文章的早期草稿中,我写道“我们将实体存储在块中”,后来将其改为“我们将实体的组件数据存储在块中”。这是一个重要的区别,要认识到实体只是一个 32 位整数。除了其组件的数据之外,没有任何东西可以存储或分配。因为它们非常便宜,所以您可以将它们用于游戏对象不适合的场景。就像在粒子系统中的每个单独粒子使用一个实体一样。

HPC#、突发、ECS。太棒了,但是我的游戏引擎在哪里?

我们需要构建的下一层非常大。它是由“渲染器”、“物理”、“网络”、“输入”、“动画”等功能组成的“游戏引擎”层。这大致就是我们今天的状况。我们已经开始着手制作这些作品,但它们不会在一夜之间完成。

这听起来可能有点令人沮丧。从某种程度上来说确实如此,但从另一种程度上来说则不然。由于 ECS 及其上构建的所有内容都是用 C# 编写的,因此它可以在传统的 Unity 中运行。因为它在 Unity 内部运行,所以您可以编写使用 ECS 前功能的 ECS 组件。目前还没有纯粹的 ECS 网格绘制系统。但是,您可以编写一个使用预 ECS Graphics.DrawMeshIndirect API 作为实现的 ECS MeshRenderSystem,同时等待纯 ECS 版本发布。这正是我们的“特大城市”演示所使用的技术。加载/流式传输/剔除/LODding/动画都是用纯 ECS 系统完成的,但最终的绘制不是。

因此你可以混合搭配。这样做的好处是,您已经可以从 Burst 代码生成和 ECS 性能中获益,从而为您的游戏代码带来好处,而不必等待我们发布所有子系统的纯 ECS 版本。不好的是,在这个过渡阶段,你可以看到并感受到这种“使用两个粘在一起的不同世界”的摩擦。

我们将把所有源代码以 包裹的形式运送到我们的 ECS HPC# 子系统。您可以检查、调试、修改每个子系统,并且可以更细粒度地控制何时升级哪个子系统。例如,您可以升级物理子系统包而不升级其他任何东西。

游戏对象会发生什么情况?

游戏对象不会去任何地方。十多年来,人们已经成功地在其上推出了令人惊叹的游戏。那个基金会不会消失。

变化的是,随着时间的推移,你会看到我们用于改进的精力从单纯的游戏对象世界转向 ECS 世界。

API 可用性/样板

当人们查看 ECS 时,提出的一个常见且非常有效的观点是,其中有大量的输入。大量样板代码阻碍了您实现想要实现的目标。

即将出现许多改进,旨在消除对大多数样板文件的需求,并使您更容易表达您的意图。由于我们一直专注于基础性能,因此我们尚未实现其中的许多功能,但我们相信 ECS 游戏代码没有必要包含太多样板代码,或者比编写 MonoBehaviour 需要更多的工作。

Project Tiny 已经实现了一些改进(例如基于 lambda 的迭代 API)。说到这个..

Project Tiny 的 ECS 与这一切如何契合?

正如本博文所讨论的, Project Tiny 将在相同的 C# ECS 上发布。Project Tiny 将在以下几个方面成为我们 ECS 的一个重要里程碑:

  • 它将能够在完整的仅 ECS 环境中运行。一名没有过去包袱的新球员。
  • 这意味着它也是纯 ECS,并且必须附带真实(微型)游戏所需的所有 ECS 子系统。
  • 我们将采用 Project Tiny 的编辑器支持所有 ECS 场景(而不仅仅是 tiny)的实体编辑。
加入我们?

我们有针对 DOTS 堆栈各个不同部分的职位空缺,特别是在伯班克和哥本哈根,请访问 careers.unity.com

此外,请务必加入 Unity Entity Component System 和 C# Job System 论坛, 以提供反馈并获取有关实验和预览功能的信息。