改造

首先我们创建一个 ExecuteAuthoring 类,将其挂载到 SubScene 的一个空的 gameObject 之上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
namespace HelloCube.Execute
{
public class ExecuteAuthoring : MonoBehaviour
{
public bool MainThread;
public bool IJobEntity;
public bool Aspects;
public bool Prefabs;
public bool IJobChunk;
public bool Reparenting;
public bool EnableableComponents;
public bool GameObjectSync;

class Baker : Baker<ExecuteAuthoring>
{
public override void Bake(ExecuteAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.None);

if (authoring.MainThread) AddComponent<MainThread>(entity);
if (authoring.IJobEntity) AddComponent<IJobEntity>(entity);
if (authoring.Aspects) AddComponent<Aspects>(entity);
if (authoring.Prefabs) AddComponent<Prefabs>(entity);
if (authoring.IJobChunk) AddComponent<IJobChunk>(entity);
if (authoring.Reparenting) AddComponent<Reparenting>(entity);
if (authoring.EnableableComponents) AddComponent<EnableableComponents>(entity);
if (authoring.GameObjectSync) AddComponent<GameObjectSync>(entity);
}
}
}

public struct MainThread : IComponentData
{

}

public struct IJobEntity : IComponentData
{

}

public struct Aspects : IComponentData
{

}

public struct Prefabs : IComponentData
{

}

public struct IJobChunk : IComponentData
{

}

public struct Reparenting : IComponentData
{

}

public struct EnableableComponents : IComponentData
{

}

public struct GameObjectSync : IComponentData
{

}
}

此处我们可以发现:这里 GetEntity 使用的是 TransformUsageFlags.None,而前面使用的是 TransformUsageFlags.Dynamic。两者的区别在于:

  • Dynamic:表明 entity 需要在运行时移动必要的 transform 组件,如:LocalTransformLocalToWorld
  • None:表明 entity 不需要 transform

也就是在此处我们的 var entity = GetEntity(TransformUsageFlags.None);Bake 的时候获取了全部的实体,然后根据我们的选择,对其打上不同的组件,各个系统根据这些组件选择运行,也就做到了我们快速切换此 ECS 架构执行的 System

改造成 IJobEntitySystem 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
namespace HelloCube.JobEntity
{
public partial struct RotationSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<Execute.IJobEntity>();
}

[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var job = new RotationJob() { deltaTime = SystemAPI.Time.DeltaTime };
job.Schedule();
// state.Dependency = job.Schedule(state.Dependency); 此处与 job.Schedule(); 等价
}
}

[BurstCompile]
partial struct RotationJob : IJobEntity
{
public float deltaTime;

// ref 是方法内可变引用,in 是方法类不可变引用
private void Execute(ref LocalTransform transform, in RotationSpeed speed)
{
transform = transform.RotateY(speed.RadiansPerSecond * deltaTime);
}
}
}

相比较于之前的,可以看到,其是把旋转这个任务提取了出来。只需使用 RotationJob 继承 IJobEntity。然后在 Execute 中编写逻辑即可。观察发现:

  • 此处的 ref 对应上一次的 RefRW,也就是读写引用访问
  • 此处的 in 对应上一次的 RefRO,也就是只读引用访问

接着在 SystemOnUpdate 中使用 Schedule 执行即可。此处的 Schedule 有两种写法,此处等价:

  • job.Schedule();:将 IJobEntity 实例对象添加到任务调度队列(job scheduler queue),以便顺序(非并行)执行。自动使用系统的 Dependency 属性作为输入和输出依赖性。
  • state.Dependency = job.Schedule(state.Dependency);:将IJobEntity 实例对象添加到任务调度队列,顺序(非并行)执行。其参数接受一个 JobHandle dependsOn,为了用来标识已经调度的作业(作业指实现了 IJobEntity 接口的任务)。这个标识的用处为:如果一个任务在写入一个组件,那么其不能与其他读取或写入相同组件的作业并行执行。只读取相同组件的作业可以并行执行,也就是避免竞态条件

更多请查阅 官方文档 Method Schedule

SystemAPI.Query+foreach 和 IJobEntity 的比较

Iterate over component data with SystemAPI.Query

Iterate over component data with IJobEntity

以上是两种迭代组件数据方式的官网文档。其中,SystemAPI.Query 有一句话:

You can’t store in a variable and then use it in multiple statements: there isn’t a way to reuse . This is because the implementation of the API relies on knowing what the query types are at compile time. The source-generation solution doesn’t know at compile-time what to generate and cache, which type handles to call on, nor which dependencies to complete.

相应的 IJobEntity 当中也有相关的一句话:

IJobEntity is similar to Entities.ForEach, however you can reuse throughout several systems, so you should use it over where possible.

这两段话都是在表达一个意思:缓存重用

  • SystemAPI.Query 不能将其存储在一个变量然后在多个语句中使用(也就是缓存)。因为其没有办法重用,因为 API 的实现依赖于在编译时了解查询的类型,源代码在编译的时候不知道要生成什么和缓存什么,要调用哪些类型句柄,也不知道要完成哪些依赖项。
  • IJobEntity 则表明可以在多个系统中重用。DOTS 会自动为我们处理。

而且 IJobEntity 是支持使用 ScheduleParallel 来并行处理,而 SystemAPI.Query+foreach 只能顺序(非并行)执行。因此建议是尽可能多的使用 IJobEntity