参考文章:浅谈Unity ECS(一)Uniy ECS基础概念介绍:面向未来的ECS - 知乎 (zhihu.com)
视频链接:【青幻译制】GDC讲座系列之三 守望先锋的游戏架构和网络代码_哔哩哔哩_bilibili
云风的 BLOG: 浅谈《守望先锋》中的 ECS 构架 (codingnow.com)
游戏开发中的ECS架构-CSDN博客
一个典型的 ECS 框架 ↑
世界->是系统和实体的组合 -> 实体是一个组件的集合所对应的ID,组件仅仅用于储存游戏状态,而不具备行为,系统具有行为却不保存游戏状态
组件没有函数,系统没有成员变量
Entity Component System (ECS) 是一个 gameplay 层面的框架,它是建立在渲染引擎、物理引擎之上的,主要解决的问题是如何建立一个模型来处理游戏对象 (Game Object) 的更新操作。
这边最主要区别就是把对象上的数据和行为剥离,由专门的System来处理某一种行为,而不是每个对象在自己的一个更新函数中处理所有和自己相关的操作
Unity ECS基本概念
Entity的概念
它的意义在于生命期管理,这里是用 32bit ID 而不是指针来表示的,另外附着了渲染用到的资源 ID 。因为仅负责生命期管理,而不设计调用其上的方法,用整数 ID 更健壮。整数 ID 更容易指代一个无效的对象,而指针就很难做到。
Entity
为ECS中基本的成员,实际上只是由一个Index和一个Version组成(Version只有在Entity被回收后会加1),其实际的Component数据存储在一个Chunk上(Unity ECS特有的数据类型),需要操作其Component数据时,根据其index到EntityDataManager中找到其所在的Chunk和IndexInChunk,取到对应的Component数据后进行操作。
public struct Entity : IEquatable<Entity>
{
public int Index;
public int Version;
......
}
Component的概念
component是Entity的一个属性,通常记成了IComponent 或 ISharedComponentData 接口的结构体(两个接口都为空接口,仅标记了类型)。一个 Entity 可以包含多个 Component ,记成了 ISharedComponentData 的数据会在多个 Entity 中共享,同时可以使用托管类型的成员,一般用来存放 GameObject 或 RenderMesh 等渲染相关的成员
一个Entity的Component可以在CreateEntity时指定,也可以使用一个ArcheType创建或从已有Entity复制来创建。同时已经创建的Entity还可以通过AddComponent
和RemoveComponent
来动态进行Component的添加或删除(由于效率问题不推荐)。
Tips:Component可以用Proxy包装后直接挂在GameObject上,挂载多个Proxy的GameObject可以作为Prefab直接传入EntityManager.Instantiate来生成新的Entity,如:
// Create an entity from the prefab set on the spawner component.
var prefab = spawnerData.prefab;
var entity = EntityManager.Instantiate(prefab);
ArcheType的概念
ArcheType 是Unity ECS 中特有的概念,也是 Unity ECS 内存管理中的一个核心部分,许多重要操作都与此相关。ArcheType 是由几个固定 Component 组成的 Entity 原型
- ArcheType管理所有属于它的Entity Component数据,对应数据存放在归属于它的chunk上
- 可以通过ArcheType快速访问所有该类型的Entity Component数据
- 拥有Component的Entity一定处在某个ArcheType的管理之下
- ArcheType拥有缓存机制,第二次创建相同的ArcheType时会自动将现有的返回
我们可以使用EntityManager.CreateArchetype(params ComponentType[] types)来主动创建一个ArcheType,通过ArcheType可以直接调用EntityManager.CreateEntity(EntityArchetype archetype)来快速创建具有某一类特征的Entity。同时如果使用直接传入Components的方式来创建Entity时也会自动生成含有对应Component的ArcheType。
ComponentSystem的概念
ComponentSystem 为 System 在 Unity ECS 中的实现,一个 ComponentSystem 会对含有某些 Component 的 Entity 执行一些特定的操作,通常继承自 ComponentSystem 或 JobComponentSystem。区别是继承 ComponentSystem 只会在主线程执行,而继承自 JobComponentSystem 则可以利用 JobSystem 来进行多线程并发处理,但同时对应操作过程中的限制也更严格。在大部分情况下应当尽量使用 JobComponentSystem 来完成相关的操作,从而提升性能
多个不同的 ComponentSystem 可以在定义时通过 UpdateBefore ,UpdateAfter,UpdateBefored 等标签来控制其执行顺序,这会在一定程度上影响并发执行,通常只在必要时使用
一个 ComponentSystem 通常关注一个包含特定的 Component 组合的 Entity 集合(称为 Component Group)这个 ComponentGroup 集合可以通过 GetComponentGroup 主动获取
ComponentGroup m_Spawners;
//获取包含ObjectSpawner和Position两个Component的ComponentGroup
protected override void OnCreateManager()
{
m_Spawners = GetComponentGroup(typeof(ObjectSpawner), typeof(Position));
}
也可以使用 IJobProcessComponentData 中定义和 RequireSubtractiveComponentAttribute 等标签自动注入(Inject),同样也会生成一个 ComponentGroup
//通过RequireComponentTagAttribute为JobComponentSystem添加额外的依赖项
//[RequireComponentTagAttribute(typeof(Object))]
//通过RequireSubtractiveComponentAttribute为JobComponentSystem添加额外的排除项
[RequireSubtractiveComponentAttribute(typeof(ObjectSpawner))]
struct ObjectMove : IJobProcessComponentData<Position>
{
......
public void Execute(ref Position position)
{
......
}
}
数据存储相关
Chunk的概念
chunk 是Unity ECS 中特有的一个数据结构,在ECS部分代码中有大量使用,通常是指用来存放 Component 信息的 ArchetypeChunk,此外还有更一般的 Chunk 通过 ChunkAllocator 进行开辟,可以存放 ArcheType 中的各类型信息,大小和存储结构都与 ArchetypeChunk 不同,此处的 Chunk 特指存放 ArcheType 中Component 信息的 ArchetypeChunk。chunk 有以下几个特点:
EntityManager
会将Component数据存放在固定的16kb大小的Chunk
中(可以在Chunk
定义中找到指定大小kChunkSize
)- 每个
Chunk
结构包含了这个区块中内容的相关信息 - 每个
EntityArchetype
都包括了一个Chunk
的独特集合 - 一个
chunk
只能存在于一个archetype
中 - 一个
ArchetypeChunk
结构是一个到具体Chunk
的指针