关于DOTS:Entity Component System

63 篇文章 11 订阅

英文原文:
https://blog.unity.com/technology/on-dots-entity-component-system

  这是关于我们新的以数据为导向的技术栈(DOTS)的几篇文章之一,分享一些关于我们如何和为什么走到今天,以及我们接下来要去哪里的见解。

  在我的上一篇文章中,我谈到了HPC#和Burst作为Unity向前发展的底层基础技术。我喜欢把我们的堆栈的这一层称为 “游戏引擎的引擎”。任何人都可以使用这个堆栈来编写一个游戏引擎。我们可以。我们会的。你也可以。不喜欢我们的?编写你自己的,或者根据你的喜好修改我们的。

Unity的组件系统

  我们在上面建立的下一层是一个新的组件系统。Unity一直都是以组件的概念为中心。你在一个GameObject上添加一个Rigidbody组件,它就会开始下降。你给一个GameObject添加一个Light组件,它就会开始发射光线。添加一个AudioEmitter组件,这个GameObject就会开始发出声音。

  对于程序员和非程序员来说,这是一个非常自然的概念,而且容易建立直观的用户界面。实际上,我对这个概念的成熟程度感到非常惊讶。以至于我们想保留它。

  不合时宜的是我们如何实现我们的组件系统。它是以面向对象的思维方式编写的。组件和GameObjects是 "重型C++"对象。创建/删除它们需要一个mutex锁来修改id->objectpointers的全局列表。所有的GameObjects都有一个名字。每个对象都有一个指向C++对象的C#包装器。那个C#对象可以在内存的任何地方。C++对象也可以在内存中的任何地方。缓存缺失的情况很多。我们尽可能地减轻症状,但你能做的只有这么多。

  有了面向数据的思维方式,我们可以做得更好。从用户的角度来看,我们可以保持同样好的属性(添加一个Rigidbody组件,这个东西就会掉下来),但也可以通过我们的新组件系统获得惊人的性能和并行性。

  这个新的组件系统就是我们的实体组件系统(ECS)。非常粗略地讲,今天你用GameObject做的事,在新系统中你用实体做。组件仍然被称为组件。那么有什么不同呢?数据布局。

我们来看看一些常见的数据访问模式

  你在Unity中以传统方式编写的典型组件可能看起来像这样:

class Orbit : MonoBehaviour
{
   public Transform _objectToOrbitAround;

   void Update()
   {
       //please ignore this math is all broken, that's not the point here :)
       var currentPos = GetComponent<Transform>().position;
       var targetPos = _objectToOrbitAround.position;
       GetComponent<RigidBody>().velocity += SomehowSteerTowards(currentPos,targetPos)
   }
}

  这种模式一次又一次地出现。一个组件必须在同一个GameObject上找到一个或多个其他组件,并在其上读/写一些值。

这里面有很多问题:

  • Update()方法是为一个单一的轨道组件调用的。下一次Update()的调用可能是针对一个完全不同的组件,很可能导致这段代码在下次为另一个轨道组件运行此帧时被从缓存中驱逐。
  • Update()必须使用GetComponent()去寻找它的Rigidbody。它可以被缓存起来,但你必须注意Rigidbody组件不会被销毁)。
  • 我们所操作的其他组件在内存中的位置完全不同。

  ECS使用的数据布局认识到这是一个非常常见的模式,并优化了内存布局,使这样的操作变得快速。

