Unity ECS Manual [3]

63 篇文章 11 订阅

使用EntityQuery查询数据

  要读取或写入数据,必须首先找到要更改的数据。ECS中的数据存储在组件中,ECS根据它们所属实体的原型在内存中将这些组件分组在一起。您可以使用EntityQuery获得ECS数据的视图,该视图仅包含给定算法或流程所需的特定数据。

  您可以使用EntityQuery执行以下操作:

  • 运行一个Job以处理选定的实体和组件
  • 获取包含所有选定实体的NativeArray
  • 获取选定组件的NativeArrays(按组件类型)

  EntityQuery返回的实体和组件数组保证是“并行的”,也就是说,相同的索引值总是应用于任何数组中的同一实体。

NOTE
SystemBase Entities.ForEach构造基于你为这些API指定的组件类型和属性创建内部的EntityQuery实例。你不能用Entities.ForEach使用不同的EntityQuery对象,(尽管你可以获得Entities.ForEach实例构造的查询对象并在其他地方使用它)。

定义查询

  EntityQuery查询定义了一个archetype必须包含的组件类型集合,以便ECS在视图中包含其块和实体。你也可以排除那些包含特定类型组件的原型。

  对于简单的查询,你可以基于组件类型的数组来创建一个EntityQuery。下面的例子定义了一个EntityQuery,它可以找到所有同时具有RotationQuaternion和RotationSpeed组件的实体。

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

  该查询使用ComponentType.ReadOnly而不是更简单的typeof表达式来指定系统不向RotationSpeed写入。在可能的情况下总是指定只读,因为对数据的读访问限制较少,这可以帮助Job调度器更有效地执行Job。

EntityQueryDesc

  对于更复杂的查询,您可以使用EntityQueryDesc对象来创建EntityQuery。EntityQueryDesc提供了一种灵活的查询机制,可以根据以下组件集指定要选择的原型:

  • All:此数组中的所有组件类型必须存在于原型中
  • Any:原型中必须至少存在此数组中的一个组件类型
  • None:此数组中的任何组件类型都不能存在于原型中

  例如,以下查询包括包含RotationQuaternion和RotationSpeed组件的原型,但排除包含Frozen组件的任何原型:

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

NOTE
不要在EntityQueryDesc中包含可选组件。要处理可选组件,请使用ArchetypeChunk.Has方法确定块是否包含可选组件。因为同一块中的所有实体都有相同的组件,所以您只需要检查每个块是否存在一次可选组件,而不是检查每个实体一次。

Query options(查询选项)

  创建EntityQueryDesc时,可以设置其选项变量。这些选项允许专门的查询(通常您不需要设置它们):

  • Default:未设置任何选项;查询行为正常。
  • IncludePrefab:包括包含特殊预置标记组件的原型。
  • IncludeDisabled:包括包含特殊Disabled标签组件的原型。
  • FilterWriteGroup:考虑查询中任何组件的WriteGroup。

  当你设置了FilterWriteGroup选项时,只有那些在查询中明确包含的WriteGroup中的组件的实体才会包括在视图中。ECS排除了具有同一WriteGroup中任何其他组件的实体。

  在下面的例子中,C2和C3是基于C1的同一个 Write Group 中的组件,这个查询使用的FilterWriteGroup选项需要C1和C3:

public struct C1 : IComponentData { }

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

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

public class ECSSystem : SystemBase
{
    protected override void OnCreate()
    {
        var queryDescription = new EntityQueryDesc
        {
            All = new ComponentType[] { ComponentType.ReadWrite<C1>(),
                                        ComponentType.ReadOnly<C3>() },
            Options = EntityQueryOptions.FilterWriteGroup
        };
        var query = GetEntityQuery(queryDescription);
    }

    protected override void OnUpdate()
    {
        throw new NotImplementedException();
    }
}

  这个查询排除了任何同时具有C2和C3的实体,因为C2没有明确地包括在查询中。虽然你可以使用 "None"来将其设计到查询中,但通过写组来做有一个重要的好处:你不需要改变其他系统使用的查询(只要这些系统也使用WriteGroup)。

  Write Groups是一种机制,你可以用它来扩展现有系统。例如,如果C1和C2被定义在另一个系统中(也许是一个你不控制的库的一部分),你可以把C3放到与C2相同的写组中,以改变C1的更新方式。对于任何你添加到C3组件的实体,系统会更新C1,而原来的系统则不会。对于没有C3的其他实体,原系统会像以前一样更新C1。

有关详细信息,请参阅Write Groups。

Combining queries(组合查询)

  为了结合多个查询,你可以传递一个EntityQueryDesc对象的数组,而不是一个单一的实例。你必须使用逻辑OR操作来组合每个查询。下面的例子选择了任何包含RotationQuaternion组件或RotationSpeed组件(或两者)的原型:

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

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

EntityQuery query
    = GetEntityQuery(new EntityQueryDesc[] { desc1, desc2 });


