ECS Acess entities data 二

Using ComponentSystem

你可以使用 ComponentSystem 访问你的数据 data. ComponentSystem方法在主线程上运行,因此不能利用多个CPU核心. 在下列情况下使用 ComponentSystems :

  • 调试或探索性开发 — 有时候,当代码在主线程上运行时,更容易观察到发生了什么.例如,您可以记录调试文本并绘制调试图形。
  • 当系统需要访问或使用只能在主线程上运行的其他api时 — 这可以帮助你逐渐地将游戏系统转换成ECS,而不是从一开始就必须重写所有内容。
  • 系统执行的工作量小于创建和调度作业的小开销。
  • 当遍历实体时需要对其直接进行结构更改时 (add/remove components, destroy entities, etc.) .不像 JobComponentSystem, ComponentSystems 可以直接在ForEach lambda 方法里面更改实体.

Important: 结构上的改变迫使完成所有job. 这个事件叫做一个同步点 a sync point and会导致性能下降,因为系统在等待同步点时不能利用所有可用的CPU内核. 在一个 ComponentSystem内, 你应该使用一个 post-update command buffer. 同步点仍然会发生,但是所有的结构变化都是成批发生的,所以它的影响稍微小一些.为了获得最大的效率,可以使用 JobComponentSystem and an entity command buffer. 在创建大量实体时,您还可以使用一个单独的世界来创建实体,然后将这些实体转移到主游戏世界。

Iterating with ForEach delegates

 ComponentSystem 提供 Entities.ForEach 方法遍历所有实体. Call ForEach 在系统的 OnUpdate() 方法里面调用 ForEach方法,把相关的组件传递给lambda表达式的方法

下面的例子来自HelloCube ForEach示例,它为任何具有RotationQuaternion和RotationSpeed组件的实体进行旋转:

public class RotationSpeedSystem : ComponentSystem
{
   protected override void OnUpdate()
   {
       Entities.ForEach( (ref RotationSpeed rotationSpeed, ref RotationQuaternion rotation) =>
       {
           var deltaTime = Time.deltaTime;
           rotation.Value = math.mul(math.normalize(rotation.Value),
               quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * deltaTime));
       });
   }

您可以使用带有最多六种组件类型的ForEach lambda函数。

与 JobComponentSystems不同,你可以在ForEach内部进行更改实体的结构

For example, if you wanted to remove the RotationSpeed component form any entities whose rotation speed is currently zero, you could alter your ForEach function as follows:

Entities.ForEach( (Entity entity, ref RotationSpeed rotationSpeed, ref RotationQuaternion rotation) =>
{
   var __deltaTime __= Time.deltaTime;
   rotation.Value = math.mul(math.normalize(rotation.Value),
       quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * __deltaTime__));

   if(math.abs(rotationSpeed.RadiansPerSecond) <= float.Epsilon) //Speed effectively zero
       EntityManager.RemoveComponent(entity, typeof(RotationSpeed));
});

当组件系统在主线程上运行时,系统可以安全地执行这些命令。

Entity Queries

  你可以使用 fluent-style 查询来特定的实体,约束可以链接在一起,对于c#的LINQ系统的用户来说应该非常熟悉。

注意,作为参数传递给ForEach lambda函数的任何组件都将自动包含在WithAll集合中,而不能显式地包含在查询的WithAll、WithAny或WithNone部分中。

A WithAll 包含所有组件的实体.下面表示含有 Rotation and Scale component:

Entities.WithAll<Rotation, Scale>().ForEach( (Entity e) =>
{
    // do stuff
});

