ECS框架文档翻译十七 Component Groups

以下文档均来源于ECS官网:

https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/ecs_entities.html

使用EntityQuery查询数据

读写数据的第一步是查找数据。 ECS框架中的数据是存储在组件中的,组件根据它们所属实体的原型,在内存中被组合在一起。 要定义ECS的数据视图,让其仅包含给你需要计算或处理的特定数据,您可以构建一个EntityQuery。

当创建完一个EntityQuery,你可以

  • 运行一个Job来处理该视图筛选的那些实体和组件
  • 获取一个包含筛选结果实体的NativeArray
  • 获取一个包含筛选结果组件的NativeArrays(通过组件类型)

EntityQuery返回的实体和组件数组,被确保是可以并行(parallel)的,也就是说,在数组中,同样的index值,始终可以索引相同的实体。

注意: ComponentSystem.Entites.ForEach 委托和 IJobForEach ,会在内部创建多个基于组件类型及其指定属性的EntityQuery,以用于外部API调用

定义query

EntityQuery查询定义了原型必须包含的组件类型集,他会将筛选后的块和实体包含在视图中。 您还可以排除包含特定类型组件的原型。

对于简单查询来说,您可以基于组件类型数组创建EntityQuery。 以下示例定义了一个EntityQuery,它将查找包含RotationQuaternion和RotationSpeed组件的所有实体。

EntityQuery m_Group = GetEntityQuery(typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>());

本查询使用 ComponentType.ReadOnly<T> 而不是简单的typeof 表达式来指定系统不会对RotationSpeed组件执行写入。在可以的情况下,应该始终指定只读,因为对数据读取的访问限制较少,这可以帮助Job调度程序更有效地执行作业。

EntityQueryDesc

对于更复杂的情况,你可以使用EntityQueryDesc来创建EntityQuery。EntityQueryDesc提供了一个灵活的查询机制,它将基于以下组件约束来筛选原型。

  • All = 原型中存必须包含该数组中指定的所有的组件类型
  • Any = 原型中至少包含一个该数组指定的组件类型
  • None = 原型中存不能包含该数组中指定的任意的组件类型

例如,以下的查询结果包含这样的原型-它们的组件中包含RotationQuaternion和RotationSpeed组件,但是不包含Frozen组件

var query = new EntityQueryDesc
{
   None = new ComponentType[]{ typeof(Frozen) },
   All = new ComponentType[]{ typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>() }
}
EntityQuery m_Group = GetEntityQuery(query);

注意: 不要在EntityQueryDesc中包含完全可选的组件{译者注:完全可选组件指那些未出现在查询条件中,筛选后的实体却会包含的组件}。 要处理可选组件,请使用IJobChunk.Execute()中的chunk.Has<T>() 方法来确定当前ArchetypeChunk是否具有可选组件。 由于同一块中的所有实体具有相同的组件,因此您只需要检查每个块是否存在一个可选组件 - 而不需要每个实体检查一次。

查询选项

当你创建一个EntityQueryDesc,你还可以设置他的Options 变量. options变量允许进行一些特殊的查询(通常您不需要设置它们):

  • Default — 无设置选项;通常的查询行为.
  • IncludePrefab — 包括拥有特殊预制标签组件的原型
  • IncludeDisabled — 包括拥有特殊禁用标记组件的原型
  • FilterWriteGroup — 考虑任何被查询组件的WriteGroup属性

当你设置了 FilterWriteGroup 选项,如果存在一些组件来自于某一个Write Group(写入组),那么只有它们显式地被包含在查询条件中,对应的实体才会包含在视图中,如果某些实体包含位于同一WriteGroup的任何其他组件,它们将会排除在外。{译者注:也就是每一类WriteGroup来说,尽可能同时只允许一个结构来执行访问}