ECS数据布局

  ECS将所有具有完全相同成分集的实体在内存中分组。 它称这样的集合为原型(archetype)。原型的一个例子是。“位置&速度&刚体&对撞机”。ECS以16k的块来分配内存。每个块只包含单一原型的实体的组件数据。

  在ECS的土地上,你必须静态地声明 "我想对所有同时拥有速度、刚体和轨道组件的实体进行一些操作,而不是让用户更新方法在运行时搜索其他组件来进行操作。为了找到所有这些实体,我们只需找到符合特定 "组件搜索查询 "的所有原型。每个原型都有一个存储该原型的实体的Chunks列表。我们在所有这些Chunks上循环,在每一个Chunks里面,我们都在做一个紧密的内存线性循环,来读写组件数据。这种在每个实体上运行相同代码的线性循环也为Burst提供了可能的矢量化机会。

  在许多情况下,这个过程可以微不足道地分成几个作业,使操作ECS组件的代码在几乎100%的核心利用率上运行。

  ECS为你做了所有这些工作,你只需要提供你想在每个实体上运行的代码。如果你想的话,你可以手动进行分块迭代)。

  当你从一个实体中添加/删除一个组件时,它就会转换原型。我们把它从当前的块中移到新的原型的块中,然后把前一个块中的最后一个实体换回来,以 “填补这个洞”。

  在ECS中,你也可以静态地声明你打算如何处理组件的数据。只读或读写。通过承诺(承诺被验证)只从Position组件中读取数据,ECS可以获得更有效的作业调度。其他同样想从Position组件中读取数据的作业就不必等待了。

  这种数据布局也使我们能够处理我们长期以来的困扰,那就是加载时间和序列化性能。为一个大场景加载/流传ECS数据并不比从磁盘上加载原始字节和原样使用它们多多少。

  这就是Megacity演示在手机上几秒钟内加载的原因。

快乐的 “意外”

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

HPC#, Burst, ECS. 真棒,但我的游戏引擎在哪里?

  我们需要建立的下一个层是非常大的。这是 "游戏引擎 "层,由 “渲染器”、“物理学”、“网络”、“输入”、"动画 "等功能组成。这就是我们今天的大致情况。我们已经开始在这些部分工作,但它们不会在一夜之间准备好。

  这听起来可能是一种无奈。在某种程度上是的,但在另一种程度上,它不是的。因为ECS和建立在它之上的所有东西都是用C#编写的,它可以在传统的Unity中运行。因为它在Unity内部运行,所以你可以编写使用ECS之前功能的ECS组件。现在还没有纯粹的ECS网格绘制系统。然而,你可以写一个ECS MeshRenderSystem,使用ECS之前的Graphics.DrawMeshIndirect API作为实现,同时等待纯ECS版本的到来。这正是我们的Megacity演示所使用的技术。加载/流式/剪裁/LODding/动画都是用纯ECS系统完成的,但最终的绘制却不是。

  所以你可以混合和匹配。这样做的好处是,你已经可以获得Burst代码的好处,以及ECS的性能,而不必等待我们为所有子系统提供纯ECS版本。不太好的是,在这个过渡阶段,你可以看到并感受到这种摩擦,即你 “使用两个不同的世界,却被粘在一起”。

  我们将把ECS HPC#子系统的所有源代码装入软件包中。你可以检查、调试、修改每个子系统,也可以更精细地控制何时升级哪个子系统。例如,你可以升级物理学子系统包而不升级其他东西。

游戏对象将发生什么变化?

  游戏对象是不会消失的。十多年来,人们已经在它上面成功地推出了令人惊叹的游戏。这个基础是不会消失的。

  将会改变的是,随着时间的推移,你会看到我们进行改进的能量从完全进入游戏对象世界,向ECS世界倾斜。

API 可用性/模板

  在看ECS的时候,人们提出的一个常见的、非常有道理的观点是,有很多类型的东西。大量的模板代码挡在你和你要实现的目标之间。

  在地平线上有很多改进,旨在消除对大多数模板的需求,使其更简单地表达你的意图。 我们还没有实现其中的许多改进,因为我们一直专注于基础性能,但我们相信没有充分的理由让ECS游戏代码有很多模板代码,或者比编写MonoBehaviour更费劲。

  Project Tiny已经实现了其中的一些改进(比如基于lambda的迭代API)。 说到这一点…

Tiny项目的ECS是如何融入这一切的?

  Project Tiny将在本博文所谈到的相同的C# ECS的基础上发布。Project Tiny将在几个方面成为我们ECS的一个重要里程碑。

  • 它将能够在一个完整的ECS专用环境中运行。一个没有过去包袱的新玩家。
  • 这意味着它也是纯粹的ECS,必须与一个真正的(微小的)游戏所需要的所有ECS子系统一起发布。
  • 我们将采用Project Tiny的编辑器支持所有ECS场景下的实体编辑,而不仅仅是tiny。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值