DOTS how to work
本文最后编辑于2019.12.31, DOTS随时可能修改接口,修改结构。
创建一个entity
如何把一个场景里的GO变成不存在场景里的Entity呢。一个典型的物体,有Transform, MeshFilter, MeshRenderer。给它挂上ConvertToEntity,点播放就会自动把它变成Entity了。 其中Unity自动做了几件事: 根据Transform来给这个新创建的Entity添加了Translation, Rotation。根据MeshFilter, MeshRenderer来给Entity添加渲染相关的Components。比如渲染转换这一步,是通过MeshRendererConversion : GameObjectConversionSystem来完成的。详细代码项目里直接看。 而我们自定义了一些的IComponentData,要怎么样在Editor里编辑好,然后让ConvertToEntity把它改成内部的数据呢?
方法1:定义IComponentData, 加上attribute [GenerateAuthoringComponent]. 在Unity约定俗成中,对于某个componentData, 它对应转化MonoBehavior会取名Authoring,就是作者的意思。而这个attribute会自动创建authoring代码。
[GenerateAuthoringComponent]
public struct RotationSpeed_ForEach : IComponentData
{
public float RadiansPerSecond;
}
方法2:手动定义Authoring脚本。他需要定义和组件同样的public数据,RequiresEntityConversion必有,AddComponentMenu可有可无,ConverterVersion应该是可有可无的。 IConvertGameObjectToEntity接口是必须的,实现这个接口来convert我们的数据。
[Serializable]
public struct RotationSpeed_IJobChunk : IComponentData
{
public float RadiansPerSecond;
}
[RequiresEntityConversion]
[AddComponentMenu("DOTS Samples/IJobChunk/Rotation Speed")]
[ConverterVersion("joe", 1)]
public class RotationSpeedAuthoring_IJobChunk : MonoBehaviour, IConvertGameObjectToEntity
{
public float DegreesPerSecond = 360.0F;
// The MonoBehaviour data is converted to ComponentData on the entity.
// We are specifically transforming from a good editor representation of the data (Represented in degrees)
// To a good runtime representation (Represented in radians)
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
var data = new RotationSpeed_IJobChunk { RadiansPerSecond = math.radians(DegreesPerSecond) };
dstManager.AddComponentData(entity, data);
}
}
最后要构建一个System来使用数据。System里创建Job有两种方式。第一种是用lambda表达式,第二种是定义一个Job struct。两种方式都可以利用BurstComplier来提高性能。
方法1:定义JobComponentSystem, 在OnUpdate时返回JobHandle。这里没有用Burst,使用方法是在foreach之前.WithBurst(FloatMode.Default, FloatPrecision.Standard, true)
ref in out是比较重要的,对于编译器来说,它可以决定job的依赖和是否并行,串行。
public class RotationSpeedSystem_ForEach : JobComponentSystem
{
// OnUpdate runs on the main thread.
protected override JobHandle OnUpdate(JobHandle inputDependencies)
{
float deltaTime = Time.DeltaTime;
// Schedule job to rotate around up vector
var jobHandle = 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));
})
.Schedule(inputDependencies);
// Return job handle as the dependency for this system
return jobHandle;
}
}
方法2: 定义IJobChunk。这里是把所有数据chunk一起处理了,和IJobForEach相对。具体区别。 EntityQuery是查询定义。
public class RotationSpeedSystem_IJobChunk : JobComponentSystem
{
EntityQuery m_Group;
protected override void OnCreate()
{
// Cached access to a set of ComponentData based on a specific query
m_Group = GetEntityQuery(typeof(Rotation), ComponentType.ReadOnly<RotationSpeed_IJobChunk>());
}
// Use the [BurstCompile] attribute to compile a job with Burst. You may see significant speed ups, so try it!
[BurstCompile]
struct RotationSpeedJob : IJobChunk
{
public float DeltaTime;
public ArchetypeChunkComponentType<Rotation> RotationType;
[ReadOnly] public ArchetypeChunkComponentType<RotationSpeed_IJobChunk> RotationSpeedType;
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
{
var chunkRotations = chunk.GetNativeArray(RotationType);
var chunkRotationSpeeds = chunk.GetNativeArray(RotationSpeedType);
for (var i = 0; i < chunk.Count; i++)
{
var rotation = chunkRotations[i];
var rotationSpeed = chunkRotationSpeeds[i];
// Rotate something about its up vector at the speed given by RotationSpeed_IJobChunk.
chunkRotations[i] = new Rotation
{
Value = math.mul(math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * DeltaTime))
};
}
}
}
// OnUpdate runs on the main thread.
protected override JobHandle OnUpdate(JobHandle inputDependencies)
{
// Explicitly declare:
// - Read-Write access to Rotation
// - Read-Only access to RotationSpeed_IJobChunk
var rotationType = GetArchetypeChunkComponentType<Rotation>();
var rotationSpeedType = GetArchetypeChunkComponentType<RotationSpeed_IJobChunk>(true);
var job = new RotationSpeedJob()
{
RotationType = rotationType,
RotationSpeedType = rotationSpeedType,
DeltaTime = Time.DeltaTime
};
return job.Schedule(m_Group, inputDependencies);
}
}
IDeclareReferencedPrefabs
如果需要传入一个prefab给System用,Authoring需要实现这个接口。
public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
{
referencedPrefabs.Add(Prefab);
}
//Convert中
Prefab = conversionSystem.GetPrimaryEntity(Prefab)
长度不定的数组
对于数组,DOTS需要声明一个struct继承IBufferElementData,然后当作一个单独的Component,贴到Entity上。注意,这不是类型定义,而是Component定义。组合起来成为Archetype。理解这件事需要自己写一下代码。
public struct float3BufferElement : IBufferElementData
{
public float3 Pos;
public static implicit operator float3(float3BufferElement e)
{
return e.Pos;
}
public static implicit operator float3BufferElement(float3 e)
{
return new float3BufferElement { Pos = e };
}
public static implicit operator float3BufferElement(Vector3 e)
{
return new float3BufferElement { Pos = e };
}
}
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
var buffer = dstManager.AddBuffer<float3BufferElement>(entity);
for (int i = 0; i < Points.Length; i++)
{
buffer.Add(Points[i]);
}
}
ForEach((Entity entity, int entityInQueryIndex, in ListSpawner spawner, in DynamicBuffer<float3BufferElement> buffer)=>{
...
})
长度一定,不会改变的数组或数据 Blob Data
blob asset是不变的数据,存储在unmaganaged memory里。它可以包含原始类型,字符串,结构体,数组,数组的数组。数组和结构体只能包含blittable types.字符串是BlobString。一旦创建,就不能更改。你可以新建一个,然后引用新的。 这个功能比较适合保存一些配置信息,在authoring时创建Component,将引用存在Component里。多个Component可以引用同一个blob asset。
public struct Blobfloat3
{
public BlobArray<float3> Positions;
}
public struct ListSpawner : IComponentData
{
public BlobAssetReference<Blobfloat3> Pos;
public Entity Prefab;
}
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
var builder = new BlobBuilder(Unity.Collections.Allocator.Temp);
ref var root = ref builder.ConstructRoot<Blobfloat3>();
var float3Array = builder.Allocate(ref root.Positions, Points.Length);
for (int i = 0; i < Points.Length; i++)
{
float3Array[i] = Points[i];
}
var blobAsset = builder.CreateBlobAssetReference<Blobfloat3>(Unity.Collections.Allocator.Persistent);
builder.Dispose();
var data = new ListSpawner
{
Prefab = conversionSystem.GetPrimaryEntity(Prefab),
Pos = blobAsset
};
dstManager.AddComponentData(entity, data);
}
ForEach((Entity entity, int entityInQueryIndex, in ListSpawner spawner)=>{
...
})