Creating an EntityQuery(创建EntityQuery)

  在System类外部,可以使用EntityManager.CreateEntityQuery函数创建EntityQuery,如下所示:

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

  然而,在一个System类中,你可以从System中获得一个查询,而不是从头开始创建它。系统会缓存你的实现所创建的任何查询,并返回缓存的实例,而不是尽可能地创建一个新的。

  当你的System使用Entities.ForEach时,使用WithStoreEntityQueryInField来获得Entities.ForEach构造所使用的查询实例。

public partial class RotationSpeedSys : SystemBase
{
    private EntityQuery query;

    protected override void OnUpdate()
    {
        float deltaTime = Time.DeltaTime;

        Entities
            .WithStoreEntityQueryInField(ref query)
            .ForEach(
            (ref RotationQuaternion rotation, in RotationSpeed speed) => {
                rotation.Value
                    = math.mul(
                        math.normalize(rotation.Value),
                            quaternion.AxisAngle(math.up(),
                                speed.RadiansPerSecond * deltaTime)
                     );
            })
            .Schedule();
    }
}

  在其他情况下,比如你需要一个查询的实例来安排一个IJobChunk作业,可以使用GetEntityQuery函数:

public class RotationSystem : SystemBase
{
    private EntityQuery query;

    protected override void OnCreate()
    {
        query = GetEntityQuery(typeof(RotationQuaternion),
               ComponentType.ReadOnly<RotationSpeed>());
    }

    protected override void OnUpdate()
    {
        throw new NotImplementedException();
    }
}

  请注意,缓存查询时不考虑过滤器设置。此外,如果你在一个查询上设置了过滤器,那么在你下次用GetEntityQuery访问同一查询时就会设置相同的过滤器。使用 ResetFilter 来清除任何现有的过滤器。

Defining filters(定义筛选器)

  筛选器会排除原本会包含在查询返回的实体中的实体,这些实体基于以下内容:

  • Shared component filter:根据共享组件的特定值过滤实体集。
  • Change filter:根据特定组件类型的值是否已更改来筛选图元集。

  在对查询对象调用ResetFilter之前,您设置的筛选器将一直有效。

NOTE Write Groups使用不同的机制。请参阅查询选项。

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

  要使用一个共享组件过滤器,在EntityQuery中包含共享组件–与其他需要的组件一起–并调用SetSharedComponentFilter函数。然后传入一个相同的ISharedComponent类型的结构,包含要选择的值。所有的值都必须匹配。你最多可以向过滤器添加两个不同的共享组件。

  你可以在任何时候改变过滤器,但是如果你改变过滤器,它不会改变你从组ToComponentDataArray或ToEntityArray函数中得到的任何现有的实体或组件数组。你必须重新创建这些数组。

  下面的例子定义了一个名为SharedGrouping的共享组件和一个只处理Group字段设置为1的实体的系统。

struct SharedGrouping : ISharedComponentData
{
    public int Group;
}

class ImpulseSystem : SystemBase
{
    EntityQuery query;

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

    protected override void OnUpdate()
    {
        // 只遍历那些将SharedGrouping数据设置为1的实体。
        query.SetSharedComponentFilter(new SharedGrouping { Group = 1 });

        var positions = query.ToComponentDataArray<Position>(Allocator.Temp);
        var displacements = query.ToComponentDataArray<Displacement>(Allocator.Temp);

        for (int i = 0; i < positions.Length; i++)
            positions[i] =
                new Position
                {
                    Value = positions[i].Value + displacements[i].Value
                };
    }
}


Change filters(变化过滤器)

  如果你只需要在一个组件值发生变化时更新实体,你可以使用SetChangedVersionFilter函数将该组件添加到EntityQuery过滤器中。例如,下面的EntityQuery只包括来自另一个系统已经写入 Translation 组件的块的实体:


EntityQuery query;

protected override void OnCreate()
{
    query = GetEntityQuery(typeof(LocalToWorld),
            ComponentType.ReadOnly<Translation>());
    query.SetChangedVersionFilter(typeof(Translation));

}

NOTE
为了提高效率,变化过滤器应用于整个块,而不是单个实体。变化过滤器也只检查一个系统是否已经运行,声明了对该组件的写入权限,而不是实际改变任何数据。换句话说,如果另一个有写入能力的类型组件的Job访问了该块,那么变化过滤器就会包括该块中的所有实体。这就是为什么你应该始终声明对你不需要修改的组件的只读访问。

Executing the query(执行查询)

  通常情况下,当你安排一个使用查询的Job时,你会 "执行 "该查询。你也可以调用EntityQuery方法之一,该方法返回实体、组件或块的数组:

  • ToEntityArray返回选定实体的数组。
  • ToComponentDataArray返回所选实体的T类型组件的数组。
  • CreateArchetypeChunkArray返回包含所选实体的所有块。因为查询的操作对象是原型、共享组件值和变化过滤器,这些对于一个块中的所有实体都是相同的,所以存储在返回的块集合中的实体集与ToEntityArray返回的实体集完全相同。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值