Unity ECS+Jobs System笔记 访问数据3(七)

来源:https://docs.unity3d.com/Packages/[email protected]/manual/index.html
我会对官方文档内容略作整理,有需要可以查看官方文档

4、ComponentSystem

你可以使用ComponentSystem处理数据,ComponentSystem在主线程上运行,因此不利用多个CPU内核,在以下情况下使用ComponentSystems:

  • 调试模式,有时更容易在主线程上观察代码运行时发生了什么,例如,可以记录Debug并绘制图形
  • 当系统需要访问只能在主线程上运行的API时,这可以帮助你逐步将游戏系统转换为ECS,而不必从一开始就重写所有内容
  • 系统执行的工作量所需的开销比使用Job系统少时

特别注意: 进行结构更改会强制完成所有作业,这一事件称为同步点,因为系统在等待同步点时无法利用所有可用的CPU内核所以可能导致性能下降。在ComponentSystem中,你应该使用一个post-update指令缓冲,虽然同步点仍然会出现,但所有结构变化都是批量发生的,因此影响略小。为了获得最大的运行效率,请使用JobComponentSystem和EntityCommandBuffer。在创建大量实体时,还可以使用单独的World创建实体,然后将这些实体传输到主游戏世界

通过ForEach进行迭代

ComponentSystem提供了Entities.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函数有最多六种类型的组件作为参数进行使用
如果你需要改变现有实体的结构,你可以添加一个实体组件到你的lambda函数的参数上,然后使用它来向你的ComponentSystem的PostUpdateCommands缓冲添加指令(如果允许在lambda函数内部进行结构更改,则可能会更改正在迭代的数组中的数据,从而导致错误)
例如,如果要从旋转速度当前为零的任何实体中删除RotationSpeed组件,可以按如下方式更改ForEach函数:

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
       PostUpdateCommands.RemoveComponent(entity, typeof(RotationSpeed));               
});

OnUpdate()函数完成后,系统在更新后缓冲区后执行命令

Fluent Queries(快速查询)

你可以使用fluent-style查询来约束ForEach的lambda函数,使其只对那些满足某些约束的实体作用,这些查询可以指定是否对any、all或none性质的实体作用,约束可以链接在一起,对于C#的LINQ系统的用户来说应该非常熟悉
请注意,作为参数传递给ForEach的lambda函数的任何组件都会自动包含在WithAll中,并且不能显式包含在WithAll,WithAny或WithNone部分中

  • 一个WithAll约束可以指定拥有所有组件的实体,例如在下面这个查询中,ComponentSystem为具有“Rotation”和“Scale”组件的所有实体执行lambda函数
Entities.WithAll<Rotation, Scale>().ForEach( (Entity e) =>
{
    // do stuff
});

将WithAll用于必须存在于实体上但不需要读取或写入的组件(添加一个作为访问ForEach的lambda函数的参数所需要的组件),例如:

Entities.WithAll<SpinningTag>().ForEach( (Entity e, ref Rotation r) =>
{
    // do stuff
});
  • 一个WithAny约束可以指定在组件中至少拥有一个组件的实体,例如:ComponentSystem为同时具有Rotation和Scale组件,以及具有RenderDataA或RenderDataB(或两者)的实体执行以下lambda函数:
Entities.WithAll<Rotation, Scale>().WithAny<RenderDataA, RenderDataB>().ForEach( (Entity e) =>
{
    // do stuff
});

请注意,无法通过WithAny知道一个指定实体有哪些组件,如果需要根据存在的组件来分别处理实体,则必须为每种情况创建特定查询,或者将JobComponentSystem与IJobChunk一起使用

  • 一个WithNone约束可以排除在组件中至少拥有一个组件的实体,例如:ComponentSystem为没有Rotation组件的所有实体执行以下lambda函数:
Entities.WithNone<Rotation>().ForEach( (Entity e) =>
{
    // do stuff
});

此外,你也可以通过指定WithAnyReadOnly和WithAllReadOnly来筛选那些组件,但是要确保它们是只读组件,这将确保它们没有标记在写入和ID被更改的时候

查询选项

你还可以对With设置一些额外选项:

条件 描述
Default 默认
IncludePrefab 该查询不会隐式的排除具有Prefab组件的实体
IncludeDisabled 该查询不会隐式的排除具有Disabled(已禁用)组件的实体
FilterWriteGroup 查询会根据查询中指定的组件的WriteGroupAttribute属性筛选所选实体

ComponentSystem查询那些没有Rotation组件,包括Disabled(被禁用)的实体:

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

5、Manual iteration

你还可以在NativeArray中显式请求所有chunk,并使用诸如IJobParallelFor来处理它们的Job,如果你需要以某种方式管理chunk,而不是简单地迭代EntityQuery中的所有chunk,则建议使用此方法。如:

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_group;   

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

       m_group = GetEntityQuery(query);
   }

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

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

在ComponentSystem中手动迭代

虽然不是推荐做法,但可以使用EntityManager类手动迭代实体或块,而这些迭代方法应该只用于测试或调试代码(或者只是用于测试)或者是在一个孤立的World中——在这个世界中你可以的完美控制所有实体
例如,以下这段代码遍历当前世界中的所有实体:

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();

你可能感兴趣的:(Unity笔记)