来源:https://docs.unity3d.com/Packages/[email protected]/manual/index.html
我会对官方文档内容略作整理,有需要可以查看官方文档
常见的ECS模式是系统读取一组输入的组件然后写出另一组输出的组件,但是,有时候你想重写这个系统,统并根据自己的输入集来更新输出组件
WriteGroups允许重写一个在没有重写系统的情况下写入组件的系统,WriteGroup定义了一组用作写入特定组件的源的组件,定义了WriteGroup的系统还必须对其选择要更新的实体的EntityQuery启用WriteGroup过滤
使用WriteGroup时需要定义WriteGroup属性,此属性将目标类型的输出组件作为参数,在更新目标组件时,将该属性放在用作源的组件上,例如,以下声明指定了组件A是WriteGroup目标组件W的一部分:
[WriteGroup(typeof(W))]
public struct A : IComponentData{ public int Value; }
请注意,WriteGroup的目标组件必须包含在查询中,并以可写方式进行访问。否则,将忽略该查询的WriteGroup
在查询中启用WriteGroup过滤时,除非您将它们显式添加到All或Any列表中,否则查询会将WriteGroup中的所有组件添加到查询的None列表中,因此,只会查询一个每个实体上的组件都是指定WriteGroup查询时所需要的实体,如果该实体具有来自该WriteGroup的一个或多个附加组件,则查询就会拒绝查询
到目前为止,WriteGroups不会做任何你无法实现的事情除了重写查询,但是,当你使用无法重写的系统时会带来好处——你可以将自己的组件添加到该系统定义的任何WriteGroup中,当你将该组件与预先存在的组件一起放在实体上时,系统不再选择和更新该实体,然后,你自己的系统可以在不与其他系统争用的情况下更新实体
WriteGroup例子:
条件:
Entity X | Entity Y |
---|---|
A | A |
W | B |
W |
查询会选择实体X,但不选择Y
没有选择实体Y,因为它具有组件B,它是同一WriteGroup的一部分,但查询并不需要,启用WriteGroup过滤会将查询更改为:
如果没有WriteGroup过滤,查询将选择实体X和Y
你可以通过将WriteGroup属性添加到WriteGroup中每个组件的声明来创建WriteGroups,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过滤,请在创建查询的查询描述对象上设置FilterWriteGroups选项
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;
}
You can then update any entities containing RotationAngleAxis without contention:
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);
}
}
如果你想扩展其他系统而不仅仅是覆盖它,并且你希望允许将来的系统覆盖或扩展你的系统,那么你可以在你自己的系统上启用WriteGroup过滤,但是,默认情况下执行此操作时,任何一个默认系统都不会处理这个组件组合,您必须显式查询并处理每个组合
比如说,让我们回到前面描述的AddingSystem示例,它定义了一个包含目标组件W的组件A和B的WriteGroup,如果你只是添加一个新组件,称之为“C”,到 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中的没有被显示调用的组件组合的实例都不会被任何写入目标组的系统处理,但是,在程序最初创建这样的实体很可能是一个逻辑错误