Unity ECS+Jobs System笔记 案例解析2(十一)

本章节为大家解析ECS案例,资源来自,大家自行获取:
https://github.com/Unity-Technologies/EntityComponentSystemSamples

3、IJobForEach——Job System入门案例

这个案例的构造和案例1十分相似(如果没看过案例1的,可以先去看一下案例1的内容),ECS中的Entity和Component都是差不多的,区别就在于System
之前的案例使用的是ComponentSystem,我们也说了,可以用,但是不推荐,因为它是在主线程中运行的,并没有利用到Job System,所以在这个案例中就使用到了Job System

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

public class RotationSpeedSystem_IJobForEach : JobComponentSystem
{
    [BurstCompile]

那么首先,现在代码继承的是JobComponentSystem而不是ComponentSystem,并且在这里BurstCompile属性可以让代码使用Burst来对Job进行编写,会有很大的速度提升!

    // OnUpdate runs on the main thread.
    protected override JobHandle OnUpdate(JobHandle inputDependencies)
    {
        var job = new RotationSpeedJob
        {
            DeltaTime = Time.deltaTime
        };

        return job.Schedule(this, inputDependencies);
    }

因为继承了JobComponentSystem,所以要实现其中的虚函数OnUpdate函数(在主线程上运行的),而在OnUpdate函数中要实现的就是具体的Job(分配在工作线程上执行),那么我们就需要创建这个Job(这里设置了DeltaTime这个参数),最后执行这个Job:

    struct RotationSpeedJob : IJobForEach<Rotation, RotationSpeed_IJobForEach>
    {
        public float DeltaTime;

        // The [ReadOnly] attribute tells the job scheduler that this job will not write to rotSpeedIJobForEach
        public void Execute(ref Rotation rotation, [ReadOnly] ref RotationSpeed_IJobForEach rotSpeedIJobForEach)
        {
            // Rotate something about its up vector at the speed given by RotationSpeed_IJobForEach.
            rotation.Value = math.mul(math.normalize(rotation.Value), quaternion.AxisAngle(math.up(), rotSpeedIJobForEach.RadiansPerSecond * DeltaTime));
        }
    }
}

在之前的ECS(五)中,我们已经简单介绍过了,这里再复习一下:

  • 定义一个结构体,然后继承一个Job接口,在这里继承的是IJobForEach,参数里填上需要查询的组件;在这个例子中Job需要读取RotationSpeed_IJobForEach组件中的数据并写入Rotation组件中
  • 定义变量,例如DeltaTime,作为执行时的参数传入
  • 编写Execute()方法(在这个案例中,执行了旋转这个行为),JobComponentSystem为每个符合条件的实体调用Execute()方法,传入到IJobForEach所标识的实体中去,因此,Execute()的参数必须与为结构体所定义的泛型参数匹配
  • 识别组件的读写性(这里指[ReadOnly])可以帮助Job scheduler更有效地分配Jobs
小结

在这个案例中,我们了解了如何使用Job System

  1. 创建一个类(System)继承自JobComponentSystem,并实现OnUpdate接口
  2. 创建一个Job继承自IJobForEach并在参数里填上需要查询的组件
    2.1 实现Execute()方法
  3. 在OnUpdate中执行Job

4、IJobChunk——根据区块更新实体

不说了,这个案例的构造和案例3很像,但是首先让我们回顾一个概念——原型(Archetype),当你创建一个实体,并且对其添加了组件,EntityManager就会对这个实体上的独一无二的组件组合保持追踪,而这个独一无二的组件组合,我们就称之为Archetype(原型),而相同原型的实体是被存在同一区块(Chunk) 内的,当实体的原型受到了影响发生了改变,EntityManager必须将改变后的数据移动到新的区块中
由此可见,我们可以根据原型来追踪一类实体:

以下内容在ECS(六)中都有介绍

4.1、通过EntityQuery查询数据
public class RotationSpeedSystem : JobComponentSystem
{
   private EntityQuery m_Group;
   protected override void OnCreate()
   {
       m_Group = GetEntityQuery(typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>());
   }
   //…
}

