• 【Unity】Entities 1.0 学习(一):Aspect


            Unity在 2022年下半年(我印象是9月份左右)推出了 Entities 1.0 ,可以在 2022.2.0b8 以上的版本使用。当时我粗略地看了一下,但是没有深入学习。最近空闲时间稍多,就认真来学习一下 Entities 1.0有啥新的东西。

            1.0 毕竟是大更新,改变的东西还是很多,所以应该会断断续续分好几次更新。

            首先来看官方的更新文档:

    What's new in Entities 1.0 | Entities | 1.0.0-exp.12https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/whats-new.html        新增的功能有很多了,我就不一一列举了,需要的同学自行看文档了。我就一个一个的来看,学到哪里算哪里。

            这篇文章是 Entities 1.0 的介绍,不是 ECS 相关的入门。我默认你已经熟练使用之前版本的 Unity ECS,只是对 1.0 的更新缺乏了解的。如果没有 ECS 的相关知识,不建议阅读此文,建议先学习 ECS 并有初步的理解。

            我目前看的版本是 1.0.0-exp.12 ,后续Unity可能会做一些更改,但是应该不会很大。

    1、什么是Aspect

            简单地理解就是 ComponentData 的二次封装 。

            Aspect 是 1.0 新增一个新特性,允许将多个CompoenentData 或是 Buffer 组合在一起,这样使用起来就更加方便,感觉更像面向对象了。

    Organize your code with aspects | Entities | 1.0.0-exp.12https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/aspects-intro.html        例如我在某个Entity上挂载了多个 ComponentData 时,我可以通过 Aspect 将他们全部拿到并自定义一些方法,而不必要再像以前那样拿到各个单独的 ComponentData 然后分别修改。不但在设计上更为友好,同时还能大量减少重复代码。


    关于重复代码:

            在以前的书写中,如果某段逻辑在多个 System 中都会使用,要么是重复写代码,要么放到一个公共类。而放到公共类中,如果涉及到大量参数传递时也会很恶心。

            而在 Aspect 中,我们可以将公共方法直接卸载 Aspect 中,其值获取可以直接由内部属性构造获取,非常方便。        


    2、构建 Aspect

            接下来从一个实例开始介绍 Aspect :

    1. public readonly partial struct BuildAspect : IAspect
    2. {
    3. //下面的 UTransform 和 UBounds 都是自定义的 ComponentData
    4. private readonly RefRW mTransfrom;
    5. private readonly RefRO mBounds;
    6. //RenderPartParentBuffer_Child 是自定义的的 BufferElementData
    7. private readonly DynamicBuffer mChilds;
    8. }

            readonly partial 是固定写法,这个不能改,然后继承 IAspect,并且添加至少一个 RefRO / RefRW 即可完成对 Aspect 的构建。

            当然,在 Aspect 里也是可以嵌套其他 Aspect 的。

            在代码编译阶段,Unity会根据你写的 Aspect 代码自动生成(派生类)对应的创建代码,以及Handler(下文会看到,在System内部使用的)之类,这种机械代码完全就可以不用写了。


    关于:IAspectCreate

            如果你浏览 Unity.Transforms.TransformAspect 的时候,你会发现他继承了这个 IAspectCreate。但实际上我们自己写的时候是不需要的,只需要继承 IAspect 即可。实际上当我们阅读 TransformAspect 发现非常复杂,实现绕来绕去的,是因为我们看到的代码包含了自动生成的代码。而我们自己写的时候是不需要写这些的。

            当继承 IAspect 之后,编译时 Unity 会自动生成一个派生并继承 IAspectCreate,并自动写完所需要的代码。如果我们自己继承了 IAspectCreate ,还需要自己写生成代码。


            当然,就上文的代码还看不出有什么用,但是我们将其功能完善之后你就能看出端倪了:

    1. public readonly partial struct BuildAspect : IAspect
    2. {
    3. private readonly RefRW mTransfrom;
    4. private readonly DynamicBuffer mChilds;
    5. private readonly RefRO mBounds;
    6. public float3 Position
    7. {
    8. get { return mTransfrom.ValueRO.Position; }
    9. set { mTransfrom.ValueRW.Position = value; }
    10. }
    11. public quaternion Rotation
    12. {
    13. get { return mTransfrom.ValueRO.Rotation; }
    14. set { mTransfrom.ValueRW.Rotation = value; }
    15. }
    16. public UBounds Bounds => mBounds.ValueRO;
    17. public RenderPartParentBuffer_Child GetFirstChild => mChilds[0];
    18. }

            显然,这里面就和我们平时写的C#就差不多了,几乎没有限制,可以自由地调用现有属性完成逻辑。我们大可以在这个基础上完成一些逻辑的封装,从而使ECS代码更接近大家熟知的面向对象模式。

    3、主线程调用 Aspect

            主线程只需要通过EntityManager来获得这个Aspect即可:

    1. Entity e = Entity.Null;//示例;
    2. EntityManager mananger = default;//示例;
    3. var buildAspect = mananger.GetAspect(e);
    4. buildAspect.Position = 0;//设置位置 Test

            很显然,看得出来Aspect所谓的获取,其实就是重新构建一个新的结构体,Unity并没有设计一个缓存之类的。

            在主线程调用这个非常友好,直接都可以当做一个Class来使用都是没有问题的。

    4、System中调用Aspect

            相比较而言,在System中调用就麻烦很多了。


            非常搞笑的是,Unity的官方文档里说在System里可以用SystemAPI来创建Aspect:

             实际上这个API根本不能用,里面就没有实现,谁调用谁报错(笑)。

            大家可以看看官方文档,是不是我的理解有问题。


    4.1、在 IJobEntity 中使用 Aspect

            在 IJobEntity 的写法则非常简单,啥预处理都不用,直接像下面这样写就可以:

    1. using Unity.Entities;
    2. using Unity.Burst;
    3. // It's best practice to Burst-compile your code
    4. [BurstCompile]
    5. partial struct CannonBallJob : IJobEntity
    6. {
    7. void Execute(ref CannonBallAspect cannonBall){
    8. // Your game logic
    9. }
    10. }

            就这样就轻松愉快地可以用起来了。

    4.2、在 IJobChunk 中使用 Aspect

            而在 IJobChunk 中用起来麻烦多了,需要做的工作不是多一点点:

    1. public partial class EcsTestBuildSystem : SystemBase
    2. {
    3. private EntityQuery mEntityQuery;
    4. private BuildAspect.TypeHandle _aspectTypeHandle;//声明一个Handle
    5. protected override void OnCreate()
    6. {
    7. base.OnCreate();
    8. mEntityQuery = GetEntityQuery
    9. (
    10. ComponentType.ReadWrite(),
    11. ComponentType.ReadWrite(),
    12. ComponentType.ReadOnly(),
    13. );
    14. _aspectTypeHandle = new BuildAspect.TypeHandle(ref CheckedStateRef, false);
    15. }
    16. protected override void OnUpdate()
    17. {
    18. _aspectTypeHandle.Update(ref CheckedStateRef);//更新
    19. Dependency = new EcsTestRunner_Update()
    20. {
    21. BuildAspectHandle = _aspectTypeHandle//传值
    22. }.ScheduleParallel(mEntityQuery, Dependency);
    23. }
    24. [BurstCompile]
    25. private struct EcsTestRunner_Update : IJobChunk
    26. {
    27. public BuildAspect.TypeHandle BuildAspectHandle;
    28. public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
    29. {
    30. var builds = BuildAspectHandle.Resolve(chunk);
    31. for (int i = 0; i < chunk.Count; i++)
    32. {
    33. var build = builds[i];
    34. }
    35. }
    36. }
    37. }

            瞬间代码量就上去了!


            在  IJobChunk 中使用Aspect之所以麻烦,是因为这里几乎所有的代码都是要我们自己写的。而在 IJobEntity 中,大部分代码都是 Unity 在派生类中帮助我们写好了。

            但是我还是建议使用 IJobChunk , 麻烦但是有意义的。你可以理解为这个是 Job 的一种高级写法,我们可以通过这个控制 Chunk,虽然麻烦了点,但是可以获取更高的效率。如果要真的用好 ECS ,IJobChunk 肯定是家常便饭。


    5、其他注意事项

    • 构建 Aspect 是会有额外消耗的,毕竟每次都会创建一个新的。所以如果追求极致性能就不要使用这个。

    • 在 Aspect 的写入其实是单线程的,如果是多线程写入会提示线程竞争。所以要么单线程跑,要么加上 NativeDisableParallelForRestriction 属性。

    • Aspect 在以读写的方式构造时,无论改了哪些属性,都会将里面的所有值设置为修改,自然也会损失一些性能。构建一个 Aspect 很容易,但是要构建一个高效率的 Aspect ,还是非常困难的。

  • 相关阅读:
    [Java]线上监控诊断工具Arathas,入门使用
    数据结构---HashMap和HashSet
    关于账本数据库:你想知道的这里都有
    何时使用Elasticsearch而不是MySql
    如何快速通过阿里云ACP 认证?
    RISC-V选项
    行业洞察 | 爱聊天的虚拟人
    python让女友照片飞来飞去
    Day20.1:关于this、super的解析
    08 图形学——Bezier曲线与曲面
  • 原文地址:https://blog.csdn.net/cyf649669121/article/details/127814468