举例来说,假如C2和C3时位于同一个基于C1的Write Group{译者注:意思是C2、C3都会对C1产生写操作},你可以用以下的方式使用FilterWriteGroup选项来创建针对C1和 C3的查询:

public struct C1: IComponentData{}

[WriteGroup(C1)]
public struct C2: IComponentData{}

[WriteGroup(C1)]
public struct C3: IComponentData{}

// ... In a system:
var query = new EntityQueryDesc{
    All = new ComponentType{typeof(C1), ComponentType.ReadOnly<C3>()},
    Options = EntityQueryDescOptions.FilterWriteGroup
};
var m_group = GetEntityQuery(query);

本查询将排除同时具有C2和C3的任何实体,因为C2未明确包含在查询中。 虽然您也可以使用None将其设计为查询的约束条件,但通过Write Group执行此操作可以提供一个重要的好处:您无需更改其他系统使用的查询条件(只要这些系统也使用Write Group)。{译者注:其它系统可能会对C2只读,而需要屏蔽C3,又需要增加一次None来过滤C3,当情况更为复杂,写入组被更多的组件共享时,这种查询条件也会更难以维护,使用写入组就可以很好地避免这种尴尬}

写入组是一种允许您扩展现有系统的机制。 例如,如果C1和C2在另一个系统A中定义(可能是您无法控制的库的一部分),则可以将C3放入与C2相同的写入组中,以便更改C1的更新方式。 对于添加C3组件的任何实体,系统将更新C1,而原始系统不会。 对于没有C3的其他实体,原始系统将像以前一样更新C1。
{
译者注:这里得原始系统指的系统A,它内部的形式应该是:

public struct C1: IComponentData{}

[WriteGroup(C1)]
public struct C2: IComponentData{}


// ... In a system:
var query = new EntityQueryDesc{
    All = new ComponentType{typeof(C1), ComponentType.ReadOnly<C2>()},
    Options = EntityQueryDescOptions.FilterWriteGroup
};
var m_group = GetEntityQuery(query);

}

查看 Write Groups 以获取更多信息

组合查询

您还可以通过传递EntityQueryDesc对象数组,而不是单个实例来进行组合查询。 每个查询间使用逻辑或运算来进行组合。 以下示例筛选出包含RotationQuaternion组件或RotationSpeed组件(或包含两者)的原型:

var query0 = new EntityQueryDesc
{
   All = new ComponentType[] {typeof(RotationQuaternion)}
};

var query1 = new EntityQueryDesc
{
   All = new ComponentType[] {typeof(RotationSpeed)}
};

EntityQuery m_Group = GetEntityQuery(new EntityQueryDesc[] {query0, query1});

创建EntityQuery

在System类的外部,你可以EntityManager.CreateEntityQuery()函数来创建EntityQuery:

EntityQuery m_Group = CreateEntityQuery(typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>());

然而,在系统类的内部,你应该使用GetEntityQuery()函数{译者注:因为系统内部默认会创建一系列的查询结构以满足诸如WithAll,WithAny之类的API调用,所以这里只需Get即可,是这样嘛【?】}:

public class RotationSpeedSystem : JobComponentSystem
{
   private EntityQuery m_Group;
   protected override void OnCreate()
   {
       m_Group = GetEntityQuery(typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>());
   }
   //…
}

为了提高效率,同时避免不必要地创建引用类型(会造成垃圾收集),您应该在系统的OnCreate()函数中为系统创建EntityQuery,并将结果存储在成员变量中(在上面的示例中,m_Group变量用于此目的)。

定义筛选器

除了查询中定义的必须包含或排除那些组件之外,您还可以为视图指定以下的筛选类型:

  • Shared component values —根据共享组件的不同数值样本,来筛选实体集
  • Change filter — 根据指定类型的组件是否可能已经发生了变化,来筛选实体集

共享组件筛选器(Shared component filters)

