ECS框架文档翻译十三 Using IJobForEach

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

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

你可以在JobComponentSystem中定义IJobForEach作业以读写组件数据。当此Job运行时,ECS框架会找出具有所需组件的所有实体,并为每个实体调用Job的Execute()方法。数据将按照其在内存中布局的顺序进行处理,并且Job会并行运行,因此IJobForEach既简单又高效。

以下示例介绍了一个简单的使用IJobForEach的系统。 Job读取RotationSpeedcomponent 组件数据并向RotationQuaternion组件写入数据。

public class RotationSpeedSystem : JobComponentSystem
{
   // Use the [BurstCompile] attribute to compile a job with Burst.
   [BurstCompile]
   struct RotationSpeedJob : IJobForEach<RotationQuaternion, RotationSpeed>
   {
       public float DeltaTime;
       // The [ReadOnly] attribute tells the job scheduler that this job will not write to rotSpeed
       public void Execute(ref RotationQuaternion rotationQuaternion, [ReadOnly] ref RotationSpeed rotSpeed)
       {
           // Rotate something about its up vector at the speed given by RotationSpeed.  
           rotationQuaternion.Value = math.mul(math.normalize(rotationQuaternion.Value), quaternion.AxisAngle(math.up(), rotSpeed.RadiansPerSecond * DeltaTime));
       }
   }

// OnUpdate runs on the main thread.
// Any previously scheduled jobs reading/writing from Rotation or writing to RotationSpeed 
// will automatically be included in the inputDependencies.
protected override JobHandle OnUpdate(JobHandle inputDependencies)
   {
       var job = new RotationSpeedJob()
       {
           DeltaTime = Time.deltaTime
       };
       return job.Schedule(this, inputDependencies);
   }
}

注意: 以上的系统是基于 ECS Samples repository中的例子HelloCube IJobForEach.

定义 IJobForEach 的泛型参数

IJobForEach结构体的泛型参数标识了你的系统将操作哪些组件:

struct RotationSpeedJob : IJobForEach<RotationQuaternion, RotationSpeed>

