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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
|
#pragma warning disable CS0282
#if MODULE_ENTITIES
using Unity.Entities;
using Unity.Transforms;
using Unity.Burst;
using Unity.Jobs;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Jobs;
using GCHandle = System.Runtime.InteropServices.GCHandle;
namespace Pathfinding.ECS {
using Pathfinding;
using Pathfinding.ECS.RVO;
using Pathfinding.Drawing;
using Pathfinding.Util;
using Unity.Profiling;
using UnityEngine.Profiling;
[BurstCompile]
[UpdateAfter(typeof(FollowerControlSystem))]
[UpdateAfter(typeof(RVOSystem))]
[UpdateAfter(typeof(FallbackResolveMovementSystem))]
[UpdateInGroup(typeof(AIMovementSystemGroup))]
[RequireMatchingQueriesForUpdate]
public partial struct AIMoveSystem : ISystem {
EntityQuery entityQueryPrepareMovement;
EntityQuery entityQueryWithGravity;
EntityQuery entityQueryMove;
EntityQuery entityQueryRotation;
EntityQuery entityQueryGizmos;
EntityQuery entityQueryMovementOverride;
JobRepairPath.Scheduler jobRepairPathScheduler;
ComponentTypeHandle<MovementState> MovementStateTypeHandleRO;
ComponentTypeHandle<ResolvedMovement> ResolvedMovementHandleRO;
public static EntityQueryBuilder EntityQueryPrepareMovement () {
return new EntityQueryBuilder(Allocator.Temp)
.WithAllRW<MovementState>()
.WithAllRW<ManagedState>()
.WithAllRW<LocalTransform>()
.WithAll<MovementSettings, DestinationPoint, AgentMovementPlane, AgentCylinderShape>()
// .WithAny<ReadyToTraverseOffMeshLink>() // TODO: Use WithPresent in newer versions
.WithAbsent<AgentOffMeshLinkTraversal>();
}
public void OnCreate (ref SystemState state) {
jobRepairPathScheduler = new JobRepairPath.Scheduler(ref state);
MovementStateTypeHandleRO = state.GetComponentTypeHandle<MovementState>(true);
ResolvedMovementHandleRO = state.GetComponentTypeHandle<ResolvedMovement>(true);
entityQueryRotation = state.GetEntityQuery(
ComponentType.ReadWrite<LocalTransform>(),
ComponentType.ReadOnly<MovementSettings>(),
ComponentType.ReadOnly<MovementState>(),
ComponentType.ReadOnly<AgentCylinderShape>(),
ComponentType.ReadOnly<AgentMovementPlane>(),
ComponentType.ReadOnly<MovementControl>(),
ComponentType.ReadWrite<ResolvedMovement>(),
ComponentType.ReadOnly<SimulateMovement>(),
ComponentType.ReadOnly<SimulateMovementFinalize>()
);
entityQueryMove = state.GetEntityQuery(
ComponentType.ReadWrite<LocalTransform>(),
ComponentType.ReadOnly<AgentCylinderShape>(),
ComponentType.ReadOnly<AgentMovementPlane>(),
ComponentType.ReadWrite<MovementState>(),
ComponentType.ReadOnly<MovementSettings>(),
ComponentType.ReadOnly<ResolvedMovement>(),
ComponentType.ReadWrite<MovementStatistics>(),
ComponentType.ReadOnly<SimulateMovement>(),
ComponentType.ReadOnly<SimulateMovementFinalize>()
);
entityQueryWithGravity = state.GetEntityQuery(
ComponentType.ReadWrite<LocalTransform>(),
ComponentType.ReadOnly<AgentCylinderShape>(),
ComponentType.ReadWrite<AgentMovementPlane>(),
ComponentType.ReadWrite<MovementState>(),
ComponentType.ReadOnly<MovementSettings>(),
ComponentType.ReadWrite<ResolvedMovement>(),
ComponentType.ReadWrite<MovementStatistics>(),
ComponentType.ReadOnly<MovementControl>(),
ComponentType.ReadWrite<GravityState>(),
ComponentType.ReadOnly<AgentMovementPlaneSource>(),
ComponentType.ReadOnly<SimulateMovement>(),
ComponentType.ReadOnly<SimulateMovementFinalize>()
);
entityQueryPrepareMovement = jobRepairPathScheduler.GetEntityQuery(Allocator.Temp).WithAll<SimulateMovement, SimulateMovementRepair>().Build(ref state);
entityQueryGizmos = state.GetEntityQuery(
ComponentType.ReadOnly<LocalTransform>(),
ComponentType.ReadOnly<AgentCylinderShape>(),
ComponentType.ReadOnly<MovementSettings>(),
ComponentType.ReadOnly<AgentMovementPlane>(),
ComponentType.ReadOnly<ManagedState>(),
ComponentType.ReadOnly<MovementState>(),
ComponentType.ReadOnly<ResolvedMovement>(),
ComponentType.ReadOnly<SimulateMovement>()
);
entityQueryMovementOverride = state.GetEntityQuery(
ComponentType.ReadWrite<ManagedMovementOverrideBeforeMovement>(),
ComponentType.ReadWrite<LocalTransform>(),
ComponentType.ReadWrite<AgentCylinderShape>(),
ComponentType.ReadWrite<AgentMovementPlane>(),
ComponentType.ReadWrite<DestinationPoint>(),
ComponentType.ReadWrite<MovementState>(),
ComponentType.ReadWrite<MovementStatistics>(),
ComponentType.ReadWrite<ManagedState>(),
ComponentType.ReadWrite<MovementSettings>(),
ComponentType.ReadWrite<ResolvedMovement>(),
ComponentType.ReadWrite<MovementControl>(),
ComponentType.Exclude<AgentOffMeshLinkTraversal>(),
ComponentType.ReadOnly<SimulateMovement>(),
ComponentType.ReadOnly<SimulateMovementControl>()
);
}
static readonly ProfilerMarker MarkerMovementOverride = new ProfilerMarker("MovementOverrideBeforeMovement");
public void OnDestroy (ref SystemState state) {
jobRepairPathScheduler.Dispose();
}
public void OnUpdate (ref SystemState systemState) {
var draw = DrawingManager.GetBuilder();
// This system is executed at least every frame to make sure the agent is moving smoothly even at high fps.
// The control loop and local avoidance may be running less often.
// So this is designated a "cheap" system, and we use the corresponding delta time for that.
var dt = AIMovementSystemGroup.TimeScaledRateManager.CheapStepDeltaTime;
systemState.Dependency = new JobAlignAgentWithMovementDirection {
dt = dt,
}.Schedule(entityQueryRotation, systemState.Dependency);
RunMovementOverrideBeforeMovement(ref systemState, dt);
// Move all agents which do not have a GravityState component
systemState.Dependency = new JobMoveAgent {
dt = dt,
}.ScheduleParallel(entityQueryMove, systemState.Dependency);
ScheduleApplyGravity(ref systemState, draw, dt);
var gizmosDependency = systemState.Dependency;
UpdateTypeHandles(ref systemState);
systemState.Dependency = ScheduleRepairPaths(ref systemState, systemState.Dependency);
// Draw gizmos only in the editor, and at most once per frame.
// The movement calculations may run multiple times per frame when using high time-scales,
// but rendering gizmos more than once would just lead to clutter.
if (Application.isEditor && AIMovementSystemGroup.TimeScaledRateManager.IsLastSubstep) {
gizmosDependency = ScheduleDrawGizmos(draw, systemState.Dependency);
}
// Render gizmos as soon as all relevant jobs are done
draw.DisposeAfter(gizmosDependency);
systemState.Dependency = ScheduleSyncEntitiesToTransforms(ref systemState, systemState.Dependency);
systemState.Dependency = JobHandle.CombineDependencies(systemState.Dependency, gizmosDependency);
}
void ScheduleApplyGravity (ref SystemState systemState, CommandBuilder draw, float dt) {
Profiler.BeginSample("Gravity");
// Note: We cannot use CalculateEntityCountWithoutFiltering here, because the GravityState component can be disabled
var count = entityQueryWithGravity.CalculateEntityCount();
var raycastCommands = CollectionHelper.CreateNativeArray<RaycastCommand>(count, systemState.WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory);
var raycastHits = CollectionHelper.CreateNativeArray<RaycastHit>(count, systemState.WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory);
// Prepare raycasts for all entities that have a GravityState component
systemState.Dependency = new JobPrepareAgentRaycasts {
raycastQueryParameters = new QueryParameters(-1, false, QueryTriggerInteraction.Ignore, false),
raycastCommands = raycastCommands,
draw = draw,
dt = dt,
gravity = Physics.gravity.y,
}.ScheduleParallel(entityQueryWithGravity, systemState.Dependency);
var raycastJob = RaycastCommand.ScheduleBatch(raycastCommands, raycastHits, 32, 1, systemState.Dependency);
// Apply gravity and move all agents that have a GravityState component
systemState.Dependency = new JobApplyGravity {
raycastHits = raycastHits,
raycastCommands = raycastCommands,
draw = draw,
dt = dt,
}.ScheduleParallel(entityQueryWithGravity, JobHandle.CombineDependencies(systemState.Dependency, raycastJob));
Profiler.EndSample();
}
void RunMovementOverrideBeforeMovement (ref SystemState systemState, float dt) {
if (!entityQueryMovementOverride.IsEmptyIgnoreFilter) {
MarkerMovementOverride.Begin();
// The movement overrides always run on the main thread.
// This adds a sync point, but only if people actually add a movement override (which is rare).
systemState.CompleteDependency();
new JobManagedMovementOverrideBeforeMovement {
dt = dt,
// TODO: Add unit test to make sure it fires/not fires when it should
}.Run(entityQueryMovementOverride);
MarkerMovementOverride.End();
}
}
void UpdateTypeHandles (ref SystemState systemState) {
MovementStateTypeHandleRO.Update(ref systemState);
ResolvedMovementHandleRO.Update(ref systemState);
}
JobHandle ScheduleRepairPaths (ref SystemState systemState, JobHandle dependency) {
Profiler.BeginSample("RepairPaths");
// This job accesses graph data, but this is safe because the AIMovementSystemGroup
// holds a read lock on the graph data while its subsystems are running.
dependency = jobRepairPathScheduler.ScheduleParallel(ref systemState, entityQueryPrepareMovement, dependency);
Profiler.EndSample();
return dependency;
}
JobHandle ScheduleDrawGizmos (CommandBuilder commandBuilder, JobHandle dependency) {
// Note: The ScheduleRepairPaths job runs right before this, so those handles are still valid
return new JobDrawFollowerGizmos {
draw = commandBuilder,
entityManagerHandle = jobRepairPathScheduler.entityManagerHandle,
LocalTransformTypeHandleRO = jobRepairPathScheduler.LocalTransformTypeHandleRO,
AgentCylinderShapeHandleRO = jobRepairPathScheduler.AgentCylinderShapeTypeHandleRO,
MovementSettingsHandleRO = jobRepairPathScheduler.MovementSettingsTypeHandleRO,
AgentMovementPlaneHandleRO = jobRepairPathScheduler.AgentMovementPlaneTypeHandleRO,
ManagedStateHandleRW = jobRepairPathScheduler.ManagedStateTypeHandleRW,
MovementStateHandleRO = MovementStateTypeHandleRO,
ResolvedMovementHandleRO = ResolvedMovementHandleRO,
}.ScheduleParallel(entityQueryGizmos, dependency);
}
JobHandle ScheduleSyncEntitiesToTransforms (ref SystemState systemState, JobHandle dependency) {
Profiler.BeginSample("SyncEntitiesToTransforms");
int numComponents = BatchedEvents.GetComponents<FollowerEntity>(BatchedEvents.Event.None, out var transforms, out var components);
if (numComponents == 0) {
Profiler.EndSample();
return dependency;
}
var entities = CollectionHelper.CreateNativeArray<Entity>(numComponents, systemState.WorldUpdateAllocator);
for (int i = 0; i < numComponents; i++) entities[i] = components[i].entity;
dependency = new JobSyncEntitiesToTransforms {
entities = entities,
syncPositionWithTransform = SystemAPI.GetComponentLookup<SyncPositionWithTransform>(true),
syncRotationWithTransform = SystemAPI.GetComponentLookup<SyncRotationWithTransform>(true),
orientationYAxisForward = SystemAPI.GetComponentLookup<OrientationYAxisForward>(true),
entityPositions = SystemAPI.GetComponentLookup<LocalTransform>(true),
movementState = SystemAPI.GetComponentLookup<MovementState>(true),
}.Schedule(transforms, dependency);
Profiler.EndSample();
return dependency;
}
}
}
#endif
|