ProjectDDD/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMoveSystem.cs
2025-07-08 19:46:31 +09:00

239 lines
10 KiB
C#

#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 entityQueryGizmos;
EntityQuery entityQueryMovementOverride;
JobRepairPath.Scheduler jobRepairPathScheduler;
ComponentTypeHandle<MovementState> MovementStateTypeHandleRO;
ComponentTypeHandle<ResolvedMovement> ResolvedMovementHandleRO;
public void OnCreate (ref SystemState state) {
jobRepairPathScheduler = new JobRepairPath.Scheduler(ref state);
MovementStateTypeHandleRO = state.GetComponentTypeHandle<MovementState>(true);
ResolvedMovementHandleRO = state.GetComponentTypeHandle<ResolvedMovement>(true);
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<PhysicsSceneRef>(),
// When in 2D mode, gravity is always disabled
ComponentType.Exclude<OrientationYAxisForward>(),
ComponentType.Exclude<AgentOffMeshLinkMovementDisabled>(),
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(systemState.Dependency);
RunMovementOverrideBeforeMovement(ref systemState, dt);
// Move all agents which do not have a GravityState component
systemState.Dependency = new JobMoveAgent {
dt = dt,
}.ScheduleParallel(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);
systemState.Dependency = new JobClearTemporaryData().Schedule(systemState.Dependency);
}
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