首先,先来了解下什么是DOTS?
DOTS是Data-Oriented-Tech-Stack,官方中文翻译是:多线程式数据导向型技术堆栈。
它主要由三部分组成:
下图是Unite2019的介绍截图:
所以,DOTS是一套集合型的高效技术堆栈,整合了Job System(编写多线程代码)、ECS(编写高性能代码)、Burst Compliler(编译生成高性能代码)。通过使用DOTS可以让Unity项目跑的更高效,在移动平台上可以表现为更高的fps,更低的电池消耗,更小的发热。另外关于ECS可以查看本博客的另一篇简明教程。
先确认下版本,这里推荐最新Unity版本,博主这里用的是Unity2019.3正式版;
1.打开菜单栏-window->package manager,右侧的Advanced下拉切换为show preview package(截至目前2020.2.9依旧是预览版),安装:Burst、Entities、Jobs、Hybrid Renderer(用于DOTS的渲染相关)、Unity Physics(用于DOTS的高性能物理组件)
2.启用Entity Debugger(调试器),菜单栏-Window-Analysis-Entity Debugger,可以自行拖拽窗口到合适位置方便查看调试信息;
(1)创建子场景:新建一个场景,创建一个Cube,移除Box collider组件,选中并右键New SubScene From Selection,当然也可以自行创建一个空物体,挂载SubScene脚本。
创建后则会自动在Assets下产生一个场景文件及对应的缓存文件。可以在Assets下自行更改刚创建的SubScene的名称,本质上还是一个Unity Scene,不一样的是Cube所挂载的父节点,多挂了一个SubScene脚本而已,用于管理和标记此场景是一个DOTS的SubScene。
在SubScene脚本下,可以进行一些配置,包括对应的场景、面板hierarchy 颜色,是否自动载入场景,关闭和启用编辑子场景,保存等。打开编辑时,可以在SubScene节点下自由编辑填充其他内容,可以参考project tiny 的小赛车项目,里面就将整个大地图就被作为了一个子场景。关闭编辑时,则整个子场景的子节点不可见,也不可编辑,被当作一个GameObject。
打开子场景编辑后,查看Entity Debugger,点击右边的All Entities(Editor World)可以看到当前子场景的一些状态信息,参考(一)2.图
(2)编码
创建一个C#脚本,Rotate.cs,没什么,就是一个结构体,它将作为数据:
using Unity.Entities;
public struct Rotate:IComponentData{
public float radiansPerSecond;
}
创建第二个脚本,RotateAuthoring.cs,主要作用是将GameObeject转化为Entity:
using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;using Unity.Transforms;
public class RotateAuthoring :MonoBehaviour,IConvertGameObjectToEntity
{
[SerializeField]
private float degresPerSecond;
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponentData(entity, new Rotate
{ ratiansPerSecond = math.radians(degresPerSecond) });
dstManager.AddComponentData(entity, new RotationEulerXYZ());
}
}
IConvertGameObjectToEntity接口用于将当前的GameObject(Cube)进行转换为DOTS的entity,然后绑定数据;
其中RotationEulerXYZ是transform的旋转,为了提高性能,DOTS里重写了一套组件,将原来的Transform里的旋转、位置、缩放等都单独出来。
第三个脚本是RotateSystem,继承自ComponentSystem,作用是做entity的行为驱动。
using Unity.Entities;
using Unity.Transforms;
public class RotateSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach((ref Rotate rotate, ref RotationEulerXYZ euler)=>
{
euler.Value.y += rotate.ratiansPerSecond * Time.DeltaTime;
});
}
}
Entities.ForEach用于遍历当前所有的entity,然后将数据赋值给entity进行操作;
编写代码完毕,就将第二个脚本RotateAuthoring挂载在Cube上。设置DegresPerSecond的值为50:
运行项目,cube开始旋转。查看status:
修改RotateSystem.cs脚本:
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
public class RotateSystem : JobComponentSystem
{
private struct RotateJob : IJobForEach<RotationEulerXYZ, Rotate>
{
public float deltaTime;
public void Execute(ref RotationEulerXYZ euler, ref Rotate rotate)
{
euler.Value.y += rotate.ratiansPerSecond * deltaTime;
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var job = new RotateJob { deltaTime = Time.DeltaTime };
return job.Schedule(this, inputDeps);
}
}
主要变动是将继承改为了JobComponentSystem,增加了一个RotateJob,然后到OnUpdate中调用job;
在菜单栏中启用BurstCompile
在RotateSystem代码中的RotateJob结构体上添加[BurstCompile]特性,需要using Unity.Burst;
[BurstCompile]
private struct RotateJob : IJobForEach<RotationEulerXYZ, Rotate>
{
public float deltaTime;
public void Execute(ref RotationEulerXYZ euler, ref Rotate rotate)
{
euler.Value.y += rotate.ratiansPerSecond * deltaTime;
}
}
开启SubScene的编辑,并大量复制cube,3000个以上。保存子场景。运行,开启unity编辑器的Status来查看:
我们发现,帧率依旧很稳定,只是Batches飙高到了3359。我们也需要优化掉:
(6)优化Batches
在Project面板下创建一个Material,命名为cube,并勾选Enable GPU Instancing.
选中子场景的所有cube,并将材质赋值到所有cube上。保存子场景,再次运行,发现batches已经获得了优化,帧率也大幅度提高。
DOTS带来的性能改进是十分客观的,可以想象一些弹幕游戏、塔防游戏,使用上DOTS是可以非常直观的看到性能改进的。目前Unity也在大力推这套开发技术,相信未来会有越来越多的项目使用DOTS,并得益于DOTS.