要使用共享组件筛选器,首先要在EntityQuery中包含该共享组件(以及其他所需组件),然后调用SetFilter()函数,传入一个ISharedComponent类型的结构,进行筛选时,所有的结果的ISharedComponent组件的值必须与传入组件值相同。 您最多可以向筛选器添加两个不同的共享组件。

您可以随时更改筛选器,但更改筛选器不会更改从组ToComponentDataArray()或ToEntityArray()函数接收的任何现有实体或组件数组。 您必须重新创建这些数组。

以下示例定义名为SharedGrouping的共享组件,以及一个仅处理Group字段设置为1的实体的系统。

struct SharedGrouping : ISharedComponentData
{
    public int Group;
}

class ImpulseSystem : ComponentSystem
{
    EntityQuery m_Group;

    protected override void OnCreate(int capacity)
    {
        m_Group = GetEntityQuery(typeof(Position), typeof(Displacement), typeof(SharedGrouping));
    }

    protected override void OnUpdate()
    {
        // Only iterate over entities that have the SharedGrouping data set to 1
        m_Group.SetFilter(new SharedGrouping { Group = 1 });

        var positions = m_Group.ToComponentDataArray<Position>(Allocator.Temp);
        var displacememnts = m_Group.ToComponentDataArray<Displacement>(Allocator.Temp);

        for (int i = 0; i != positions.Length; i++)
            positions[i].Value = positions[i].Value + displacememnts[i].Value;
    }
}

更改筛选器(Change filters)

如果只需在某些组件的值更改时才更新实体,则可以使用SetFilterChanged()函数将该组件添加到EntityQuery筛选器。 例如,以下EntityQuery,仅包含那些已经被另一个系统写入过Translation组件的块中的实体:

protected override void OnCreate(int capacity)
{
    m_Group = GetEntityQuery(typeof(LocalToWorld), ComponentType.ReadOnly<Translation>());
    m_Group.SetFilterChanged(typeof(Translation));
}

请注意,为了提高效率,更改过滤器适用于整个块而不是单个实体。 更改筛选器也仅仅是检查系统是否已经运行过那些Job,那些声明了对组件拥有写访问权限的job,而不是它是否真有数据更改。 换句话说,如果一个块中存在更改筛选器中的组件类型C,且此块已被另一个能够写入C类组件的Job访问,则更改筛选器将选中该块中的所有实体。(这是您应该始终对不需要修改的组件设置只读访问权限的另一个原因。)

执行查询

当您在作业中使用EntityQuery,或者调用EntityQuery中一个方法来返回视图中的实体、组件或块的数组时,EntityQuery会执行其筛选查询:

  • ToEntityArray() 返回被选中实体的数组
  • ToComponentDataArray<T> 返回一个关于被选中实体的T类型的组件数组
  • CreateArchetypeChunkArray()返回包含所选实体的块集。 (由于查询是针对原型、共享组件数值和更改筛选器进行操作的,这些操作对于块中的所有实体都是相同的,因此存储在返回的块集中的实体集与 ToEntityArray()方法返回的实体集完全相同。

在Job中

在JobComponentSystem中,将EntityQuery对象传递给系统的Schedule() 方法。 在以来自HelloCube的IJobChunk示例中,m_Group参数是EntityQuery对象

// OnUpdate runs on the main thread.
protected override JobHandle OnUpdate(JobHandle inputDependencies)
{
    var rotationType = GetArchetypeChunkComponentType<Rotation>(false); 
    var rotationSpeedType = GetArchetypeChunkComponentType<RotationSpeed>(true);

    var job = new RotationSpeedJob()
    {
        RotationType = rotationType,
        RotationSpeedType = rotationSpeedType,
        DeltaTime = Time.deltaTime
    };

    return job.Schedule(m_Group, inputDependencies);
}

EntityQuery在内部使用Job来创建所需的数组。当你将m_Group参数传递给Schedule()方法时,EntityQuery作业将被与系统自己的作业一起调度,并可以利用并行处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值