查看原文
其他

DOTS 1.0 预览版,更新了什么?

雨松 Unity官方平台 2023-08-28

近期,DOTS 1.0 的预览版已经发布,开发者社区大佬雨松 MOMO 以及社区牛人达哥纷纷第一时间进行了学习,并输出文章和视频,帮助大家掌握本次重大更新。

达哥自制视频,带你入门 DOTS 框架 ↓
https://www.bilibili.com/video/BV1i14y177Ny
雨松 MOMO 深度解读,Dots 1.0 更新了什么 ↓
https://developer.unity.cn/projects/633c1ed4edbc2a09bd9fefd8

本文摘自雨松 MOMO 的文章,欢迎学习。


Dots 1.0 更新的包如下:
 com.unity.entities
 com.unity.entities.graphics
 com.unity.netcode
 com.unity.physics
 com.unity.burst
 com.unity.collections

 com.unity.logging (异步日志方案,比Debug.log同步日志方案速度更快)

开始之前需要升级一下 Unity 的版本,需要 Unity 2022.2.0b8 或者以上版本,除了 com.unity.logging 以外其他包都是 Dots 核心的 package 不再赘述。

调试部分

Dots 从 2018 年问世到现在已经4年时间了,阻碍 Dots 发展的大家普遍的共识是:“开发方式对机器友好,对人不友好”。传统 GameObject 的开发流程中是对人友好的,但对机器不友好。Dots 是则对机器友好,对人不友好!这样的开发模式人就不容易理解和看懂了。显然需要在人和机器中找一个平衡点,Dots 1.0 这次是带着诚意来的。

  调试面板

  Entities Hierarchy (实体层次面板视图)

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

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

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

以上创作流程已经和传统的 GameObject 方式类似了。
  创作模式与运行模式

新版 Dots 提出了创作模式与运行模式的概念,之前的方式在关闭运行游戏以后所有的改动就会自动还原。当处于创作模式下,无论游戏运行中都是可以修改并且保存的。这就能解决有些编辑工作需要依赖游戏运行后,以前由于引擎限制了无法解决此类问题,所有修改都必须在非运行模式下。

创作模式(点击橘黄色的小圆圈切换)

运行模式

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

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

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

  组件

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

  系统

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

  Archetypes

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

通过这一组调试面板可以帮助开发者快速定位问题。

代码部分

这次代码部分的更新在使用侧这边并不是特别大,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的实体组件TargetEnableablem_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);

雨松 MOMO 在文章中还针对 [性能调试部分] 和 [渲染部分] 有详细解读,欢迎点击“阅读原文”解锁:

https://developer.unity.cn/projects/633c1ed4edbc2a09bd9fefd8

另外,Unity 社区特意推出《DOTS 1.0 初体验》系列直播,接下来的三周,每周五 12:30,B 站 Unity 官方直播间见 




长按关注

Unity 官方微信

第一时间了解Unity引擎动向,学习最新开发技巧





 点击“阅读原文”,跳转社区原文 


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存