当前位置:   article > 正文

Unity的Dots技术入门_unity dots

unity dots

前言

看过Dots技术宣传片,当时确实被震惊到了,脑袋里闪过一句话:时代变了!大人。DOTS是Data-Oriented Technology Stack(数据导向的技术栈):借助Unity全新高性能多线程式数据导向型技术堆栈(DOTS),充分利用当今的多核处理器。游戏的运行速度会变得更快。

1.Unity的Dots由来

这种技术在游戏领域最先成功的案例来自守望先锋,各位可以去看看《守望先锋》架构设计与网络同步,ECS全称是Entity Component System,即实体、组件和系统。

  • 实体一般用途的对象,可以用唯一ID来标识。
  • 组件是一组数据,可以是实体一部分,也可以是实体和世界交互的中间数据(注意:没有功能)。实体由若干组件组成的,否则它是空盒子。
  • 系统基于实体中的特定组件来实现特定功能,但是系统是集中批量处理所有的实体的。

ECS这种架构有什么好处呢?相比OOP方式实现Component,ECS中相同组件的数据是连续的,System集中处理这些组件,可以方便利用CPU Cache提升效率。由于System处理的可能是几个组件,ECS将组件尽量的最小化(否则会有很多数据冗余),这样就实现数据的扁平化。同时System间禁止相互调用,最大程度的实现代码解耦。而OOP最被诟病的调用层次深,结构复杂等问题都被解决了。
Dots就是由实体组件系统(ECS)、任务系统(Job System)、Burst Compiler编译器三部分组成。DOTS保证相同类型组件在内存中都是顺序排列,极大程度增加缓存的命中率,此外配合任务系统(Job System)让开发者无需头疼多线程同时访问数据需要手动加解锁的麻烦,最终加持Burst Compiler让性能飞起来。

2.与OOP编程对比

在对比之前,需要了解CPU相关概念:

  • 1.CPU处理数据速度非常快,内存条和硬盘跟不上它的速度。
  • 2.CPU自身有三级缓存,用来提高CPU获取数据速度。n级缓存最快(n是1->3),但容量最小。
  • 3.内存是指CPU拿取数据的最开始的地方,CPU访问内存速率远小于三级缓存速率。
  • 4.CPU操作数据会从三级缓存中取得数据,速度非常快,但有些情况下数据不在三级缓存中(缺页中断),就需要寻址到内存中,并且把目标数据重新放到三级缓存中,提高下一次访问速度。

很多传统游戏引擎是基于面向对象来设计的,游戏世界中的东西都是对象(GameObject),每个对象都有叫做Update方法,游戏引擎遍历所有对象,依次调用其Update方法。有些引擎甚至定义多种Update方法,在同一帧的不同时机去调用(Unity通过反射机制调用这些Update函数)。 OOP编程模式其实是有极大的缺陷的。因为游戏对象由很多部分聚合而成,引擎的功能模块很多,不同模块关注的部分往往互不相关。比如音效模块数据并不需要关心物理碰撞数据、渲染模块数据不关心游戏逻辑数据,对于某种模块没有必要的数据保存到CPU中,我们可以称为垃圾数据。从自然意义上说,把游戏对象属性聚合是很正常的事情,对象生命期管理也是最合理的方式。但这种对象大量存在时就会严重性能问题,接下来展示Unity的MonoBehaviour所继承的Component子类。

exp1

其实MonoBehaviour继承关系是这样的,Behaviour->Component->Object,这些子类全部加起来的数据、属性可不是以上这么点,有兴趣可以进去看看这些子类都定义了什么数据、属性、方法。MonoBehaviour对象过多会导致大量无用数据占据CPU的缓存(比如控制transform旋转,里面却有音效、物理、相机等等属性),结论是传统引擎的设计思路对CPU缓存不太友好导致利用率不高,出现CPU缺页中断情况会比较频繁,下面给出图解:

exp2

使用Ecs技术对CPU缓存利用率有质的提高,因为保存到CPU缓存的数据都比较纯净(ECS中的实体、组件),这样可以有效降低CPU缺页中断,所以Dots相对于OOP编程有以下优点:

  • 1.ECS对CPU缓存友好,减少CPU缺页中断,防止CPU获取数据慢,从而CPU出现等待情况。
  • 2.JobSystem编写多线程代码可以提供高性能,显著提高帧速率和延长移动设备的电池寿命。
  • 3.Burst Compiler,用于生成高度优化的本地代码。

