Skip to content

门泊吴船亦已谋

Unity ECS 框架摸索 第六章 更灵活的筛选与遍历

前文中我们用 IJobForEach 或者 JobComponentSystem.Entities.ForEach 都只能筛选并遍历具有特定 Component 的 Entity,但其实有更灵活且同样高效的方式

EntityQuery

Unity 提供了 EntityQuery 用于精确筛选 Entity,并且文档中提到 IJobForEach 与 JobComponentSystem.Entities.ForEach 的内部实现其实都是 EntityQuery

GetEntityQuery

最简单的,我们可以筛选具有指定组件的实体,并规定这些组件的读写权限

例如下面的代码选出了全部的同时拥有 Translation 与 MoveSpeed 组件的 Entity,并且规定 Translation 可读可写,MoveSpeed 只读

EntityQuery m_Query = GetEntityQuery(typeof(Translation), ComponentType.ReadOnly<MoveSpeed>());

EntityQueryDesc

对于更复杂的筛选,我们使用 EntityQueryDesc 来创建 EntityQuery,一个 EntityQueryDesc 对象具有三个属性,它们的类型都是 ComponentType 数组

  • All:数组中所有的类型必须出现在 Entity 中,且可以在其中规定读写权限
  • Any:数组中至少一个类型会出现在 Entity 中
  • None:数组中任意类型都不会出现在 Entity 中

下面举个例子,筛选同时具有 Translation、MoveSpeed,不具有 RotationSpeed,至少具有 Wing 或 magic 中的一个的所有 Entity

var query = new EntityQueryDesc
{
    All = new ComponentType[] { ComponentType.ReadOnly<MoveSpeed>(), typeof(Translation) },
    None = new ComponentType[] { typeof(RotationSpeed) },
    Any = new ComponentType[] { typeof(Magic), typeof(Wing) }
};
EntityQuery m_Group = GetEntityQuery(query);

联合筛选

我们可以在调用 GetEntityQuery 函数时传入 EntityQueryDesc 数组,数组中的筛选条件是「或」的关系,下面是例子

var query1 = new EntityQueryDesc
{
    All = new ComponentType[] { ComponentType.ReadOnly<MoveSpeed>(), typeof(Translation), typeof(Wing) },
    None = new ComponentType[] { typeof(RotationSpeed) }
};
var query2 = new EntityQueryDesc
{
    All = new ComponentType[] { ComponentType.ReadOnly<MoveSpeed>(), typeof(Translation), typeof(Magic) }
};
m_Group = GetEntityQuery(new EntityQueryDesc[] { query1, query2 });

Shared Component 过滤

可以对筛选出的 Entity 拥有的指定 Shared Component 进行过滤,保证过滤出的 Entity 拥有的指定 Shared Component 均为特定值

例如下面代码用一个 Shared Component 作为分组标记,过滤出 GroupId 为 5 的所有 Entity

public struct SharedGroup : ISharedComponentData {
    public int GroupId;
}

public class FlySystem : JobComponentSystem
{
    [BurstCompile]
    struct FlyJob : IJobForEach<MoveSpeed, Translation>
    {
        public void Execute([ReadOnly]ref MoveSpeed moveSpeed, ref Translation translation)
        {
            var v = translation.Value;
            v.y += moveSpeed.MetrePerSecond;
            translation = new Translation { Value = v };
        }
    }
    EntityQuery m_Group;
    protected override void OnCreate()
    {
        m_Group = GetEntityQuery(ComponentType.ReadOnly<SharedGroup>(), ComponentType.ReadOnly<MoveSpeed>(), typeof(Translation));
        m_Group.AddSharedComponentFilter(new SharedGroup());
    }
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        m_Group.SetSharedComponentFilter(new SharedGroup { GroupId = 4 });
        return new FlyJob().Schedule(m_Group, inputDeps);
    }
}

解释:
当前版本官方手册中这个地方的代码有误,上面代码中把 AddSharedComponentFilter 放在 OnCreate,SetSharedComponentFilter 放在 OnUpdate 的用法是我瞎猜的。当前版本的官方手册用的是之前版本的,根本不是这两个函数
另外我碰巧发现,就算没有 Add,直接 Set 它也会自动 Add

版本修改过滤

过滤出指定组件被修改过的

同样也有 Add 和 Set 两个版本,官方手册同样不靠谱

然后下面的代码用于过滤出 Translation 组件经过修改的实体
当然是我瞎猜的也没有试(反正 API 还会改)

m_Group.AddChangedVersionFilter(typeof(Translation));

手动遍历

这种方式可以遍历所有的 Entity,可以灵活访问任意 Entity 的任意 Component,但显然效率低下,官方手册说蛇皮操作推荐用这个

手册说用 IJobParallelFor,懒得写了(反正 API 也会改)(看着语法提示干就完了,奥利给)