Unity的Dots技术最近是很热的,我也在闲暇之余简单学习了一下,学习新的技术会让人快乐是件不争的事实对吧(努力安慰自己……)
Data-Oriented Technology Stack,这是它的全称,翻译过来叫多线程式数据导向型技术堆栈,这个是官方的翻译,多么的高大上,它首先给我带来的感觉就是一种新的编程思想,那就是面向数据,对于习惯了面向对象编程的我们Unity从业者来讲,接受它可能挺不适应的,至少在我最近的学习感受是这样。
ecs,即Entity,Component和System ,Entity它代表的就是一个实例,一个承载关联数据和行为的实体,就好比是一张身份证ID,有了它,对于一个人来说,它的行为,信息我们可以通过检索轻松拿到,Component承载的是数据,System承载的就是行为方法。这样的架构使得数据和行为解耦,提高了代码的扩展性
JobSystem是Unity退出的自己的多线程管理机制,它充分解放了计算机多核多线程的运算力,能为代码的执行速度带来不小的提升,而且用户不用处理繁琐的线程管理事务,十分的方便~关于JobSystem
Unity 构建了名为 Burst 的代码生成器和编译器,它有更高的效率。
以官方的小球旋转为例,首先要做的就是构建Component数据类型,本利很简单,数据只有旋转速度,Component代码如下:
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
public struct RotationSpeedComponent : IComponentData
{
public float Speed;
}
注意:ECS中的Component数据必须是实现IComponentData的结构体,为什么要是结构体不是类呢?因为struct直接在栈里分配,它不像class一样分配在托管堆里,不进行垃圾回收,实现“连续紧凑的内存布局”,相比于数据分布在内存的各个角落的class,效率也会更高~
然后是System:
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using Unity.Transforms;
public class RotationSpeedSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach((ref RotationSpeedComponent rotationSpeedComponent, ref Unity.Transforms.Rotation translation) =>
{
translation.Value = math.mul(math.normalize(translation.Value),quaternion.AxisAngle(math.up(), rotationSpeedComponent.Speed*Time.deltaTime));
});
}
}
Unity.Mathematics是UnityECS中新写的数学类,也是提高性能的举措之一,例子中的RotationSpeedSystem 继承自 ComponentSystem,它实现OnUpdate方法,这相当于我们熟悉的MonoBehavior的Update方法,实现每帧刷新的逻辑。
本例中Entity的构建要借助于Unity.Entities中的ConvertToEntity脚本,还要实现IConvertGameObjectToEntity接口,在方法中关联Component和System,将GameObject转化为Entity,代码如下:
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;
public class EntitiesManager : MonoBehaviour,IConvertGameObjectToEntity
{
public float speed=3;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void Convert(Entity entity, EntityManager entityManager, GameObjectConversionSystem conversionSystem)
{
var moveComponent=new RotationSpeedComponent(){Speed = speed};
entityManager.AddComponentData(entity, moveComponent);
}
}
这个脚本EntitiesManager 挂在GameObject身上,注意还要添加ConvertToEntity脚本~
这样就简单实现了一个ECS结构,当然用官方的话,这是混合ECS的写法,它与MonoBehaviour还是有一些瓜葛~
ECS也在不断迭代更新中,最新的版本,我们可以利用[GenerateAuthoringComponent]这个属性标签对Component声明,来省去EntitiesManager 脚本中关联Component和System的操作,使得代码更简洁~
还是物体旋转,如果我们需要数以万计的物体旋转,是可以用JobSystem实现多线程并行处理来,官方给出的ECS结合JobSystem的使用案例:
首先Component还是一样,这次用上面提到的[GenerateAuthoringComponent]属性标签来简化代码:
using System;
using Unity.Entities;
// ReSharper disable once InconsistentNaming
[GenerateAuthoringComponent]
public struct RotationSpeed_ForEach : IComponentData
{
public float RadiansPerSecond;
}
在System中我们用 Entities.WithName方法来找到我们要处理的Component,并用ForEach处理每个的旋转行为,代码:
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
// This system updates all entities in the scene with both a RotationSpeed_ForEach and Rotation component.
// ReSharper disable once InconsistentNaming
public class RotationSpeedSystem_ForEach : SystemBase
{
// OnUpdate runs on the main thread.
protected override void OnUpdate()
{
float deltaTime = Time.DeltaTime;
// Schedule job to rotate around up vector
Entities
.WithName("RotationSpeedSystem_ForEach")
.ForEach((ref Rotation rotation, in RotationSpeed_ForEach rotationSpeed) =>
{
rotation.Value = math.mul(
math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * deltaTime));
})
.ScheduleParallel();
}
}
ScheduleParallel方法其实就是运用了JobSystem的Schedule方法来实现对多个物体旋转的并行处理。
RotationSpeed_ForEach .cs要挂在我们的预制体上,可以看到,标签自动使得脚本变成RotationSpeed_ForEach Authoring,其实框架系统自动实现了与RotationSpeedSystem_ForEach的关联
然后是生成一万个预制物体的代码:
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
// ReSharper disable once InconsistentNaming
[AddComponentMenu("DOTS Samples/SpawnFromMonoBehaviour/Spawner")]
public class Spawner_FromMonoBehaviour : MonoBehaviour
{
public GameObject Prefab;
public int CountX = 100;
public int CountY = 100;
void Start()
{
// Create entity prefab from the game object hierarchy once
var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);
var prefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(Prefab, settings);
var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
for (var x = 0; x < CountX; x++)
{
for (var y = 0; y < CountY; y++)
{
// Efficiently instantiate a bunch of entities from the already converted entity prefab
var instance = entityManager.Instantiate(prefab);
// Place the instantiated entity in a grid with some noise
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});
}
}
}
}
Entity.Instantiate()是实例外化一个Entity的API。SetComponentData讲每个实例加入EntityManager中。
我们运行下,可以看到,它的FPS还是挺高的,性能很棒~
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200302103525263.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzE5NzE0NDA3,size_16,color_FFFFFF,t_70
下面是用传统的GameObject.Instantiate方法复制预制体并在MonoBehaviour. Update方法中处理旋转的方法,它的性能比起ECS+Jobsystem差了不止一星半点。。
以上就是对ECS 官方案例的学习解读,可能有很多不足之处,望指正~ 附官方案例源码UnityECSSamples