3.Dots官方案例

拉取Unity官方案例AngryBots_ECS,博主使用Unity2020.2.0a打开案例工程,发现一大堆的问题和报错(剩下渲染管线方面问题无法解决,但不影响运行),接下来分析如何一步步解决这些问题。error首先是Hybrid Renderer版本问题,看报错好像依赖了High Definition RP包(但是下载RP后还是不行),所以这里尝试把Hybrid Renderer更新到最新的版本,于是第一个报错就消失了。出现了以下报错就和接龙一样…error1Job System不存在OnCreateManager接口,可以进入到基类ComponentSystemBase里,发现了OnCreate生命周期函数,可以重写它来代替报错函数。error2World.Active去代替World.DefaultGameObjectInjectionWorld,可能Active是老版本的命名方式,GameObjectConversionUtility.ConvertGameObjectHierarchy接口所需参数也不一致,可以改成一下代码段:

  1. var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);
  2. manager = World.DefaultGameObjectInjectionWorld.EntityManager;
  3. bulletEntityPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(bulletPrefab, settings);

error3最后TimeData.deltaTime改成大写开头即可,全部报错改好后会发现运行时还有问题,可能是Burst Compiler的问题,重启Unity后所有问题都得到解决了。
如果你终于走到这步,可能会很高兴。但启动游戏后会发现看不到子弹和敌人…不使用Ecs是可以看到子弹的,如下图所示:

exp3

ps:莫名其妙死亡的话,只是看不到的敌人将主角杀死,上面说过可能是渲染管线的问题,经过在下长时间努力,大概知道问题所在了。尝试Edit/Project Settings/Graphics下将Scriptable Render Pipeline Settings设置成none,可以看到一下画面(可以看到子弹、敌人)。

exp4

具体解决方案不太清楚,如果有人知道麻烦下面留言,万分感谢~
感觉GameObjectConversionSettings或Hybrid Rendering问题,但不知道如何修改。
案例演示视频

4.实战DOTS技术

使用Unity2020.2.0a创建新工程后,需要导入hybrid、entities(entities依赖jobs、burst),如下图所示:exp5低版本Unity可能不存在此文件,并且manifest.json也不存在jobs、burst这些依赖包,这时需要手动拉取。接下来讲述下3种拉取方式:

  • 使用Package Manager拉取需要包(可能搜索不出来)。
  • 打开Package Manager后,窗口左上角有个加号,点开有三个选项Add package from xxx(有时低版本不出现Package Manager)。
  • 在manifest.json里添加两个依赖包(最稳定的方法):
    “com.unity.entities”: “0.14.0-preview.18”,
    “com.unity.rendering.hybrid”: “0.8.0-preview.18”,

依赖包全部导入后,接下来先尝试用DOTS技术写多个Cube旋转吧。

  • 1.创建旋转方块组件:
  1. using Unity.Entities;
  2. public struct RotationCubeComponent : IComponentData
  3. {
  4. public float speed;
  5. }

注意必须是结构体,以后文章会分析为什么是结构体,如何才可以使用类,结构体比类的优点等等。

  • 2.创建旋转方块事件
  1. using Unity.Entities;
  2. using Unity.Mathematics;
  3. using Unity.Transforms;
  4. using Unity.Jobs;
  5. public class RotationCubeSystem : SystemBase
  6. {
  7. protected override void OnUpdate()
  8. {
  9. float deltaTime = Time.DeltaTime;
  10. Entities
  11. .WithName("RotationCubeSystem")
  12. .ForEach((ref RotationCubeComponent rotationSpeed, ref Rotation rotation) =>
  13. {
  14. rotation.Value = math.mul(
  15. math.normalize(rotation.Value),
  16. quaternion.AxisAngle(math.up(), rotationSpeed.speed * deltaTime));
  17. })
  18. .ScheduleParallel();
  19. }
  20. }

ScheduleParallel已经使用Job System多线程技术了,不使用多线程处理可以自行百度。

  • 3.创建实体管理器
  1. using Unity.Entities;
  2. using UnityEngine;
  3. public class EntitiesManger : MonoBehaviour, IConvertGameObjectToEntity
  4. {
  5. public float FloCubeSpeed = 10f;
  6. public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
  7. {
  8. var data = new RotationCubeComponent { speed = FloCubeSpeed };
  9. dstManager.AddComponentData(entity,data);
  10. }
  11. }

