以下文档均来源于ECS官网:
https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/ecs_entities.html
WriteGroups(写入组)
常见的ECS模式是让一个系统读取一组输入组件,然后写出到另一组输出组件。 但是,您可能希望覆盖该系统并根据您自己的输入集来更新输出组件。
WriteGroups允许您在不必更改原有系统的情况下,重新定义该系统是否写入指定的组件。 WriteGroup属性用来标识当前组件将成为指定组件的写入源。 对于那些定义了WriteGroup属性的组件,系统在筛选将要更新的实体时,还必须对EntityQuery筛选对象启用WriteGroup选项。
使用WriteGroup属性来定义WriteGroup。此属性将输出目标组件的类型作为参数。 在需要更新目标组件时,需要将该属性放在将会修改目标组件的每个源组件上。 例如,以下声明指定了组件A是针对目标组件W的写入组的一部分:
[WriteGroup(typeof(W))]
public struct A : IComponentData{ public int Value; }
请注意,WriteGroup的目标组件必须包含在查询中,并以可写方式进行访问。否则,该查询的WriteGroup将被忽略。
当你在查询中启用WriteGroup选项时,查询会将所有位于WriteGroup中的组件添加到查询的None列表中,除非它们已经被显式添加到All或Any列表中。因此,如果查询将会仅仅选中这样的实体--如果实体上的某个组件位于一个WriteGroup,那么此组件必须被明确地定义在查询中。如果一个实体具有来自该WriteGroup的一个或多个附加组件,则本查询将会拒绝选择它。
到目前为止,WirteGroup无法帮你做那些连重写查询都无法做到的事情。 但是,当您使用的是无法覆盖的系统时,它的优势将体先出来。 您可以将自己的组件添加到该系统定义的任何WriteGroup中,当您将该组件与预先存在的组件一起放在实体上时,系统将不再选择和更新该实体。 此时,您自己的系统可以在不与其他系统争用的情况下更新实体。
WriteGroup 示例:
给定:
- 组件 A 和 B位于 针对组件W的WriteGroup
- 查询:
- 所有的: A, W
- 启用 WriteGroup 筛选
-
实体:
Entity X Entity Y A A W B W
本次查询将会选择实体X,而不选择实体Y。
未选择实体Y,因为它具有组件B,它是同一个WriteGroup的一部分,但查询不需要。 启用WriteGroup筛选会将查询更改为:
- All: A, W
- None: B
如果没有WriteGroup筛选选项,那么查询将会同时选择实体X和Y。
注意: 有关更多示例,您可以查看Unity.Transforms代码,该代码对其更新的每个组件都使用了WriteGroups,包括LocalToWorld。
创建WriteGroup
您可以通过将WriteGroup属性添加组件的声明中来创建WriteGroup。 WriteGroup属性采用一个参数,该参数是该写入组的目标写入组件类型。 一个组件可以隶属于多个写入组。{译者注:也就是让当前组件成为多个其它组件的写入源}
例如,如果组件W的写入源 = A + B,那么你可以像以下那样为W定义WriteGroup:
public struct W : IComponentData
{
public int Value;
}
[WriteGroup(typeof(W))]
public struct A : IComponentData
{
public int Value;
}
[WriteGroup(typeof(W))]
public struct B : IComponentData
{
public int Value;
}
请注意,您不能将WriteGroup的目标(上例中的struct W)添加到它自己的WriteGroup中。
启用 WriteGroup 筛选
若需要启动WriteGroup 筛选, 你需要将你查询使用的EntityQueryDesc对象的Options选项设置为FilterWriteGroup:
public class AddingSystem : JobComponentSystem
{
private EntityQuery m_Query;
protected override void OnCreate()
{
var queryDescription = new EntityQueryDesc
{
All = new ComponentType[] {typeof(A), typeof(B)},
Options = EntityQueryOptions.FilterWriteGroup
};
m_Query = GetEntityQuery(queryDescription);
}
// Define Job and schedule...
}
覆盖使用了写入组的另一个系统
如果系统为其写入的组件定义WriteGroup,那么你就可以覆盖该系统并使用您自己的系统写入这些组件。 要覆盖系统,请将您自己的组件添加到该系统定义的WriteGroups中。 由于WriteGroup选项会排除位于WriteGroup中,而查询中未明确要求的任何组件,因此任何具有你自己的组件的实体都将被其他系统忽略{译者注:这个概念好像被重复说了好几遍}。
例如,如果要通过指定角度和旋转轴来设置实体的方向,可以创建一个组件和一个系统,将角度和旋转轴的值转换为四元数,并将其写入Unity.Transforms.Rotation组件。 为了防止Unity.Transforms系统更新Rotation组件,无论你的其他组件是什么,你都可以将你的组件放在针对Rotation的WriteGroup中:
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
[Serializable]
[WriteGroup(typeof(Rotation))]
public struct RotationAngleAxis : IComponentData
{
public float Angle;
public float3 Axis;
}
然后,你可以更新那些包含RotationAngleAxis组件的实体,而避开对Rotation组件写入过程的争用。
{译者注:如果让筛选结果同时包含了系统定义的写入组件和我自己定义的写入组件,它们之间可能会同时写入Rotation组件,必然产生竞争,造成同步点}
using Unity.Burst;
using Unity.Entities;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Transforms;
public class RotationAngleAxisSystem : JobComponentSystem
{
[BurstCompile]
struct RotationAngleAxisSystemJob : IJobForEach<RotationAngleAxis, Rotation>
{
public void Execute([ReadOnly] ref RotationAngleAxis source, ref Rotation destination)
{
destination.Value = quaternion.AxisAngle(math.normalize(source.Axis), source.Angle);
}
}
protected override JobHandle OnUpdate(JobHandle inputDependencies)
{
var job = new RotationAngleAxisSystemJob();
return job.Schedule(this, inputDependencies);
}
}
扩展另一个使用WriteGroups的系统
如果您想扩展其他系统而不是仅覆盖它,甚至,您希望允许将来实现的系统可以覆盖或扩展您当前的系统,那么您可以在您自己的系统上启用WriteGroup筛选。 但是要注意的是,执行此操作后,默认情况下,存在位于WriteGroup的组件的任意组合,都不能通过任何一个系统的隐式查询。 您必须显式查询并处理每个组合。
作为一个例子,让我们回到前面描述的AddingSystem示例,它定义了一个针对写入目标组件W的组件A和B的WriteGroup。如果你只是添加一个新组件到WriteGroup,称之为“C”,那么新系统即可以查询包含C的实体,就算这些实体也有组件A或B也没关系。但是,如果新系统也启用了WriteGroup筛选,那就不再适用了。 如果您只需要组件C,那么WriteGroup过滤将排除任何具有A或B的实体。相反,您必须显式查询有意义的每个组件组合。 (您可以在适当的时候使用查询的“Any”子句。)
var query = new EntityQueryDesc
{
All = new ComponentType[] {ComponentType.ReadOnly<C>(), ComponentType.ReadWrite<W>()},
Any = new ComponentType[] {ComponentType.ReadOnly<A>(), ComponentType.ReadOnly<B>()},
Options = EntityQueryOptions.FilterWriteGroup
};
如果某些实体中包含这样的位于WriteGroup中的组件,而该组件又未被显式处理,那么这样的实体不会被写入该WriterGroup目标组件(且WriteGroups筛选开启)的任何系统处理。然而,更要紧的是,在程序中创建这样的实体很可能是一个逻辑错误。