通过使用以下属性,你可以修改此Job对实体的筛选:

  • [ExcludeComponent(typeof(T)] — 排除那些原型中包含了T类型组件的实体
  • [RequireComponentTag(typeof(T)] — 只包含那些原型中含有T类型组件的实体。当需要保持某组件与实体的关联,却又需要避免读写它时,可以使用这个属性

例如,以下的Job定义选择了那些原型中包含Gravity、RotationQuaternion、RotationSpeed 组件,但是不含有Frozen component组件的实体:

[ExcludeComponent(typeof(Frozen))]
[RequireComponentTag(typeof(Gravity))]
[BurstCompile]
struct RotationSpeedJob : IJobForEach<RotationQuaternion, RotationSpeed>

如果你需要更复杂的请求来选择操作实体,你可以使用IJobChunk作业而不是IJobForEach。

Execute() 方法的书写

JobComponentSystem 将会对连续的实体逐个调用 Execute()方法,传入你在泛型参数中定义的那些组件。也就是说,你的Execute()方法的参数应该与本Job结构体中定义的泛型参数相匹配。

例如,写作以下的 Execute()方法,它保持了与结构体定义的泛型参数的匹配,并且声明了参数属性:读取RotationSpeed组件,读写 RotationQuaternion组件。(Read/write是默认的,所以不需要声明属性)

public void Execute(ref RotationQuaternion rotationQuaternion, [ReadOnly] ref RotationSpeed rotSpeed){}

你可以增加以下的属性,来帮助ECS优化你的系统:

  • [ReadOnly] — 此组件数据在本方法中只读不写。
  • [WriteOnly] — 此组件数据在本方法中只写不读。
  • [ChangeFilter] — 只有当此组件数据发生变化时(从上一次本系统运行以来),才会执行此方法

声明为只读或者只写组件,将让Job更加高效地被调度执行。例如,当一个Job正在读取某个组件时,调度器不会安排另一个Job去写这个组件,但是当两个Job都只是在读取某个组件时,它们可以并行执行。

注意,为了考虑性能,ChangeFilter只在整个实体块(Chunk)上运行,它不会追踪单个实体。如果一个实体块被一个可以针对某类组件写入的Job访问,那么ECS框架将会认为整个的Chunk,包含在整个块总的所有实体,都已经发生了变化,否则,ECS框架将会把整个块中的实体都排除在外{译者注:即认为整个实体块都未发生变化}。

使用IJobForEachWithEntity

实现了IJobForEachWithEntity接口的Job,与实现了IJobForEach接口的Job的行为非常相似。 不同之处在于,IJobForEachWithEntity中的Execute()函数参数为您提供了当前处理的实体对象,以及位于展开的、并行的组件数组中的索引。

使用Entity参数

你可以使用此实体对象来向EntityCommandBuffer增加实体操作命令。例如,你可以增加命令,来向该实体增加或者删除组件,甚至删除该实体— 为了避免竞争条件,所有的命令都不会在该Job内立刻执行。命令缓存将允许您在作业线程上执行那些代价可能很高昂的计算,它们将实际插入和删除操作用队列缓存起来,而后在主线程上执行。

以下的系统,源于例子 HelloCube SpawnFromEntity,在Job中,在计算实体的位置之后,它使用一个命令缓存来将结果应用到实体:

public class SpawnerSystem : JobComponentSystem
{
   // EndFrameBarrier 提供了 CommandBuffer
   EndFrameBarrier m_EndFrameBarrier;

   protected override void OnCreate()
   {
       // 将EndFrameBarrier引用存储在内部, 这样我们不需要每帧再去获取
       m_EndFrameBarrier = World.GetOrCreateSystem<EndFrameBarrier>();
   }
   struct SpawnJob : IJobForEachWithEntity<Spawner, LocalToWorld>
   {
       public EntityCommandBuffer CommandBuffer;
       public void Execute(Entity entity, int index, [ReadOnly] ref Spawner spawner,
           [ReadOnly] ref LocalToWorld location)
       {
           for (int x = 0; x < spawner.CountX; x++)
           {
               for (int y = 0; y < spawner.CountY; y++)
               {
                   var __instance __= CommandBuffer.Instantiate(spawner.Prefab);
                   // Place the instantiated in a grid with some noise
                   var position = math.transform(location.Value,
                       new float3(x * 1.3F, noise.cnoise(new float2(x, y) * 0.21F) * 2, y * 1.3F));
                   CommandBuffer.SetComponent(instance, new Translation {Value = position});
               }
           }
           CommandBuffer.DestroyEntity(entity);
       }
   }

   protected override JobHandle OnUpdate(JobHandle inputDeps)
   {
       // 分配一个Job,并且初始化Job内部的EntityCommandBuffer
       var job = new SpawnJob
       {
           CommandBuffer = m_EndFrameBarrier.CreateCommandBuffer()
       }.ScheduleSingle(this, inputDeps);

       //我们需要告诉其它的barrier系统,在命令被执行之前此Job必须被完成。
       m_EndFrameBarrier.AddJobHandleForProducer(job);

       return job;
   }
}

注意: 本例使用了IJobForEach.ScheduleSingle(),它将会让本Job在某个单一的线程上执行。如果你使用Schedule()取代的话,系统将会分配并行的多个Job来处理这些实体。如果是并行的情况,这时你需要使用实体命令缓存的并发形式(EntityCommandBuffer.Concurrent)。

查看 ECS samples repository 来查看整个例子代码。

使用index参数

将命令添加到并发命令缓存时,你可以使用此index参数。 在并行运行Job来处理多个实体时,可以使用并发命令缓存。 在IJobForEachWithEntity作业中,如果使用Schedule()方法,而不是上面示例中使用的ScheduleSingle()方法时,Job System将会并行处理所有的实体。在并行作业中,应该始终使用并发命令缓冲区,以保证线程安全和被缓存命令的执行确定性。

在同一系统中,你还可以使用索引来跨作业地引用相同实体。 例如,如果你需要在多个过程中处理同一组实体,并同时收集临时数据,则可以在某个Job中,使用索引将临时数据插入到一个NativeArray中,再在随后的Job中,使用索引去访问该数据 。(当然,你必须将相同的NativeArray传递给两个Job)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript ECS框架是一种用于游戏开发的框架,它基于实体-组件-系统的设计模式。在这种框架中,游戏对象被表示为实体,每个实体由一组组件组成,而系统则负责处理这些实体和组件之间的交互。 在给出的引用中,可以看到一个简单的JavaScript ECS框架的实现。在这个框架中,主要的文件结构包括游戏逻辑入口文件(main.js)、角色系统(character.js)、碰撞系统(collision.js)、衰减系统(decay.js)、敌人系统(enemy.js)、渲染系统(render.js)、四叉树划线辅助系统(sketch.js)、时间系统(time.js)、用户输入系统(userInput)等。这些系统负责处理不同的游戏逻辑,例如角色的移动、碰撞检测、渲染等。 在加载框架时,首先通过异步加载main.js文件,并获取到canvas和主角图片等对象。然后,加载完毕后调用gameStart函数来启动ECS框架。gameStart函数接受canvas、dom和img作为参数,用于初始化ECS框架ECS框架的核心思想是将游戏对象分解为实体和组件,通过系统来处理它们之间的交互。实体是游戏对象的容器,组件是实体的属性或行为。系统则负责处理特定类型的组件,例如渲染系统负责处理渲染相关的组件。 通过使用ECS框架,开发者可以更好地组织和管理游戏逻辑,提高代码的可维护性和可扩展性。同时,ECS框架也能够提供更好的性能,因为它可以更有效地处理游戏对象之间的交互。 总结起来,JavaScript ECS框架是一种用于游戏开发的框架,基于实体-组件-系统的设计模式,通过将游戏对象分解为实体和组件,并通过系统来处理它们之间的交互,提供了更好的代码组织和管理方式,以及更好的性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值