GameObject转Entity,继承IConvertGameObjectToEntity重写Convert函数。

  • 4.批量生成实体
  1. using Unity.Entities;
  2. using Unity.Mathematics;
  3. using Unity.Transforms;
  4. using UnityEngine;
  5. public class EntitiesBuilder : MonoBehaviour
  6. {
  7. public GameObject cube;
  8. void Start()
  9. {
  10. var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);
  11. var prefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(cube, settings);
  12. var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
  13. for (var x = 0; x < 100; x++)
  14. {
  15. for (var y = 0; y < 100; y++)
  16. {
  17. var instance = entityManager.Instantiate(prefab);
  18. var position = transform.TransformPoint(new float3(x * 1.3F, noise.cnoise(new float2(x, y) * 0.21F) * 2, y * 1.3F));
  19. entityManager.SetComponentData(instance, new Translation { Value = position });
  20. }
  21. }
  22. }
  23. }

将EntitiesManger和Convert To Entity挂载到Cube之后,制作成Prefab即可。然后将EntitiesBuilder随便挂载到场景的物体。
具体原理分析和DOTS深度的东西后面文章会说明,至于官方案例子弹为什么不显示,博主知道具体原因也会更新的。

5.总结

既然DOTS这么牛逼,为啥还没普及?很简单,DOTS现阶段在完成度上就是接近废品的残次品,虽然经过Unity长达近三年的宣传,DOTS到今天完成度依然极低,基本上处于不可用的状态,一款游戏引擎必不可少的部分可简单列为下面八个,要把Dots技术推动起来,游戏引擎要进行大量重构,或者保留传统的MonoBehaviour将Dots技术插接进去,接下来看看Unity对这八大模块完成情况。

  • Physics-3分 物理自然是游戏引擎不可或缺的部分,DOTS配套的Unity Physics处于非常早期的状态,Authoring工具几乎为零,甚至Joint或者CharacterConroller都要从Sample里找代码,由于它提供了一套新的API,开发过程只能用极难使用来形容,来看看Trigger事件代码就知道了,而使用过程中也是Bug层出不穷,经常会遇到类似Mesh Collider Bake错误,而与之同期宣传的Havok Physics for Unity更是奇妙,在长达半年的时间里,只是单纯引入Havok便会导致HDRP的渲染出问题.而更进阶的布料、粒子、地形等也处于几乎为零的状态。
  • Graphics-3分 DOTS与之配套的是图形系统是Hybrid Renderer,不过目前也只支持Mesh Renderer,什么Particle、Trail、VFX都还没影儿,更可惜的是官方现在优先关注于HDRP兼容性(虽然也不怎么样),URP或者Built-In Pipeline就基本别想了,遇到了问题也只能双手一摊。
  • Audio-0分 Unity目前只开发了底层的 DSP 系统,上层的DOTS Audio说了一两年了连影子都没有,完成度接近于零。
  • Animation-0分 Unity Animation依然处于极早期的开发中,DOTS Sample展示了些最基本的使用,类似 Animator 这样的高层方案现在还没影儿。
  • Network-2分 也是Unity吹了两年的东西,DOTS Sample中用的NetCode只不过把FPSSample中的部分代码挪过去了,目前版本号 0.0.4,意思就是太早期了别用。
  • UI-0分 IMGUI,UI Element和UGUI三个Unity的产品均不支持DOTS。
  • AI-0分 Navmesh,Ml-Agent,Ai Planner三个Unity的产品均不支持DOTS。
  • Input-2分 老的Input System相对简单与DOTS没啥关系,可新的Input System居然也不支持DOTS,幸运的是InputSystem相对独立,嫁接进DOTS并不困难。

对于游戏引擎模块全部改成DOTS是有难度的,不然Unity公司对于DOTS支持程度就不是如此了(毕竟对游戏行业算是革命),在下大胆预言未来有某个天才或Unity公司完全实现了DOTS并且达到可以商用的地方,将会是巨大的利润和商机。大家可能比较关心的是目前DOTS能不能接入游戏,只能说部分模块可以使用(某些经典批处理场景),个人建议不要把还没成型的技术接入到项目中,现在DOTS还有很多限制,知道有这个东西即可。参考资料和案例:DOTS开源案例DOTS参考资料DOTS实用案例

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/507767
推荐阅读
相关标签
  

闽ICP备14008679号