Dots1.0更新了什么
前几天DOTS1.0的预览版已经发布,我第一时间就进行了学习,Dots1.0的更新内容我认为非常震撼,有必要写一篇文章详细说明一下。Dots1.0更新的包如下com.unity.entitiescom.unity.entities.graphicscom.unity.netcodecom.unity.physicscom.unity.burstcom.unity.collectionscom.un
·
前几天DOTS1.0的预览版已经发布,我第一时间就进行了学习,Dots1.0的更新内容我认为非常震撼,有必要写一篇文章详细说明一下。
Dots1.0更新的包如下
com.unity.logging (异步日志方案,比Debug.log同步日志方案速度更快)
开始之前需要升级一下Unity的版本,需要Unity 2022.2.0b8或者以上版本,除了com.unity.logging以外其他包都是Dots核心的package不再赘述。
1.调试部分
Dots从2018年问世到现在已经4年时间了,阻碍Dots发展的大家普遍的共识是:“开发方式对机器友好,对人不友好”。传统GameObject的开发流程中是对人友好的,但对机器不友好。Dots是则对机器友好,对人不友好!这样的开发模式人就不容易理解和看懂了。显然需要在人和机器中找一个平衡点,Dots1.0这次是带着诚意来的。
调试面板
Windows
->
Entities
->
Hierarchy (Dots层次面板视图)
->
Components(组件视图,展示所有Dots组件信息)
->
Systems(系统视图,展示所有系统)
->
Archetypes(展示当前Archetype的分部)
->
Journaling(可录制一段时间记录Entities的变化过程)

Entities Hierarchy(实体层次面板视图)
在实体层次面板视图中可看到最终运行时实体的排布信息。

如下图所示,在实体检测面板中可看到所有的实体组件信息,还可以单独展开查看,具体的组件中的数值。

在Relationships中还可以看到当前实体被那些系统所关注,展开后还可以看到具体Query的组件,以及读写状态。

以上创作流程已经和传统的GameObject方式类似了。
创作模式与运行模式
新版Dots提出了创作模式与运行模式的概念,之前的方式在关闭运行游戏以后所有的改动就会自动还原。当处于创作模式下,无论游戏运行中都是可以修改并且保存的。这就能解决有些编辑工作需要依赖游戏运行后,以前由于引擎限制了无法解决此类问题,所有修改都必须在非运行模式下。
创作模式(点击橘黄色的小圆圈切换)

运行模式

混合模式(表示可同时创作模式和运行模式)

创作模式中在运行游戏状态中也可以直接修改具体数值,然后点击保存即可。

组件中属性前面如果有橘黄色竖杠 “|”表示属于运行模式数据,关闭游戏数据会还原。没有则表示它是创作模式下的数据,运行时不会进行修改它。

组件
这里包含了所有组件,可以搜索反向找到它与那些实体之间的有关联

系统
这里列出了当前所有的系统,可以随时的关闭与启动某个系统,方便快速定位问题。

Archetypes
具体每个Archetypes所使用的组件分部以及精准的内存占用

通过这一组调试面板可以帮助开发者快速定位问题。
2.代码部分
这次代码部分的更新在使用侧这边并不是特别大,API的设计也围绕着使用者更加方便而展开,下面让我们来盘一盘有哪些改变。
Baker 全新烘焙管道
Conversion的替代者,用于将创作端的GameObject对象转换成实体对象。主要原因是Dots1.0支持了创作模式与运行模式,随时都可以对数据进行修改。看文档的意思以前都是运行时进行GameObject到Entity的转换,这导致编辑模式下需要额外维护创作的数据。通过新的Baker API可以得到更快的速度以及更稳健的性能。
从使用的角度来说就是换个API名字,主要还是把Monobehaviour上的数据转成实体组件。
public
class
SpawnerAuthoring
:
MonoBehaviour
{
public
GameObject
Prefab
;
class
Baker
:
Baker
<
SpawnerAuthoring
>
{
public
override
void
Bake
(
SpawnerAuthoring
authoring
)
{
AddComponent
(
new
Spawner
{
Prefab
=
GetEntity
(
authoring
.
Prefab
)
}
)
;
}
}
}
struct
Spawner
:
IComponentData
{
public
Entity
Prefab
;
}
Aspect 包装器
Aspect可以将多个实体组件包装在一个结构体中。如原本在遍历组件的时候需要在OnUpdate中写一些获取组件以及对应执行的方法,如果有多个系统或者代码公用就可以写在包装器中,代码写起来会更加方便。
readonly
partial
struct
VerticalMovementAspect
:
IAspect
{
readonly
RefRW
<
LocalToWorldTransform
>
m_Transform
;
readonly
RefRO
<
RotationSpeed
>
m_Speed
;
public
void
Move
(
double
elapsedTime
)
{
m_Transform
.
ValueRW
.
Value
.
Position
.
y
=
(
float
)
math
.
sin
(
elapsedTime
*
m_Speed
.
ValueRO
.
RadiansPerSecond
)
;
}
}
然后在OnUpdate或者JobUpdate中可以直接去Query这个Aspect,并且执行对应的方法。
public
void
OnUpdate
(
ref
SystemState
state
)
{
foreach
(
var
movement
in
SystemAPI
.
Query
<
VerticalMovementAspect
>
(
)
)
{
movement
.
Move
(
elapsedTime
)
;
}
}
上面的例子看似比较简单大家可以看看这两个内置的TransformAspect.cs和RigidBodyAspect.cs就明白为什么引擎会提供Aspect了。
在调试面板中也可以看到Aspects的数据结构

