我们使用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以两倍的速度运动,这是因为MovingISystem
和MovingSystemBase
同时起效,我们只需要在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 = .5 f; 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 = .5 f; 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提供了IJobEntity
和IJobChunk
两个接口,前者能够轮询Entity,后者能够轮询每个JobChunk
只要继承的这些接口有Execute方法,就会执行此方法
public void Execute(MoveToPositionAspect moveToPositionAspect)
Execute方法可以使用Aspect、Component作为参数,注意添加其他参数会导致此Job无法在多线程运行(只能调用Run
、不能调用Schedule
或ScheduleParallel
需要是“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
在Worker查看MoveJob