我们首先声明一个EntityQuery类型的查询,使用GetEntityQuery函数,参数是原型所拥有的组件,注意:这里是包含但不限于 RotationQuaternion和RotationSpeed组件的实体,如果想要查询只包含这两个组件的实体,需要采用All,例如:

   var query = new EntityQueryDesc
   {
       None = new ComponentType[]{ typeof(Frozen) },
       All = new ComponentType[]{ typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>() 				   		       
   }
4.2、创建Job

那么接下来我们已经得到了该区块,接下来就是对区块内的实体进行更新,同样建立一个系统继承JobComponentSystem,实现OnUpdate函数,在这之前,我们需要创建Job,这个Job是继承IJobChunk的

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

因为需要访问区块内的组件数组,所以必须对需要读取或写入的每种类型组件的对象创建
ArchetypeChunkComponentType类型的变量,这些变量允许你获取NativeArrays的实例,从而提供对实体组件的访问

        public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
        {
            var chunkRotations = chunk.GetNativeArray(RotationType);
            var chunkRotationSpeeds = chunk.GetNativeArray(RotationSpeedType);

使用chunk参数获取组件的NativeArray实例,由于这些数组都是对齐的,因此每个实体在不同数组中有着相同的索引

举个例子 查询的区块中有4个实体,那么:
数组A保存了4个a类型的组件,数组B保存了4个b类型的组件,数组C保存了4个c类型的组件
在这里A1 B1 C1 都是实体1的组件
在这里A2 B2 C2 都是实体2的组件

这就是数组对齐

也就可以正常使用for循环遍历组件数组,使用chunk.Count得到存储在当前块的实体的数量:

            for (var i = 0; i < chunk.Count; i++)
            {
                var rotation = chunkRotations[i];
                var rotationSpeed = chunkRotationSpeeds[i];
                chunkRotations[i] = new Rotation
                {
                    Value = math.mul(math.normalize(rotation.Value),
                        quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * DeltaTime))
                };
            }
        }
    }

最后修改chunkRotations[i]的数据来旋转chunkRotations[i]

4.3、执行Job
	protected override JobHandle OnUpdate(JobHandle inputDependencies)
    {
        var rotationType = GetArchetypeChunkComponentType<Rotation>();
        var rotationSpeedType = GetArchetypeChunkComponentType<RotationSpeed_IJobChunk>(true);

因为RotationSpeedJob中的参数我们需要加入,所以我们需要通过GetArchetypeChunkComponentType函数来声明ArchetypeChunkComponentType类型的变量,然后传入Job中使用

        var job = new RotationSpeedJob()
        {
            RotationType = rotationType,
            RotationSpeedType = rotationSpeedType,
            DeltaTime = Time.deltaTime
        };

        return job.Schedule(m_Group, inputDependencies);
    }

对m_Group执行声明的RotationSpeedJob,在这里要确保ArchetypeChunkComponentType在EntityQuery中的类型、OnUpdate中的类型和在Job中的类型是一致的! 在这个案例中,因为需要保持一致,rotationSpeedType都是只读的

4.4、小结

利用IJobChunk我们就可以对具有一类组件的实体进行更新

  1. 利用EntityQuery来查询所需的区块
  2. 创建一个类(System)继承自JobComponentSystem,并实现OnUpdate接口
  3. 创建一个Job继承自IJobChunk并建立ArchetypeChunkComponentType类型的变量
    2.1 所需的组件组通过GetNativeArray在Chunk获取
    2.2 利用For循环对Chunk内的实现所有实体的Execute()方法
    3.3 这里的重点在于通过Chunk获取组件的NativeArray是对齐的
  4. 在OnUpdate中执行Job,Job所需的ArchetypeChunkComponentType类型的参数通过GetArchetypeChunkComponentType来获取并传入

这两个案例的效果都和案例1一样,就不放图了

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值