可启动/关闭组件
一个小小的启动或者关闭组件在DOTS中可以说是很复杂的操作,因为它会改变Archetypes的内存结构,还会影响Query非常影响性能。新版Dots引用了IEnableableComponent接口,配合SetComponentEnabled方法动态开关组件,这是一个底层的操作,号称比普通的删除添加组件要快9倍左右。
public
struct
TargetEnableable
:
IComponentData
,
IEnableableComponent
{
public
float3
target
;
}
// ...
var
archetype
=
m_Manager
.
CreateArchetype
(
typeof
(
EcsTestData
)
,
typeof
(
TargetEnableable
)
)
;
var
entities
=
m_Manager
.
CreateEntity
(
archetype
,
1024
,
World
.
UpdateAllocator
.
ToAllocator
)
;
EntityQuery
query
=
GetEntityQuery
(
typeof
(
TargetEnableable
)
)
;
//关闭第数组中下标4的实体组件TargetEnableable
m_Manager
.
SetComponentEnabled
<
TargetEnableable
>
(
entities
[
4
]
,
false
)
;
int
fooCount
=
query
.
CalculateEntityCount
(
)
;
// 关闭了一个所以这里返回 1023
ISystem
进一步优化性能Dots提供了ISystem接口,让System系统中的代码也具备Burst编译的优化可能,使用HCP而且不需要垃圾回收,性能直接起飞。
[
BurstCompile
]
public
partial
struct
RotationSpeedSystem
:
ISystem
{
[
BurstCompile
]
public
void
OnCreate
(
ref
SystemState
state
)
{
}
[
BurstCompile
]
public
void
OnDestroy
(
ref
SystemState
state
)
{
}
[
BurstCompile
]
public
void
OnUpdate
(
ref
SystemState
state
)
{
var
job
=
new
RotationSpeedJob
{
deltaTime
=
SystemAPI
.
Time
.
DeltaTime
}
;
job
.
ScheduleParallel
(
)
;
}
}
IJobEntity
原本的Entities.ForEach已经不推荐使用,它虽然简单方便但在一些复杂的环境下不好用,比如无法在ForEach中实现嵌套,无法在多个System中共同操作,取而代之的则是IJobEntity。用法非常简单在Execute中传入需要的实体组件即可。
partial
struct
RotationSpeedJob
:
IJobEntity
{
public
float
deltaTime
;
void
Execute
(
ref
LocalToWorldTransform
transform
,
in
RotationSpeed
speed
)
{
transform
.
Value
=
transform
.
Value
.
RotateY
(
speed
.
RadiansPerSecond
*
deltaTime
)
;
}
}
在任意位置也可以对当实体组件进行循环遍历。
foreach
(
var
(
rotateAspect
,
speedModifierRef
)
in
SystemAPI
.
Query
<
RotateAspect
,
RefRO
<
SpeedModifier
>
>
(
)
)
rotateAspect
.
Rotate
(
time
,
speedModifierRef
.
ValueRO
.
Value
)
;
3.性能调试部分
在Profiler中提供了Entity性能调试的窗口,如下图中可看到
CreateEntity AddComponent Set ShareCommponent RemoveComponet Destory Entity所有的耗时

在Entity Memory中也可以看到某帧分配的Archetype的内存分部情况,快速定位内存瓶颈。

启动日志收集以后可以看到每一步对实体组件操作的行为,这个功能在调试的时候有很大作用。比如一个数值不知道为什么变成0.5,可以通过搜索功能定位出修改到0.5的这个日志看看它的行为即可快速定位问题。
4.渲染部分
渲染部分的更新也是我认为非常震撼的部分,因为之前Dots之所以使用不起来主要就是渲染部分无法得到很好的支持,它对HDRP和URP都做了很好的支持,以下我会以URP的视角来进行说明。
粒子特效
正常的使用粒子特效,最终放到SubScene中就可以直接支持,不需要使用特殊材质。

