之前接触 ECS 是因为看了守望先锋的架构分析。了解了一下发现这种架构天生支持多线程,而且对缓存非常友好,似乎做联网同步也很方便,感觉这应该是个挺有前途的设计思路。

这几天翻了一下 Unity 官网,看到 ECS 有文档了,例子也更新了许多,突然兴奋,准备写个 Demo 试试。顺便想记录看看 Unity 的正式版 api 能改成个什么鬼样子(大雾)

本系列基于 Entities 0.5.1-preview.11,并非正式版。
因此更倾向于科普而不是实用(学了也没用早晚要改 API),另外需要对旧版 Unity 有少许了解。

ECS 架构

ECS,是 Entity-Component-System 的缩写,该架构也就只有这三个东西组成(严格意义上)。这个架构由数据驱动逻辑,有别于我们熟知的 OOP(面向对象)

名词解释

Entity,实体,可以近似理解为 OOP 中的对象。 Entity 仅由 Component 组成,此外不含有其他任何成分。 可以认为 Entity 只是一个 id,组成它所有组件可以用这个 id 索引。 具体实现中 Entity 通常也只是一个用于标记的 id。

Component,组件,存储实际的数据,不包含逻辑。 例如 Position 组件中会有 x, y, z 表示一个实体的位置。

System,系统,不含数据,只处理逻辑,固定帧率调用。 且它会筛选拥有指定若干 Component 的 Entity 并对指定的 Component 进行读写。

ECS 介绍

理解 ECS 的重点有两个:

  • 一个是数据存储,即 Entity 由多个不同种的 Component 组成。
  • 另一个就是逻辑执行,即 System 只会筛选出拥有特定 Component 的 Entity,并只对那些特定的 Component 进行读写。System 针对的是 Component 而不是 Entity。

System 执行逻辑,Component 存储数据,Entity 则是将 Component 关联起来。

优劣分析

天生对并行友好。不难发现,可能会存在一些系统,它们每次只会处理同一个 Entity 的某几个固定组件。
比方说有几百个 Entity 和一枚多核处理器,可以每个核心分别处理一个 Entity 而完全不需要加锁。 而不同的系统如果访问的 Component 不同。

天生对缓存友好。因为 System 只关心特定的 Component,因此如果将同种 Component 存储在线性内存空间里,System 遍历 Component 的时候缓存命中率很高。 Unity 的具体实现中是将同类 Entity 的 Component 连续存储,可以实现缓存高命中。

耦合性低。理想状态下,系统只会对 Component 作出反应,因此系统之间是相对独立的。 但是各系统的执行顺序会存在依赖关系,不好处理,耦合比较严重。 系统之间信息传递也比较困难,理想情况下是通过数据共享来处理,属于可接受的公共耦合。 但实际情况很复杂,会产生不得不互相调接口的情况,比如 Unity 一些内部实现。 因此需要一些合理的设计才能真正实现低耦合。

目前成熟的 ECS 框架不算多,可以抄的作业也不多。

其他学习资料

感觉我写的有一点过于简单,想要深入了解可以看看下面这些文章。

https://www.cnblogs.com/zhaoqingqing/p/9718973.html
https://blog.codingnow.com/2017/06/overwatch_ecs.html