Unity ECS学习笔记(5)IJobChunk

最近在学习unity的ecs框架,转载几篇写的比较好的文章帮助理解

原文日期 2019-12-5 避免误导未来使用正式版的开发者。

 

经过前面的介绍,我们已经知道了JobComponentSystem和IJobForEach,也了解了C# Job System可以并行执行Job来提升性能。

我们知道,IJobForEach这种Job,只能一个个实体进行处理,这次要介绍的IJobChunk,是可以对某个块(Chunk)里的所有实体进行批量处理。

虽然结果是一样的(仍然是一个Cube在旋转),但代码却大不相同,一起来看看吧。

1.组件(Component)

接下来开始看代码吧,先创建一个组件类:

1

2

3

4

5

6

7

8

using System;

using Unity.Entities;

 

[Serializable]

public struct RotationSpeed_IJobChunk : IComponentData

{

    public float RadiansPerSecond;

}

这个组件仍然是带有一个字段,但是,大家注意一下,和之前的组件对比,这次的组件并没有使用GenerateAuthoringComponent特性,而是使用了Serializable特性。

因为这个Demo里,官方又给我们展示了另外一种相对而言更灵活的实体创建方式,这里先不纠结,后面会说到。

2.系统(System)

接下来,我们要看新的System是怎么做的了,注意,现在要看新System的代码了,我们来创建一个System类:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

using Unity.Burst;

using Unity.Collections;

using Unity.Entities;

using Unity.Jobs;

using Unity.Mathematics;

using Unity.Transforms;

 

// This system updates all entities in the scene with both a RotationSpeed_IJobChunk and Rotation component.

public class RotationSpeedSystem_IJobChunk : JobComponentSystem

{

    EntityQuery m_Group;

 

    protected override void OnCreate ()

    {

        // Cached access to a set of ComponentData based on a specific query

        m_Group = GetEntityQuery(typeof(Rotation), ComponentType.ReadOnly<RotationSpeed_IJobChunk>());

    }

 

    // Use the [BurstCompile] attribute to compile a job with Burst. You may see significant speed ups, so try it!

    [BurstCompile]

    struct RotationSpeedJob : IJobChunk

    {

// 太复杂,这个结构体的内容被我删掉了

    }

 

    // OnUpdate runs on the main thread.

    protected override JobHandle OnUpdate (JobHandle inputDependencies)

    {

        // Explicitly declare:

        // - Read-Write access to Rotation

        // - Read-Only access to RotationSpeed_IJobChunk

        var rotationType = GetArchetypeChunkComponentType<Rotation>();

        var rotationSpeedType = GetArchetypeChunkComponentType<RotationSpeed_IJobChunk>(true);

 

        var job = new RotationSpeedJob()

        {

            RotationType = rotationType,

            RotationSpeedType = rotationSpeedType,

            DeltaTime = Time.DeltaTime

        };

 

        return job.Schedule(m_Group, inputDependencies);

    }

}

好,感觉有点乱是不是,不要着急,慢慢来。

首先,我们仍然继承了JobComponentSystem,这个很熟悉。

其次,仍然重写了OnUpdate函数,这个也很熟悉,我们最终也需要返回一个JobHandle对象。

3.EntityQuery

然后,重写了OnCreate函数,并且多了一个EntityQuery字段,这个是重点。

EntityQuery是用来筛选实体的,通过调用GetEntityQuery函数,可以获取EntityQuery对象。

GetEntityQuery可以传递多个组件的类型作为参数,后续将会筛选出包含指定类型的实体。

注:GetEntityQuery还有更多更强大的筛选方式(and、or、not),这里不展开。

关于EntityQuery,后续我会专门写一篇教程。

我们获得的EntityQuery对象要用在什么地方呢?

我们看看OnUpdate函数的最后一行:return job.Schedule(m_Group, inputDependencies); 这里把EntityQuery传递给Schedule函数,于是就能筛选实体数据,传递给Job。

4.RotationSpeedJob(IJobChunk)

和IJobForEach的用法类似,我们把逻辑放到Job中,由于RotationSpeedJob的代码看起来比较复杂,我没把它贴出来,先不管。

我们先看看OnUpdate函数里是怎么使用RotationSpeedJob的:

RotationSpeedJob需要三个字段,其中DeltaTime是我们大家熟悉的,不多说。

另外两个字段代表了某个原型的块的组件类型(ArchetypeChunkComponentType),很绕,但其实说白了,就是组件类型。

通过GetArchetypeChunkComponentType函数,可以获得某个原型的块的组件类型…

唔,总之,我们获得了Rotation组件和RotationSpeed_IJobChunk组件的类型(ArchetypeChunkComponentType)。

另外,GetArchetypeChunkComponentType函数可以传递一个参数,代表是否只读。

这是什么意思呢?代表筛选出来的数据是否是只读的,这可以提升游戏的性能,当然这里又不说了(旁白:嗯嗯,你不懂的就不说是吧)。

5.IJobChunk

好了,现在来到最重要的部分。

