赞
踩
看过Dots技术宣传片,当时确实被震惊到了,脑袋里闪过一句话:时代变了!大人。DOTS是Data-Oriented Technology Stack(数据导向的技术栈):借助Unity全新高性能多线程式数据导向型技术堆栈(DOTS),充分利用当今的多核处理器。游戏的运行速度会变得更快。
这种技术在游戏领域最先成功的案例来自守望先锋,各位可以去看看《守望先锋》架构设计与网络同步,ECS全称是Entity Component System,即实体、组件和系统。
ECS这种架构有什么好处呢?相比OOP方式实现Component,ECS中相同组件的数据是连续的,System集中处理这些组件,可以方便利用CPU Cache提升效率。由于System处理的可能是几个组件,ECS将组件尽量的最小化(否则会有很多数据冗余),这样就实现数据的扁平化。同时System间禁止相互调用,最大程度的实现代码解耦。而OOP最被诟病的调用层次深,结构复杂等问题都被解决了。
Dots就是由实体组件系统(ECS)、任务系统(Job System)、Burst Compiler编译器三部分组成。DOTS保证相同类型组件在内存中都是顺序排列,极大程度增加缓存的命中率,此外配合任务系统(Job System)让开发者无需头疼多线程同时访问数据需要手动加解锁的麻烦,最终加持Burst Compiler让性能飞起来。
在对比之前,需要了解CPU相关概念:
很多传统游戏引擎是基于面向对象来设计的,游戏世界中的东西都是对象(GameObject),每个对象都有叫做Update方法,游戏引擎遍历所有对象,依次调用其Update方法。有些引擎甚至定义多种Update方法,在同一帧的不同时机去调用(Unity通过反射机制调用这些Update函数)。 OOP编程模式其实是有极大的缺陷的。因为游戏对象由很多部分聚合而成,引擎的功能模块很多,不同模块关注的部分往往互不相关。比如音效模块数据并不需要关心物理碰撞数据、渲染模块数据不关心游戏逻辑数据,对于某种模块没有必要的数据保存到CPU中,我们可以称为垃圾数据。从自然意义上说,把游戏对象属性聚合是很正常的事情,对象生命期管理也是最合理的方式。但这种对象大量存在时就会严重性能问题,接下来展示Unity的MonoBehaviour所继承的Component子类。
其实MonoBehaviour继承关系是这样的,Behaviour->Component->Object,这些子类全部加起来的数据、属性可不是以上这么点,有兴趣可以进去看看这些子类都定义了什么数据、属性、方法。MonoBehaviour对象过多会导致大量无用数据占据CPU的缓存(比如控制transform旋转,里面却有音效、物理、相机等等属性),结论是传统引擎的设计思路对CPU缓存不太友好导致利用率不高,出现CPU缺页中断情况会比较频繁,下面给出图解:
使用Ecs技术对CPU缓存利用率有质的提高,因为保存到CPU缓存的数据都比较纯净(ECS中的实体、组件),这样可以有效降低CPU缺页中断,所以Dots相对于OOP编程有以下优点:
拉取Unity官方案例AngryBots_ECS,博主使用Unity2020.2.0a打开案例工程,发现一大堆的问题和报错(剩下渲染管线方面问题无法解决,但不影响运行),接下来分析如何一步步解决这些问题。首先是Hybrid Renderer版本问题,看报错好像依赖了High Definition RP包(但是下载RP后还是不行),所以这里尝试把Hybrid Renderer更新到最新的版本,于是第一个报错就消失了。出现了以下报错就和接龙一样…Job System不存在OnCreateManager接口,可以进入到基类ComponentSystemBase里,发现了OnCreate生命周期函数,可以重写它来代替报错函数。World.Active去代替World.DefaultGameObjectInjectionWorld,可能Active是老版本的命名方式,GameObjectConversionUtility.ConvertGameObjectHierarchy接口所需参数也不一致,可以改成一下代码段:
- var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);
- manager = World.DefaultGameObjectInjectionWorld.EntityManager;
- bulletEntityPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(bulletPrefab, settings);
最后TimeData.deltaTime改成大写开头即可,全部报错改好后会发现运行时还有问题,可能是Burst Compiler的问题,重启Unity后所有问题都得到解决了。
如果你终于走到这步,可能会很高兴。但启动游戏后会发现看不到子弹和敌人…不使用Ecs是可以看到子弹的,如下图所示:
ps:莫名其妙死亡的话,只是看不到的敌人将主角杀死,上面说过可能是渲染管线的问题,经过在下长时间努力,大概知道问题所在了。尝试Edit/Project Settings/Graphics下将Scriptable Render Pipeline Settings设置成none,可以看到一下画面(可以看到子弹、敌人)。
具体解决方案不太清楚,如果有人知道麻烦下面留言,万分感谢~
感觉GameObjectConversionSettings或Hybrid Rendering问题,但不知道如何修改。
案例演示视频
使用Unity2020.2.0a创建新工程后,需要导入hybrid、entities(entities依赖jobs、burst),如下图所示:低版本Unity可能不存在此文件,并且manifest.json也不存在jobs、burst这些依赖包,这时需要手动拉取。接下来讲述下3种拉取方式:
依赖包全部导入后,接下来先尝试用DOTS技术写多个Cube旋转吧。
- using Unity.Entities;
-
- public struct RotationCubeComponent : IComponentData
- {
- public float speed;
- }
注意必须是结构体,以后文章会分析为什么是结构体,如何才可以使用类,结构体比类的优点等等。
- using Unity.Entities;
- using Unity.Mathematics;
- using Unity.Transforms;
- using Unity.Jobs;
-
- public class RotationCubeSystem : SystemBase
- {
- protected override void OnUpdate()
- {
- float deltaTime = Time.DeltaTime;
- Entities
- .WithName("RotationCubeSystem")
- .ForEach((ref RotationCubeComponent rotationSpeed, ref Rotation rotation) =>
- {
- rotation.Value = math.mul(
- math.normalize(rotation.Value),
- quaternion.AxisAngle(math.up(), rotationSpeed.speed * deltaTime));
- })
- .ScheduleParallel();
- }
- }
ScheduleParallel已经使用Job System多线程技术了,不使用多线程处理可以自行百度。
- using Unity.Entities;
- using UnityEngine;
-
- public class EntitiesManger : MonoBehaviour, IConvertGameObjectToEntity
- {
- public float FloCubeSpeed = 10f;
-
- public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
- {
- var data = new RotationCubeComponent { speed = FloCubeSpeed };
- dstManager.AddComponentData(entity,data);
- }
- }
GameObject转Entity,继承IConvertGameObjectToEntity重写Convert函数。
- using Unity.Entities;
- using Unity.Mathematics;
- using Unity.Transforms;
- using UnityEngine;
-
- public class EntitiesBuilder : MonoBehaviour
- {
- public GameObject cube;
-
- void Start()
- {
- var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);
- var prefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(cube, settings);
- var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
-
- for (var x = 0; x < 100; x++)
- {
- for (var y = 0; y < 100; y++)
- {
- var instance = entityManager.Instantiate(prefab);
- var position = transform.TransformPoint(new float3(x * 1.3F, noise.cnoise(new float2(x, y) * 0.21F) * 2, y * 1.3F));
- entityManager.SetComponentData(instance, new Translation { Value = position });
- }
- }
- }
- }
将EntitiesManger和Convert To Entity挂载到Cube之后,制作成Prefab即可。然后将EntitiesBuilder随便挂载到场景的物体。
具体原理分析和DOTS深度的东西后面文章会说明,至于官方案例子弹为什么不显示,博主知道具体原因也会更新的。
既然DOTS这么牛逼,为啥还没普及?很简单,DOTS现阶段在完成度上就是接近废品的残次品,虽然经过Unity长达近三年的宣传,DOTS到今天完成度依然极低,基本上处于不可用的状态,一款游戏引擎必不可少的部分可简单列为下面八个,要把Dots技术推动起来,游戏引擎要进行大量重构,或者保留传统的MonoBehaviour将Dots技术插接进去,接下来看看Unity对这八大模块完成情况。
对于游戏引擎模块全部改成DOTS是有难度的,不然Unity公司对于DOTS支持程度就不是如此了(毕竟对游戏行业算是革命),在下大胆预言未来有某个天才或Unity公司完全实现了DOTS并且达到可以商用的地方,将会是巨大的利润和商机。大家可能比较关心的是目前DOTS能不能接入游戏,只能说部分模块可以使用(某些经典批处理场景),个人建议不要把还没成型的技术接入到项目中,现在DOTS还有很多限制,知道有这个东西即可。参考资料和案例:DOTS开源案例、DOTS参考资料、DOTS实用案例
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。