光源、反射探头、Sprite精灵、文本、后处理也都支持了。

烘焙贴图
正常烘焙场景,使用SubScene就可以直接支持场景烘焙贴图。

蒙皮动画
蒙皮动画我觉得暂时属于半支持状态,Animation动画文件的解析需要自己处理,关键帧控制的节点变换信息需要自己传入。它的原理是这样的,首先会把骨骼节点转成Entity对象,对应SkinMeshRender有一个实体组件,接着就是GPU蒙皮动画了,它需要把骨骼变换后的位置传入GPU中播放蒙皮动画。


internal
class
AnimateRotationAuthoring
:
MonoBehaviour
{
[
Tooltip
(
"Local start rotation in degrees"
)
]
public
Vector3
FromRotation
=
new
Vector3
(
0f
,
0f
,
-
10f
)
;
[
Tooltip
(
"Local target rotation in degrees"
)
]
public
Vector3
ToRotation
=
new
Vector3
(
0f
,
0f
,
10f
)
;
[
Tooltip
(
"Time in seconds to complete a single loop of the animation"
)
]
public
float
Phase
=
5f
;
[
Range
(
0f
,
1f
)
]
[
Tooltip
(
"Phase shift as a percentage (0 to 1.0)"
)
]
public
float
Offset
;
}
然后在System中修改实体组件的变换,对应的还有旋转、缩放、平移。
partial
class
AnimateRotationSystem
:
SystemBase
{
const
float
k_Two_Pi
=
2f
*
math
.
PI
;
protected
override
void
OnUpdate
(
)
{
var
t
=
(
float
)
SystemAPI
.
Time
.
ElapsedTime
;
Entities
.
ForEach
(
(
ref
Rotation
rotation
,
in
AnimateRotation
data
)
=>
{
var
normalizedTime
=
0.5f
*
(
math
.
sin
(
k_Two_Pi
*
data
.
Frequency
*
(
t
+
data
.
PhaseShift
)
)
+
1f
)
;
rotation
.
Value
=
math
.
slerp
(
data
.
From
,
data
.
To
,
normalizedTime
)
;
}
)
.
ScheduleParallel
(
)
;
}
}
注意此时只是修改了实体组件的变化,真正影响到蒙皮动画则是下面这几个系统,与兴趣的朋友可以跟一下代码其实传统的GPU蒙皮的那一套。

BatchRendererGroup
其实早在2019年我就研究过这个API,当时它就是一个高级的GPU Instancing。BatchRendererGroup现在终于支持SrpBatcher了,在这之前Srp Batcher无法通过代码来绘制必须是GameObject的方式,所以它是跨时代的API。
m_BatchRendererGroup
=
new
BatchRendererGroup
(
this
.
OnPerformCulling
,
IntPtr
.
Zero
)
;
//...
// Batch metadata buffer
int
objectToWorldID
=
Shader
.
PropertyToID
(
"unity_ObjectToWorld"
)
;
int
worldToObjectID
=
Shader
.
PropertyToID
(
"unity_WorldToObject"
)
;
int
colorID
=
Shader
.
PropertyToID
(
"_BaseColor"
)
;
var
batchMetadata
=
new
NativeArray
<
MetadataValue
>
(
2
,
Allocator
.
Temp
,
NativeArrayOptions
.
UninitializedMemory
)
;
batchMetadata
[
0
]
=
CreateMetadataValue
(
objectToWorldID
,
0
,
true
)
;
batchMetadata
[
1
]
=
CreateMetadataValue
(
colorID
,
itemCount
*
UnsafeUtility
.
SizeOf
<
Vector4
>
(
)
*
3
,
true
)
;
//...
m_batchID
=
m_BatchRendererGroup
.
AddBatch
(
batchMetadata
,
m_GPUPersistanceBufferHandle
)
;
DOTS1.0还支持了物理以及NetCode等新功能,在我心里我认为最重要的还是对渲染支持的这一块。
参考
官方说明文档:[https://forum.unity.com/threads/experimental-entities-1-0-is-available.1341065/ ](https://forum.unity.com/threads/experimental-entities-1-0-is-available.1341065/ )
切到1.0分支,每个都可以单独打开,例子非常全面(我暂时使用版本是unity2022.2.0b9)

尤其是渲染这块的支持
更多推荐
所有评论(0)