我们的RotationSpeedJob是实现了IJobChunk接口的,之前我没有把RotationSpeedJob的内容贴出来,现在来看看:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

    [BurstCompile]

    struct RotationSpeedJob : IJobChunk

    {

        public float DeltaTime;

        public ArchetypeChunkComponentType<Rotation> RotationType;

        [ReadOnly] public ArchetypeChunkComponentType<RotationSpeed_IJobChunk> RotationSpeedType;

 

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

        {

            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_IJobChunk.

                chunkRotations[i] = new Rotation

                {

                    Value = math.mul(math.normalize(rotation.Value),

                        quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * DeltaTime))

                };

            }

        }

    }

顶部的BurstComplie特性就是所谓的“爆破”编译,总之加上去就能提升Job的性能,具体不展开,以后再聊。

RotationSpeedJob有三个字段,之前已经提到过了,现在来看看Execute函数。

稍微有点乱,我们一步步来:

a. 首先,我们在外部调用了RotationSpeedJob的Schedule函数:

并且给Schedule函数传递了m_group(EntityQuery)对象,inputDependencies参数不管它,我不懂,所以它不重要(害羞)。

这代表,我们的RotationSpeedJob将会获得通过EntityQuery筛选后的块数据。

b. 回到RotationSpeedJob的Execute函数,第一个参数【ArchetypeChunk chunk】是什么呢?它就是经过EntityQuery筛选后获得的块数据。

c. 注意,这个chunk代表的是一个块数据。也就是说,我们筛选出来的实体可能存放在不同的块里,Execute函数每次只能获得一个块里的数据。并且,同一时间可能会有很多个RotationSpeedJob被执行(并行),大幅提升了性能,这个和IJobForEach类似。

d. 通过调用chunk的GetNativeArray函数,可以获得这个块里的所有实体的某个类型的组件。

如:var chunkRotations = chunk.GetNativeArray(RotationType);

可以获得当前块的所有实体的RotationType类型的组件(即,Rotation组件)。

再次看看下面这张图,EntityA和EntityB是属于同一个块的,假设当前chunk就是这个块,那么,chunkRotations变量保存的就是EntityA和EntityB的Rotation组件(两条数据)。

e. 最后就简单了,对获取出来的组件进行取值或者赋值。

按照官方的说法,使用IJobChunk来获取数据,更高效、灵活,这里暂时不深入。

6.创建实体

最后一步,创建实体,以前是直接给组件加上一个GenerateAuthoringComponent特性就能自动创建实体。

这次不一样,这个Demo又给我们展示了另外一种更加灵活的方式去创建实体,先新建一个类:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

using Unity.Entities;

using Unity.Mathematics;

using UnityEngine;

 

[RequiresEntityConversion]

public class RotationSpeedAuthoring_IJobChunk : MonoBehaviour, IConvertGameObjectToEntity

{

    public float DegreesPerSecond = 360.0F;

 

    // The MonoBehaviour data is converted to ComponentData on the entity.

    // We are specifically transforming from a good editor representation of the data (Represented in degrees)

    // To a good runtime representation (Represented in radians)

    public void Convert (Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)

    {

        var data = new RotationSpeed_IJobChunk { RadiansPerSecond = math.radians(DegreesPerSecond) };

        dstManager.AddComponentData(entity, data);

    }

}

也是一步步来解释:

a. 这个类继承了MonoBehaviour,实现了IConvertGameObjectToEntity接口,使用了RequiresEntityConversion特性。

b. 将这个类挂到Cube上,就会自动将Cube转换为ECS的实体对象。

c. IConvertGameObjectToEntity接口需要实现Convert函数。

d. 这个Convert函数里,我们new一个RotationSpeed_IJobChunk组件对象,然后通过EntityManager的AddComponentData将组件附加到实体上。

e. 于是,我们就将Cube转换为了一个带有RotationSpeed_IJobChunk的ECS实体。

为什么说这种方式更加灵活?因为我们可以给实体附加任意多个组件(在Convert函数里可以给实体Add任意多个组件对象)。

而如果使用GenerateAuthoringComponent特性将组件转换为实体,那就只能给实体附加单个组件了。

7.运行

同样的,我们新建一个场景,创建一个Cube,把RotationSpeedAuthoring_IJobChunk、ConvertToEntity挂到Cube上,然后运行,就能看到新Demo的效果了。

唔…结果仍然是一个Cube在转,我就不贴效果图了。

8.IJobChunk的优势

官方的说法是,IJobChunk可以对整个块的实体进行操作,更加灵活。

目前来说,我是体会不到特别明显的优势,可能要深入学习ECS后才能体会。

也许IJobForEach就是简化后的IJobChunk?(瞎猜)

后续还有其他一些Job,我们未来再说。

 

注意,本系列教程基于DOTS相关预览版的Package包,是预览版,不代表正式版的时候也适用。

 

下图出自同样不错的官方案例讲解:https://blog.csdn.net/qq_30137245/article/details/99068336

图解

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值