对于必须存在于实体上但不需要读写的组件(添加要访问的组件,作为ForEach lambda函数的参数,使用WithAll

Entities.WithAll<SpinningTag>().ForEach( (Entity e, ref Rotation r) =>
{
    // do stuff
});

A WithAny至少含有一个组件. The ComponentSystem executes the following lambda function for all entities that have both Rotation and Scale, AND either RenderDataA or RenderDataB (or both):

Entities.WithAll<Rotation, Scale>().WithAny<RenderDataA, RenderDataB>().ForEach( (Entity e) =>
{
    // do stuff
});

请注意,无法知道WithAny集合中的哪些组件存在于特定实体中. 如果需要根据这些组件的存在以不同的方式对待实体,则必须为每种情况创建特定的查询, or use a JobComponentSystem with IJobChunk.

A WithNone 至少不含有组件其中的一个. The ComponentSystem executes the following lambda function for all entities that do not have a Rotation component:

Entities.WithNone<Rotation>().ForEach( (Entity e) =>
{
    // do stuff
});

另外,您可以指定 WithAnyReadOnly and WithAllReadOnly ,来查找符合特定条件组件的实体 ,这将确保它们没有被标记为已写入的,它们的块id也没有改变。

Options

You can also specify a number of options for a query using With:

Option Description
Default No options specified.
IncludePrefab The query does not implicitly exclude entities with the special Prefab component.
IncludeDisabled The query does not implicitly exclude entities with the special Disabled component.
FilterWriteGroup The query should filter selected entities based on the WriteGroupAttribute settings of the components specified in the query.

The ComponentSystem executes the following lambda function for all entities that do not have a Rotation component, including those that do have the special Disabled component:

Entities.WithNone<Rotation>().With(EntityQueryOptions.IncludeDisabled).ForEach( (Entity e) =>
{
    // do stuff
});

Using IJobChunk

您可以在JobComponentSystem中实现IJobChunk来按块遍历数据. JobComponentSystem 为每一个包含特定实体的块执行一次 Execute().然后可以逐个实体地处理每个块中的数据。

使用IJobChunk迭代比IJobForEach需要更多的代码设置,但也更显式,并且表示对数据的最直接访问,因为它实际上是存储的

使用块迭代的另一个好处是,您可以检查每个块中是否存在可选组件(with Archetype.Has<T>())并相应地处理块中的所有实体。

实现IJobChunk作业的步骤包括:

  1. 通过创建EntityQuery来标识要处理的实体.
  2. 定义一个 Job 结构体, 包含一个ArchetypeChunkComponentType 对象的字段,用来标识job直接访问的组件的类型 ,从而指定job是否来读取这些组件
  3. 创建Job struct的实例,并在system的 OnUpdate()方法里面执行它.
  4. Execute() 方法里, 得到job读取或写入的组件的 NativeArray ,最后遍历当前的块来做相应的工作

The ECS samples repository contains a simple HelloCube example that demonstrates how to use IJobChunk.

Query for data with a EntityQuery

EntityQuery定义原型必须包含的组件类型集,以便系统处理关联的块和实体. 原型也可以有其他组件,但它必须至少有EntityQuery定义的那些组件。您还可以排除包含特定类型组件的原型。

你可以使用 JobComponentSystem.GetEntityQuery() function, passing in the component types:

public class RotationSpeedSystem : JobComponentSystem
{
    private EntityQuery m_Query;

    protected override void OnCreate()
    {
        m_Query = GetEntityQuery(ComponentType.ReadOnly<Rotation>(),
                                 ComponentType.ReadOnly<RotationSpeed>());
        //...
    }

对于更复杂的情况,可以使用 EntityQueryDesc. An EntityQueryDesc提供灵活的查询机制来指定组件类型:

  • All = All component types in this array must exist in the archetype
  • Any = At least one of the component types in this array must exist in the archetype
  • None = 此数组中的任何组件类型都不能存在于原型中

For example, the following query includes archetypes containing the RotationQuaternion and RotationSpeed components, but excludes any archetypes containing the Frozen component:

protected override void OnCreate()
{
    var queryDescription = new EntityQueryDesc()
    {
        None = new ComponentType[]
        {
            typeof(Static)
        },
        All = new ComponentType[]
        {
            ComponentType.ReadWrite<Rotation>(),
            ComponentType.ReadOnly<RotationSpeed>()
        }
    };
    m_Query = GetEntityQuery(queryDescription);
}

ComponentType.ReadOnly<T> 代替简单的 typeof 表示只读组件

你也可以传递一个EntityQueryDesc 对象数组,而不是一个简单的实例.


protected override void OnCreate()
{
    var queryDescription0 = new EntityQueryDesc
    {
        All = new ComponentType[] {typeof(Rotation)}
    };

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

    m_Query = GetEntityQuery(new EntityQueryDesc[] {queryDescription0, queryDescription1});
}

Note:完全不包括 EntityQueryDesc其中的组件. 要处理选择性的组件,在 IJobChunk.Execute()内部使用 chunk.Has<T>() 来决定当前 ArchetypeChunk是否含有该组件.因为所有在同一个块中的实体含有相同的组件,所以你只需要在块中检测一次就行了,不用每个实体都检测

为了提高效率并避免不必要地创建垃圾收集的引用类型, 您应该在系统的OnCreate()函数中为系统创建EntityQueries,并将结果存储在实例变量中. (In the above examples, the m_Query variable is used for this purpose.)

Define the IJobChunk struct

 IJobChunk struct 定义job运行时需要的字段,和jobs的Execute() 方法.

为了在块中访问传递给 Execute() 方法的组件,你必须为job读写的每一个类型的组件创建一个  ArchetypeChunkComponentType<T> 对象.这些对象允许你得到NativeArrays 的实例,然后通过它来访问实体上的组件. 包括EntityQuery 引用的所有组件,或者说是Execute()方法读写的组件,你还可以用ArchetypeChunkComponentType 变量来表示那些EntityQuery没有查找到的组件类型 (在访问该组件之前,你必须在块中检测块中是否包含该组件)

For example, the HelloCube IJobChunk example declares a Job struct that defines ArchetypeChunkComponentType<T> variables for two components, RotationQuaternion and RotationSpeed:


[BurstCompile]
struct RotationSpeedJob : IJobChunk
{
    public float DeltaTime;
    public ArchetypeChunkComponentType<Rotation> RotationType;
    [ReadOnly] public ArchetypeChunkComponentType<RotationSpeed> RotationSpeedType;

    public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    {
        // ...
    }
}

系统在OnUpdate()函数中为这些变量赋值。当ECS框架运行作业时,将在Execute()方法中使用这些变量

The Job 还可以使用 Unity delta time 做3D物体的旋转动画, The example also passes this value to the Execute() method using a struct field.

Writing the Execute method

IJobChunk Execute() 语法:


public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)

chunk 表示一个包含实体和组件的内存块,用来在job对其进行遍历操作. 由于块只能包含一个原型,所以块中的所有实体都具有相同的组件集。

 使用 chunk 去得到包含组件 NativeArray 的实例:


var chunkRotations = chunk.GetNativeArray(RotationType);//这里面是一个数组,每一个组件都代表了一个实体
var chunkRotationSpeeds = chunk.GetNativeArray(RotationSpeedType);

这些数组是对齐的,因此一个实体在所有这些数组中具有相同的索引,然后使用chunk.Count得到当前块中的组件的数量,用for来遍历


var chunkRotations = chunk.GetNativeArray(RotationType);
var chunkRotationSpeeds = chunk.GetNativeArray(RotationSpeedType);
for (var i = 0; i < chunk.Count; i++)
{
    var rotation = chunkRotations[i];
    var rotationSpeed = chunkRotationSpeeds[i];

    // Rotate something about its up vector at the speed given by RotationSpeed.
    chunkRotations[i] = new Rotation
    {
        Value = math.mul(math.normalize(rotation.Value),
            quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * DeltaTime))
    };
}

