Files
vampire-like/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/CollisionQuerySystem.cs
T

216 lines
8.5 KiB
C#

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<long, int> 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<CollisionQueryData> queries,
NativeList<CollisionCandidateData> candidates, NativeParallelMultiHashMap<long, int> 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<EnemyTransformSim> Transforms;
public NativeParallelMultiHashMap<long, int>.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<CollisionQueryData> Queries;
[ReadOnly] public NativeParallelMultiHashMap<long, int> EnemyBuckets;
[ReadOnly] public NativeArray<EnemyTransformSim> EnemyTransforms;
[ReadOnly] public NativeArray<EnemyMoveConfigSim> EnemyConfigs;
[ReadOnly] public NativeArray<EnemyIdentitySim> EnemyIdentities;
public NativeList<CollisionCandidateData>.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;
if (query.SourceType == SimulationCollision.SourceTypeArea)
{
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<long> 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));
}
}
}
}
}