我们使用ISystem来创建System

创建MovingISystem

在Scripts文件夹中新建MovingISystem文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using Unity.Entities;

public partial struct MovingISystem : ISystem
{
public void OnCreate(ref SystemState state)
{
}

public void OnDestroy(ref SystemState state)
{
}

public void OnUpdate(ref SystemState state)
{
var randomComponent = SystemAPI.GetSingletonRW<RandomComponent>();
foreach (MoveToPositionAspect moveToPositionAspect in SystemAPI.Query<MoveToPositionAspect>())
{
moveToPositionAspect.Move(SystemAPI.Time.DeltaTime, randomComponent);
}
}
}

我们让MovingISystem实现和MovingSystemBase相同的功能,可以看到ISystem和SystemBase相比多了两个方法:

1
2
3
public void OnCreate(ref SystemState state)

public void OnDestroy(ref SystemState state)

其实SystemBase也能重载OnCreate和OnDestroy,只不过它只需要强制重载OnUpdate。

进入运行模式,可以看到我们的Entity以两倍的速度运动,这是因为MovingISystemMovingSystemBase同时起效,我们只需要在Entities——Systems窗口中将“Moving System Base”停止即可。

引入Jobs

ISystem可以让我们引入Jobs。

我们希望可以有很多的Entities执行Move方法,这些方法运行在多个work thread中,而不是像之前MovingSystemBase在主线程里通过foreach轮询。

修改MoveToPositonAspect

目前的Move方法是在MoveToPositonAspect实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;

public readonly partial struct MoveToPositionAspect : IAspect
{
//...

public void Move(float deltaTime,RefRW<RandomComponent> randomComponent)
{
float3 direction = math.normalize(targetPosition.ValueRW.value - transformAspect.WorldPosition);

transformAspect.WorldPosition += direction * deltaTime * speed.ValueRO.value;

float reachedTargetDistance = .5f;
if( math.distance(transformAspect.WorldPosition,targetPosition.ValueRW.value) < reachedTargetDistance)
{
targetPosition.ValueRW.value = GetRandomPosition(randomComponent);
}
}

//...
}

这个方法有一个问题,就是对RefRW<RandomComponent>的引用,在多线程程序中,单例的引用必须警惕,我们这里先分离这个方法:

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
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;

public readonly partial struct MoveToPositionAspect : IAspect
{
private readonly Entity entity;

private readonly TransformAspect transformAspect;
private readonly RefRO<Speed> speed;
private readonly RefRW<TargetPosition> targetPosition;

public void Move(float deltaTime)
{
float3 direction = math.normalize(targetPosition.ValueRW.value - transformAspect.WorldPosition);

transformAspect.WorldPosition += direction * deltaTime * speed.ValueRO.value;


}
public void TestReachedTargetPosition(RefRW<RandomComponent> randomComponent)
{
float reachedTargetDistance = .5f;
if (math.distance(transformAspect.WorldPosition, targetPosition.ValueRW.value) < reachedTargetDistance)
{
targetPosition.ValueRW.value = GetRandomPosition(randomComponent);
}
}
private float3 GetRandomPosition(RefRW<RandomComponent> randomComponent)
{
return new float3(
randomComponent.ValueRW.random.NextFloat(0f, 15f),
0,
randomComponent.ValueRW.random.NextFloat(0f, 15f)
);
}
}

这样其中的Move方法就是一个完全可以多线程运行的方法了。

添加MoveJob和TestReachedTargetPositionJob

修改MovingISystem文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//...
//添加
public partial struct MoveJob : IJobEntity
{
public float deltaTime;
public void Execute(MoveToPositionAspect moveToPositionAspect)
{
moveToPositionAspect.Move(deltaTime);
}
}
public partial struct TestReachedTargetPositionJob : IJobEntity
{
public RefRW<RandomComponent> randomComponent;
public void Execute(MoveToPositionAspect moveToPositionAspect)
{
moveToPositionAspect.TestReachedTargetPosition(randomComponent);
}
}

Unity.Entities提供了IJobEntityIJobChunk两个接口,前者能够轮询Entity,后者能够轮询每个JobChunk

只要继承的这些接口有Execute方法,就会执行此方法

public void Execute(MoveToPositionAspect moveToPositionAspect)Execute方法可以使用Aspect、Component作为参数,注意添加其他参数会导致此Job无法在多线程运行(只能调用Run、不能调用ScheduleScheduleParallel

需要是“partial struct”,在Unity DOTS中,partial的目的是为了更好地使用源生成器生成代码。

修改MovingISystem文件:

去除foreach,使用创建Job的方式来执行Move

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
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;

[BurstCompile]
public partial struct MovingISystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var randomComponent = SystemAPI.GetSingletonRW<RandomComponent>();
float deltaTime = SystemAPI.Time.DeltaTime;
JobHandle jobHandle = new MoveJob { deltaTime = deltaTime }.ScheduleParallel(state.Dependency);
jobHandle.Complete();
new TestReachedTargetPositionJob { randomComponent = randomComponent }.Run();
}
}
[BurstCompile]
public partial struct MoveJob : IJobEntity
{
public float deltaTime;
[BurstCompile]
public void Execute(MoveToPositionAspect moveToPositionAspect)
{
moveToPositionAspect.Move(deltaTime);
}
}
[BurstCompile]
public partial struct TestReachedTargetPositionJob : IJobEntity
{
[NativeDisableUnsafePtrRestriction] public RefRW<RandomComponent> randomComponent;
[BurstCompile]
public void Execute(MoveToPositionAspect moveToPositionAspect)
{
moveToPositionAspect.TestReachedTargetPosition(randomComponent);
}
}

MoveJob是完全多线程的,所以我们使用ScheduleParallel,并且使用ScheduleParallel(JobHandle dependsOn)重载来返回一个jobhandle。调用jobHandle.Complete();是为了保证“MoveJob”先运行才能运行TestReachedTargetPositionJob

TestReachedTargetPositionJob需要运行在主线程上(直接调用Run),因为它引用了RefRW<RandomComponent>,而且此引用还需要添加NativeDisableUnsafePtrRestriction特性

在Job的构造函数中不能直接调用SystemAPI,所以我们要写float deltaTime = SystemAPI.Time.DeltaTime;

我们在ISystem和IJobEntity上添加BurstCompile特性,并且在它们各自的方法内添加BurstCompile特性

查看Profiler

在MainThread查看MovingISystem

MovingISystem

在Worker查看MoveJob

MoveJob