以下文档均来源于ECS官网:
https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/ecs_entities.html
使用ComponentSystem
您可以使用ComponentSystem来处理数据。ComponentSystem的方法在主线程上运行,因此无法利用CPU的多核,一般被使用在以下情况中:
- 调试或探索性开发 - 如果代码在主线程上运行,有时更容易发现发生了什么。例如,您可以输出调试文本或绘制调试图形。
- 当系统需要访问,或者交互那些只能在主线程上运行的API接口时 - 这可以帮助您逐步将游戏系统转换为ECS模式,而不必从一开始就重写所有内容。
- 系统执行的工作量很小,甚至少于创建和调度作业的微小开销。
重要提示: 进行结构级的更改会强制完成所有作业。 此事件称为同步点(sync point),它可能会导致性能下降,因为系统在等待同步点时无法利用所有可用的CPU内核。 在ComponentSystem中,您应该使用后更新命令缓存(PostUpdateCommands)。 同步点仍然会出现,但所有结构变化都是在一个批次中发生的,因此带来的影响略小。为了获得最大效率,请使用JobComponentSystem和实体命令缓存。 在创建大量实体时,您还可以使用一个单独的World来创建实体,然后将这些实体转移到主游戏World。
使用 ForEach 委托来遍历
ComponentSystem提供了Entities.ForEach函数,该函数简化了遍历实体组的工作。 在System的OnUpdate()函数中调用ForEach,传入一个lambda函数并将相关组件作为其参数,然后在函数体内执行你所需要的操作。
以下示例来自HelloCube中的ForEach示例,为具有RotationQuaternion和RotationSpeed组件的所有实体设置旋转动画:
public class RotationSpeedSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach( (ref RotationSpeed rotationSpeed, ref RotationQuaternion rotation) =>
{
var deltaTime = Time.deltaTime;
rotation.Value = math.mul(math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * deltaTime));
});
}
您可以将ForEach lambda函数与最多六种组件类型一起使用。
如果需要对现有实体进行结构级更改,可以将实体组件添加到lambda函数参数中,并使用它将命令添加到ComponentSystem的PostUpdateCommands缓存。(如果系统允许在lambda函数内部立刻进行结构级的更改,则可能会更改正在遍历中的数组中的数据布局,从而导致错误或其他未定义的行为)
For example, if you wanted to remove the RotationSpeed component form any entities whose rotation speed is currently zero, you could alter your ForEach function as follows:
例如,如果当某个实体的RotationSpeed组件的rotation speed为0时,需要移除此RotationSpeed组件,你可以将ForEach函数改成以下形式:
Entities.ForEach( (Entity entity, ref RotationSpeed rotationSpeed, ref RotationQuaternion rotation) =>
{
var __deltaTime __= Time.deltaTime;
rotation.Value = math.mul(math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * __deltaTime__));
if(math.abs(rotationSpeed.RadiansPerSecond) <= float.Epsilon) //Speed effectively zero
PostUpdateCommands.RemoveComponent(entity, typeof(RotationSpeed));
});
当OnUpdate()函数完成以后,系统会在后更新命令缓存中执行相应的命令。
流式查询(Fluent Queries)
您可以使用流式查询( fluent-style )来约束ForEach lambda表达式,使其仅在满足某些约束的特定实体集上执行。 这些查询可以使用any、all或none约束来筛选组件,从而筛选出那些满足条件的实体。多个约束条件可以串联在一起,对于C#的LINQ系统的使用者来说应该非常熟悉。
请注意,作为参数传递给ForEach lambda函数的任何组件,都会自动包含在WithAll集中,并且不能再显式地包含在查询的WithAll,WithAny或WithNone部分中。
WithAll 约束允许您指定目标实体必须包含的组件集{译者注:不限于}。 例如,使用以下查询,ComponentSystem为具有“Rotation”和“Scale”组件的所有实体执行lambda函数:
Entities.WithAll<Rotation, Scale>().ForEach( (Entity e) =>
{
// do stuff
});
将WithAll用于那些存在于实体上,但不需要读取或写入的组件(将要访问的组件作为ForEach lambda函数的参数)。 例如:
Entities.WithAll<SpinningTag>().ForEach( (Entity e, ref Rotation r) =>
{
// do stuff
});
WithAny 约束允许你指定目标实体至少需要包含的组件集。ComponentSystem将在这样的实体上运行以下lambda函数,它们包含了Rotation和Scale组件,且包含RenderDataA或RenderDataB组件(或者同时包含二者):
Entities.WithAll<Rotation, Scale>().WithAny<RenderDataA, RenderDataB>().ForEach( (Entity e) =>
{
// do stuff
});
请注意,你无法知道具体的实体中存在WithAny集合中的哪些组件。 如果需要根据存在哪些组件来区别对待实体,则必须为每种情况创建特定的查询,或者将JobComponentSystem与IJobChunk一起使用。
WithNone 约束允许指定目标实体不允许包含的组件集。 ComponentSystem将为没有Rotation组件的所有实体执行以下lambda函数:
Entities.WithNone<Rotation>().ForEach( (Entity e) =>
{
// do stuff
});
此外,您可以指定WithAnyReadOnly
和WithAllReadOnly
来筛选具有相应组件的实体,然要要确保这些组件必须是以只读方式进行查询。 也就是说,这将确保组件被访问时没有被标记为已写入,且其块ID会发生改变{译者注:块中的每一类组件都对应一个版本ID,当这类组件发生变化时,或者仅仅是被可以写入的Job访问时,此组件对应的版本ID就会发生变化}。
可选项
你还可以指定以下一系列的可选项来查询:
可选项 | 描述 |
---|---|
Default | 无指定选项 |
IncludePrefab | 本次查询不会隐式排除那些拥有特殊预制体标签组件的实体 |
IncludeDisabled | 本次查询不会隐式排除那些拥有特殊禁用标记组件的实体 |
FilterWriteGroup | 本次查询需要考虑组件中设置的WriteGroupAttribute 属性 |
ComponentSystem 将会为所有没有Rotation组件的实体执行以下lambda函数,包含那些存在Disabled组件的实体:
Entities.WithNone<Rotation>().With(EntityQueryOptions.IncludeDisabled).ForEach( (Entity e) =>
{
// do stuff
});