如果在EntityQueryDesc中使用 Any 检索,或者有的组件并没有设为查询的条件,你可以使用 ArchetypeChunk.Has<T>()方法来检测当前块中是否含有这些组件:

if (chunk.Has<OptionalComp>(OptionalCompType))
{//...}

Note:如果你使用concurrent entity command buffer,传递chunkIndex 参数作为jobIndex 参数to the command buffer functions.

Skipping chunks with unchanged entities:跳过块中未更改的的实体

 如果你只需要在组件改变时更新实体,你可以在EntityQuery中使用change filter属性,来只选择那些更改了组件值得实体.比如,如果你的系统需要读取两个组件,当这两个组件其中的一个更改时,才会更新第三个组件,就可以使用EntityQuery :


private EntityQuery m_Query;

protected override void OnCreate()
{
    m_Query = GetEntityQuery(
        ComponentType.ReadWrite<Output>(),
        ComponentType.ReadOnly<InputA>(),
        ComponentType.ReadOnly<InputB>());
    m_Query.SetChangedVersionFilter(
        new ComponentType[]
        {
            ComponentType.ReadWrite<InputA>(),
            ComponentType.ReadWrite<InputB>()
        });
}

 EntityQuery change filter最多支持两个组件.如果您想要进行更多的检查,或者不使用EntityQuery,您可以手动进行检查. 比较块中组件更改的版本和系统的 LastSystemVersion版本作比较,使用的是, ArchetypeChunk.DidChange() 方法. 如果返回false,您可以完全跳过当前块,因为自上次系统运行以来,该类型的组件没有发生任何更改.

来自系统的LastSystemVersion必须使用struct字段传递到作业中:


[BurstCompile]
struct UpdateJob : IJobChunk
{
    public ArchetypeChunkComponentType<InputA> InputAType;
    public ArchetypeChunkComponentType<InputB> InputBType;
    [ReadOnly] public ArchetypeChunkComponentType<Output> OutputType;
    public uint LastSystemVersion;

    public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    {
        var inputAChanged = chunk.DidChange(InputAType, LastSystemVersion);
        var inputBChanged = chunk.DidChange(InputBType, LastSystemVersion);

        // If neither component changed, skip the current chunk
        if (!(inputAChanged || inputBChanged))
            return;

        var inputAs = chunk.GetNativeArray(InputAType);
        var inputBs = chunk.GetNativeArray(InputBType);
        var outputs = chunk.GetNativeArray(OutputType);

        for (var i = 0; i < outputs.Length; i++)
        {
            outputs[i] = new Output{ Value = inputAs[i].Value + inputBs[i].Value };
        }
    }
}

 所有 Job 结构体中的字段,必须在scheduling job之前给它们声明值:


protected override JobHandle OnUpdate(JobHandle inputDependencies)
{
    var job = new UpdateJob();

    job.LastSystemVersion = this.LastSystemVersion;

    job.InputAType = GetArchetypeChunkComponentType<InputA>(true);
    job.InputBType = GetArchetypeChunkComponentType<InputB>(true);
    job.OutputType = GetArchetypeChunkComponentType<Output>(false);

    return job.Schedule(m_Query, inputDependencies);
}

注意,为了提高效率,更改版本适用于整个块,而不是单个实体. 如果另一个作业访问了某个块,而该作业有能力写入该类型的组件,那么该组件的更改版本将增加,DidChange()函数将返回true,如果Job声明了对访问的组件进行写入,则更改得版本也会增加

Instantiate and schedule the Job

要运行一个 IJobChunk Job,你必须创建一个 Job struct得实例,也就是声明一个结构体,继承IJobChunk接口, 给结构体字段赋值,然后 schedule the Job. 如果你在 OnUpdate() 方法里面JobComponentSystem执行这些操作,则系统会每一帧都执行这个job.


protected override JobHandle OnUpdate(JobHandle inputDependencies)
{
    var job = new RotationSpeedJob()
    {
        RotationType = GetArchetypeChunkComponentType<Rotation>(false),
        RotationSpeedType = GetArchetypeChunkComponentType<RotationSpeed>(true),
        DeltaTime = Time.DeltaTime
    };
    return job.Schedule(m_Query, inputDependencies);
}

当年你调用GetArchetypeChunkComponentType<T>方法来设置你的组件变量类型,对于job读取但不写入的组件,请确保将isReadOnly设置为true.,它的参数如果是只读类型的组件,就设置为true,否则为false,这个参数设置正确,影响job工作的效率. 这些访问模式设置必须与结构定义和EntityQuery中查询设置匹配。

不要在system类里面缓存 GetArchetypeChunkComponentType<T>() 的返回值. 每次系统运行并将更新后的值传递给作业时,都必须调用该函数。

Manual iteration

你还可以访问 NativeArray中的所有的块,并通过job 比如 IJobParallelFor访问它们. ,但是如果你需要访问块,但是EntityQuery 里面的简单限制并不适合你,推荐使用这个方法:

public class RotationSpeedSystem : JobComponentSystem
{
   [BurstCompile]
   struct RotationSpeedJob : IJobParallelFor
   {
       [DeallocateOnJobCompletion] public NativeArray<ArchetypeChunk> Chunks;
       public ArchetypeChunkComponentType<RotationQuaternion> RotationType;
       [ReadOnly] public ArchetypeChunkComponentType<RotationSpeed> RotationSpeedType;
       public float DeltaTime;

       public void Execute(int chunkIndex)
       {
           var chunk = Chunks[chunkIndex];
           var chunkRotation = chunk.GetNativeArray(RotationType);
           var chunkSpeed = chunk.GetNativeArray(RotationSpeedType);
           var instanceCount = chunk.Count;

           for (int i = 0; i < instanceCount; i++)
           {
               var rotation = chunkRotation[i];
               var speed = chunkSpeed[i];
               rotation.Value = math.mul(math.normalize(rotation.Value), quaternion.AxisAngle(math.up(), speed.RadiansPerSecond * DeltaTime));
               chunkRotation[i] = rotation;
           }
       }
   }

   EntityQuery m_Query;   

   protected override void OnCreate()
   {
       var queryDesc = new EntityQueryDesc
       {
           All = new ComponentType[]{ typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>() }
       };

       m_Query = GetEntityQuery(queryDesc);
   }

   protected override JobHandle OnUpdate(JobHandle inputDeps)
   {
       var rotationType = GetArchetypeChunkComponentType<RotationQuaternion>();
       var rotationSpeedType = GetArchetypeChunkComponentType<RotationSpeed>(true);
       var chunks = m_Query.CreateArchetypeChunkArray(Allocator.TempJob);

       var rotationsSpeedJob = new RotationSpeedJob
       {
           Chunks = chunks,
           RotationType = rotationType,
           RotationSpeedType = rotationSpeedType,
           DeltaTime = Time.deltaTime
       };
       return rotationsSpeedJob.Schedule(chunks.Length,32,inputDeps);
   }
}

Iterating manually in a ComponentSystem:在ComponentSystem中手动遍历

虽然不是一个普遍推荐的做法, 您可以使用EntityManager类来手动遍历实体或块. 这些迭代方法应该只在测试或调试代码中使用(或者当您只是在试验时),或者在您拥有一组完全受控的实体的孤立环境中使用。

例如,下面的代码片段遍历活动世界中的所有实体:

var entityManager = World.Active.EntityManager;
var allEntities = entityManager.GetAllEntities();
foreach (var entity in allEntities)
{
   //...
}
allEntities.Dispose();

这个代码片段遍历活动世界中的所有块

var entityManager = World.Active.EntityManager;
var allChunks = entityManager.GetAllChunks();
foreach (var chunk in allChunks)
{
   //...
}
allChunks.Dispose();

Querying for data using a EntityQuery:使用EntityQuery查询实体

读取或写入数据的第一步是找到该数据. ECS框架中的数据存储在组件中,组件根据其类型和实体一起分配在内存块中. 如果想要你程序中需要的数据,就需要EntityQuery来查询.

创建EntityQuery结构体之后, 你可以:

  • 运行一个job 来访问你查询到的实体和组件
  • 获得一个NativeArray包含所有选择的实体
  • 获得一个 NativeArrays 包含所有选择的组件(通过组件类型来获得by component type)

EntityQuery 在检索entity 和 component arrays数组时是并行进行的,所以同一个实体在这两个数组中的索引是一样的,就相当于块中每一行都有两个组件,并行排列

Note: ComponentSystem.Entites.ForEach委托和  IJobForEach 是基于组件类型和组件的属性创建的EntityQueries

Defining a query:定义一个query

 EntityQuery 定义了一个实体原型当中必须有的组件类型或者是没有的组件类型.

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

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

使用 ComponentType.ReadOnly&lt;T&gt 而不是简单的 typeof 指定系统不写入RotationSpeed. 这样对数据的访问约束较少,也就是job只需要读,不需要写,能够更有效地执行job

EntityQueryDesc

 EntityQueryDesc 能够创建给复杂的EntityQuery. EntityQueryDesc提供了一种灵活的查询机制,可以根据以下组件集指定要选择的原型:

  • All = All component types in this array must exist in the archetype
  • Any = At least one of the component types in this array must exist in the archetype
  • None = None of the component types in this array can exist in the archetype

例如,下面的查询包含包含 RotationQuaternion and RotationSpeed components, but excludes any archetypes containing the Frozen component:

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

Note: ArchetypeChunk.Has&lt;T&gt;() 方法确定块是否包含可选组件.

Query options

当你创建一个 EntityQueryDesc, 您可以设置它的选项变量. 这些选项允许进行专门的查询 (通常不需要设置它们):

  • Default — no options set; the query behaves normally.
  • IncludePrefab — includes archetypes containing the special Prefab tag component.
  • IncludeDisabled — includes archetypes containing the special Disabled tag component.
  • FilterWriteGroup — considers the WriteGroup of any components in the query.

当你 设置FilterWriteGroup 选项, 只有含有在Write Group的组件的实体才会被选上,具有来自相同WriteGroup的任何附加组件的实体将被排除。

假设C2和C3是基于C1的同一WriteGroup中的组件,您使用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_Query = GetEntityQuery(query);

这个查询排除了C2和C3的任何实体,因为C2没有显式地包含在查询中. While you could design this into the query using None, doing it through a Write Group provides an important benefit:您不需要更改其他系统使用的查询(只要这些系统也使用Write Groups).

Write Groups are a mechanism that allow you to extend existing systems. For example, if C1 and C2 are defined in another system (perhaps part of a library that you don't control), you could put C3 into the same Write Group as C2 in order to change how C1 is updated. For any entities to which you add your C3 component, your system will update C1 and the original system will not. For other entities without C3, the original system will update C1 as before.

See Write Groups for more information.

Combining queries

您可以通过传递一个EntityQueryDesc对象数组而不是一个实例来组合多个查询。每个查询都使用一个逻辑或操作进行组合。 下面的示例选择一个包含RotationQuaternion组件或RotationSpeed组件(或两者)的原型:

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

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

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

Creating a EntityQuery

 在 system class系统类外部, EntityManager.CreateEntityQuery() 方法创建EntityQuery

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

但是, 在 system class系统类内部, 必须使用 GetEntityQuery() 方法:

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

当您计划重用同一个查询到的东西时,您应该缓存EntityQuery实例, 如果可能,而不是为每个使用创建一个新的. 例如,在一个系统中,您可以在系统的OnCreate()函数中创建EntityQuery,并将结果存储在一个实例变量中。以上示例中的m_Query变量用于此目的。

Defining filters

除了定义哪些组件必须包含或排除在查询之外,您还可以筛选那种类型的组件。你可以指定以下类型的过滤器:

  • Shared component values —选择拥有shared component的特定值的实体
  • Change filter —选择组件类型的值是否可能发生改变的实体

Shared component filters

要使用shared component filter, 首先要在EntityQuery包含shared component  (以及其他需要的组件), 然后调用 SetFilter() 方法,  传入与ISharedComponent类型相同的结构,该结构包含要选择的值. 所有值必须匹配 ,你可以向filter至多添加两个不同的 shared components.

您可以随时更改过滤器, 但是更改filter并不能更改数组中已存在的实体或者组件,这些实体或者组件是你通过 ToComponentDataArray() or ToEntityArray() 方法获得的. 你必须重新创建这些数组.

下面定义了一个 SharedGrouping 的shared component,系统仅仅访问拥有Group字段,且值为1的实体

struct SharedGrouping : ISharedComponentData
{
    public int Group;
}

class ImpulseSystem : ComponentSystem
{
    EntityQuery m_Query;

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

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

        var positions = m_Query.ToComponentDataArray<Position>(Allocator.Temp);
        var displacememnts = m_Query.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 filter中,比如, 例如,下面的EntityQuery只包含另一个系统已经写入转换组件的块中的实体:

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

注意,为了提高效率,更改过滤器应用于整个块,而不是单个实体。  change filter 还会检查系统是否声明了对组件的写入,即使没有真正的修改组件的数据,这个也会算数,也就是说如果一个job已经声明了那个组件是可写入的,则认为他已经更改了(这也是对不需要写入的组件声明为只读的另一个原因)

Executing the query

 EntityQuery 当你在job中使用它或者你调用他其中的方法返回一个实体数组或者组件数组或者块数组的时候,就会执行它的查询:

  • ToEntityArray() 返回符合条件的实体数组.
  • ToComponentDataArray&lt;T&gt;返回选择实体上的组件数组.
  • CreateArchetypeChunkArray() 返回包含选择的实体的块 (因为查询是基于原型, archetypes, shared component values, 、change filters这些属性在同一个块中的实体上都是相同的,存储在返回的块集中的实体集与ToEntityArray()返回的实体集完全相同.)

In Jobs

在 JobComponentSystem, 给系统的 Schedule() method方法 传递EntityQuery object对象

// 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_Query, inputDependencies);
}

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

发布了80 篇原创文章 · 获赞 7 · 访问量 2673

猜你喜欢

转载自blog.csdn.net/qq_37672438/article/details/104610233
ECS