using SepCore.Debugger; using Unity.Burst; using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; namespace SepCore.Simulation { internal static class CollisionQuerySystem { // 读写集:R EnemyTransform / EnemyMoveConfig / EnemyIdentity / CollisionQueryData(瞬时) // W CollisionCandidateData(瞬时) / 敌人碰撞分桶 public static void BuildEnemyCollisionBuckets(EnemyStore enemies, NativeParallelMultiHashMap buckets, float cellSize) { buckets.Clear(); int enemyCount = enemies.Count; if (enemyCount <= 0) { return; } BuildCollisionBucketsJob job = new BuildCollisionBucketsJob { Transforms = enemies.Transform.Data.AsArray(), Buckets = buckets.AsParallelWriter(), CellSize = cellSize }; using (CustomProfilerMarker.TickEnemies_Complete.Auto()) { job.Schedule(enemyCount, 64).Complete(); } } public static bool ScheduleCandidateQuery(NativeList queries, NativeList candidates, NativeParallelMultiHashMap enemyBuckets, EnemyStore enemies, bool hasEnemyTargets, bool hasPlayerTarget, int playerTargetEntityId, float3 playerPosition, float cellSize, out JobHandle handle) { handle = default; if (!queries.IsCreated || !candidates.IsCreated) { return false; } candidates.Clear(); int queryCount = queries.Length; if (queryCount == 0) { return false; } QueryCollisionCandidatesJob job = new QueryCollisionCandidatesJob { Queries = queries.AsArray(), EnemyBuckets = enemyBuckets, EnemyTransforms = enemies.Transform.Data.AsArray(), EnemyConfigs = enemies.MoveConfig.Data.AsArray(), EnemyIdentities = enemies.Identity.Data.AsArray(), Candidates = candidates.AsParallelWriter(), HasEnemyTargets = hasEnemyTargets, HasPlayerTarget = hasPlayerTarget, PlayerTargetEntityId = playerTargetEntityId, PlayerPosition = playerPosition, CellSize = cellSize }; handle = job.Schedule(queryCount, 64); return true; } } [BurstCompile] internal struct BuildCollisionBucketsJob : IJobParallelFor { [ReadOnly] public NativeArray Transforms; public NativeParallelMultiHashMap.ParallelWriter Buckets; public float CellSize; public void Execute(int index) { float3 position = Transforms[index].Position; int cellX = SimulationCell.ToCell(position.x, CellSize); int cellZ = SimulationCell.ToCell(position.z, CellSize); Buckets.Add(SimulationCell.Key(cellX, cellZ), index); } } [BurstCompile] internal struct QueryCollisionCandidatesJob : IJobParallelFor { [ReadOnly] public NativeArray Queries; [ReadOnly] public NativeParallelMultiHashMap EnemyBuckets; [ReadOnly] public NativeArray EnemyTransforms; [ReadOnly] public NativeArray EnemyConfigs; [ReadOnly] public NativeArray EnemyIdentities; public NativeList.ParallelWriter Candidates; public bool HasEnemyTargets; public bool HasPlayerTarget; public int PlayerTargetEntityId; public float3 PlayerPosition; public float CellSize; public void Execute(int index) { CollisionQueryData query = Queries[index]; int centerCellX = SimulationCell.ToCell(query.Position.x, CellSize); int centerCellZ = SimulationCell.ToCell(query.Position.z, CellSize); int queryRange = math.max(1, (int)math.ceil(query.Radius / CellSize)); int selectedCount = 0; bool reachedLimit = false; if (HasPlayerTarget && query.SourceEntityId != PlayerTargetEntityId && query.SourceOwnerEntityId != PlayerTargetEntityId) { float3 playerPosition = PlayerPosition; playerPosition.y = query.Position.y; float3 playerDelta = playerPosition - query.Position; float playerSqrDistance = math.lengthsq(playerDelta); float playerRadiusSqr = query.Radius * query.Radius; if (playerSqrDistance <= playerRadiusSqr) { Candidates.AddNoResize(new CollisionCandidateData { QueryId = query.QueryId, SourceType = query.SourceType, SourceEntityId = query.SourceEntityId, SourceOwnerEntityId = query.SourceOwnerEntityId, TargetEntityId = PlayerTargetEntityId, SqrDistance = playerSqrDistance }); selectedCount++; if (selectedCount >= query.MaxTargets) { reachedLimit = true; } } } if (!HasEnemyTargets || reachedLimit) { return; } for (int dx = -queryRange; dx <= queryRange && !reachedLimit; dx++) { for (int dz = -queryRange; dz <= queryRange && !reachedLimit; dz++) { long key = SimulationCell.Key(centerCellX + dx, centerCellZ + dz); if (!EnemyBuckets.TryGetFirstValue(key, out int enemyIndex, out NativeParallelMultiHashMapIterator iterator)) { continue; } do { if (enemyIndex < 0 || enemyIndex >= EnemyTransforms.Length) { continue; } EnemyIdentitySim identity = EnemyIdentities[enemyIndex]; if (identity.EntityId == query.SourceOwnerEntityId) { continue; } EnemyTransformSim enemyTransform = EnemyTransforms[enemyIndex]; float deltaY = query.SourceType == SimulationCollision.SourceTypeArea ? 0f : enemyTransform.Position.y - query.Position.y; float3 delta = new float3( enemyTransform.Position.x - query.Position.x, deltaY, enemyTransform.Position.z - query.Position.z); float sqrDistance = math.lengthsq(delta); float targetRadius = query.SourceType == SimulationCollision.SourceTypeArea ? math.max(0f, EnemyConfigs[enemyIndex].BodyRadius) : 0f; float effectiveRadius = query.Radius + targetRadius; if (sqrDistance > effectiveRadius * effectiveRadius) { continue; } Candidates.AddNoResize(new CollisionCandidateData { QueryId = query.QueryId, SourceType = query.SourceType, SourceEntityId = query.SourceEntityId, SourceOwnerEntityId = query.SourceOwnerEntityId, TargetEntityId = identity.EntityId, SqrDistance = sqrDistance }); selectedCount++; if (selectedCount >= query.MaxTargets) { reachedLimit = true; break; } } while (EnemyBuckets.TryGetNextValue(out enemyIndex, ref iterator)); } } } } }