开始使用ECS
ECS工作流程
- 创建子场景
ECS使用子场景来包含应用程序的内容。你将GameObjects和MonoBehaviour组件添加到子场景,然后bakers将GameObjects和MonoBehaviours组件转换为实体和ECS组件。
DOC:Baking取代了Entities中以前的转换管道(IConvertGameObjectToEntity)。
- 创建ECS组件
组件存储应用程序的数据,为了在程序中创建行为,System提供读取和写入ECS组件数据的逻辑。
- 创建实体
要在编辑器中创建实体,请将游戏对象添加到子场景。烘焙过程将这些游戏对象转换为实体。或者,要将 ECS 组件附加到转换后的实体,您可以创建烘焙器。创建烘焙器时,您可以定义它适用于哪个 MonoBehaviour 组件,然后编写使用 MonoBehaviour 组件数据创建并将 ECS 组件附加到转换后的实体的代码。您还可以从烘焙器创建其他实体并将 ECS 组件附加到它们。在此工作流程中,MonoBehaviour 组件称为创作组件。
- 创建系统
系统在您的应用程序中创建行为。为此,它们可以查询和转换 ECS组件数据、创建和销毁实体以及从实体中添加和删除 ECS 组件。默认情况下,当您创建系统时,Unity 会实例化它并将其添加到默认世界。
- 优化
默认情况下,您在系统中编写的任何代码都会在主线程上同步运行。如果系统影响许多实体的数据并且会受益于多线程,则最佳做法是创建与Burst兼容的作业,并安排它们尽可能并行运行。Burst 将您的 C# 代码编译为优化的本机 CPU 代码,作业使您能够跨多个线程分配工作并利用多个处理器。
ECS概述
实体概念
实体Entity类似于非托管轻量级GameObject,它代表程序的特定元素。但是,实体充当将各个唯一组件关联在一起的ID,而不是包含任何代码或作为其关联组件的容器。
实体集合存储在World中,其中的世界EntityManager
管理着世界中所有的实体。EntityManager
包含可用于创建、销毁和修改该世界内的实体的方法。
CreateEntity
创建新实体
Instantiate
复制现有实体并根据该副本创建新实体
DestroyEntity
摧毁一个现有的实体
AddComponent
将组件添加到现有实体
RemoveComponent
从现有实体中移除组件
GetComponent
检索实体组件的值
SetComponent
覆盖实体组件的值
组件概念
组件包含System可以读取或者写入的实体数据。
使用IComponentData
没有方法的结构将结构体标记为组件类型。此组件类型只能包含非托管数据(他们可以包含方法,但最好将它们作为纯数据)。
系统概念
系统提供将组件数据从其当前状态转换为其下一个状态的逻辑。
系统每帧在主线程上运行一次。系统被组织成系统组的层次结构,您可以使用这些层次结构来组织系统更新的顺序。
您可以在实体中创建非托管或托管系统。要定义托管系统,请创建一个从 继承的类SystemBase。要定义非托管系统,请创建一个从 继承的结构ISystem。有关更多信息,请参阅系统概述。
世界概念
世界是实体的集合。实体的ID号仅在其自己的世界中是唯一的。世界有一个EntityManager
结构,您可以使用它来创建、销毁和修改世界内的实体。
一个世界拥有一组系统,通常只能访问同一个世界内的实体。此外,一个世界内具有相同组件类型集的一组实体一起存储在原型中,原型决定了程序中的组件在内存中的组织方式。
-
初始化
- 默认情况下,当您进入播放模式时,Unity 会创建一个World实例并将每个系统添加到这个默认世界。
- 如果您希望手动将系统添加到默认世界,请创建一个实现
ICustomBootstrap
接口的单个类。 - 如果您想要完全手动控制引导,请使用这些定义来禁用默认的世界创建:
#UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_RUNTIME_WORLD
:禁用默认运行时世界的生成。#UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_EDITOR_WORLD
:禁用默认编辑器世界的生成。#UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP
:禁用两个默认世界的生成。
原型(Archetypes)概念
原型是世界中所有具有相同唯一组件类型组合的实体的唯一标识符。例如,世界中所有具有组件类型 A 和 B 的实体共享一个原型。
当您添加或删除实体中的组件类型时,世界的EntityManager会将实体移至适当的原型。例如,如果实体具有组件类型A、B和C,并且您删除了其B组件,世界EntityManager会将实体移至具有组件类型 A和C的原型。如果不存在这样的原型,世界的EntityManager会创建它。
- 原型块(Archetype chunks)
所有具有相同原型的实体和组件都存储在称为块的统一内存块中。每个块由 16KiB 组成,它们可以存储的实体数量取决于块原型中组件的数量和大小。EntityManager
根据需要创建和销毁块。
结构变化概念
导致Unity重新组织内存块或内存中块内容的操作称为结构性更改。
了解哪些操作是结构性更改非常重要,因为它们可能占用大量资源,并且您只能在主线程上执行它们,而不能从作业中执行。
- 以下操作被视为结构变化:
- 创建或者销毁一个实体。
- 添加或移除组件。
- 设置共享组件值。
- 创建实体
当你创建一个实体时,Unity要么将该实体添加到现有的块中,要么如果实体的原型没有可用的块,则创建一个新块并将实体添加到该块中。
- 摧毁一个实体
当您销毁实体时,Unity从其块中移除该实体。如果移除实体会在块中留下空隙,Unity 会移动块中的最后一个实体来填补空隙。如果移除实体会使块为空,Unity 会释放该块。
- 添加或删除组件
当您添加或删除实体中的组件时,您会更改实体的原型。Unity将每个实体存储在与实体原型匹配的块中。这意味着如果您更改实体的原型,Unity必须将实体移动到另一个块。如果不存在合适的块,Unity将创建一个新的块。如果移动使前一个块留有空隙或留空,Unity将移动块中的最后一个实体以填补空隙或释放该块。
- 设置共享组件值
当您设置实体共享组件的值时,Unity会将实体移动到与新共享组件值匹配的块。如果不存在合适的块,Unity会创建一个新的块。如果移动导致前一个块有间隙或为空,Unity会移动块中的最后一个实体以填补间隙或释放该块。
- 避免同步点
同步点 (sync point) 是程序执行中等待迄今为止已安排的所有作业完成的点。同步点会限制您在一段时间内使用作业系统中可用的所有工作线程的能力。因此,您应该尽量避免同步点。ECS中数据的结构变化是同步点的主要原因。
您可以使用实体命令缓冲区来排队结构更改,而不是立即执行它们。您可以在帧中的稍后时间点播放存储在实体命令缓冲区中的命令。这样可以将帧中分布的多个同步点减少为单个同步点。
每个标准ComponentSystemGroup实例都提供EntityCommandBufferSystem作为组中第一个和最后一个更新的系统。如果您从其中一个标准系统获取实体命令缓冲区对象,则所有结构更改都会发生在帧中的同一点,从而产生一个同步点。您还可以使用实体命令缓冲区来记录作业中的结构更改,而不是仅在主线程上进行结构更改。
如果您无法为任务使用实体命令缓冲区,请按系统执行顺序将进行结构更改的所有系统分组在一起。如果两个系统都进行结构更改,则如果它们按顺序更新,则只会创建一个同步点。
Aspect概念
Aspect是一种类似对象的包装器,可用于将实体的组件子集组合成单个C# 结构。方面对于组织组件代码和简化系统中的查询非常有用。Unit为相关组件组提供了预定义的方面,您也可以使用界面定义自己的方面IAspect
。
这些Aspect可以包含一下内容:
- 单个Entity字段用于存储实体的ID。
RefRW<T>
和RefRO<T>
字段来访问类型的组件数据T,其中T实现- IComponentData。EnabledRefRW
和EnabledRefRO
字段来访问实现的组件的启用状态-IEnableableComponent
。DynamicBuffer<T>
用于访问实现的缓冲区元素的字段`IBufferElementData任何
ISharedComponent字`段都以只读方式访问共享组件值。- 其他方面类型
- 字段
您可以使用RefRW<T>
或RefRO<T>
将组件声明为方面的一部分。要声明缓冲区,请使用DynamicBuffer<T>
。
方面内部声明的字段定义了必须查询哪些数据才能使方面实例在特定实体上有效。
要使字段可选,请使用属性[Optional]
。要将DynamicBuffer嵌套方面声明为只读,请使用[ReadOnly]
属性。
- 系统中创建Aspect实例
要在系统中创建方面实例,请调用SystemAPI.GetAspect
。
例如MyAspect asp = SystemAPI.GetAspect<MyAspect>(myEntity);
- 迭代某个Aspect
public partial struct MySystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
foreach (var cannonball in SystemAPI.Query<CannonBallAspect>())
{
// use cannonball aspect here
}
}
}
(END)