Checkpoint 9
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct AreaCollisionHitEventData
|
||||
{
|
||||
public int QueryId;
|
||||
public int SourceEntityId;
|
||||
public int SourceOwnerEntityId;
|
||||
public int TargetEntityId;
|
||||
public float SqrDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5140d989cba042e6bcb217893491273d
|
||||
timeCreated: 1771901785
|
||||
@@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct AreaCollisionRequestData
|
||||
{
|
||||
public int SourceEntityId;
|
||||
public int SourceOwnerEntityId;
|
||||
public bool SourceWasActiveAtQueryTime;
|
||||
public Vector3 Center;
|
||||
public float Radius;
|
||||
public int MaxTargets;
|
||||
public int ShapeType;
|
||||
public Vector3 Direction;
|
||||
public float HalfAngleDeg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e32f4e0724314c70882f5ed043a4dca4
|
||||
timeCreated: 1771901749
|
||||
@@ -0,0 +1,48 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
[BurstCompile]
|
||||
private struct BuildEnemySeparationBucketsBurstJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<EnemyJobOutputData> Inputs;
|
||||
public NativeParallelMultiHashMap<long, int>.ParallelWriter Buckets;
|
||||
public float CellSize;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
BuildEnemySeparationBucket(
|
||||
index,
|
||||
Inputs,
|
||||
Buckets,
|
||||
CellSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BuildEnemySeparationBucket(
|
||||
int index,
|
||||
NativeArray<EnemyJobOutputData> inputs,
|
||||
NativeParallelMultiHashMap<long, int>.ParallelWriter buckets,
|
||||
float cellSize
|
||||
)
|
||||
{
|
||||
EnemyJobOutputData output = inputs[index];
|
||||
if (!output.AvoidEnemyOverlap)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float3 position = output.Position;
|
||||
position.y = 0f;
|
||||
int cellX = (int)math.floor(position.x / cellSize);
|
||||
int cellZ = (int)math.floor(position.z / cellSize);
|
||||
buckets.Add(SeparationCellKey(cellX, cellZ), index);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e833e91677bb49a4a458c9c8b90099e9
|
||||
timeCreated: 1771901500
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct CollisionCandidateData
|
||||
{
|
||||
public int QueryId;
|
||||
public int SourceType;
|
||||
public int SourceEntityId;
|
||||
public int SourceOwnerEntityId;
|
||||
public int TargetEntityId;
|
||||
public float SqrDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 453c14964a4b4aed89c0abd7b25cd26c
|
||||
timeCreated: 1771901722
|
||||
@@ -0,0 +1,22 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct CollisionQueryData
|
||||
{
|
||||
public int QueryId;
|
||||
public int SourceType;
|
||||
public int SourceEntityId;
|
||||
public int SourceOwnerEntityId;
|
||||
public bool SourceWasActiveAtQueryTime;
|
||||
public float3 Position;
|
||||
public float Radius;
|
||||
public int MaxTargets;
|
||||
public int ShapeType;
|
||||
public float3 Direction;
|
||||
public float HalfAngleDeg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58abe0d4fd7a49acb716fbd26e0fb19f
|
||||
timeCreated: 1771901691
|
||||
@@ -0,0 +1,22 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct EnemyJobInputData
|
||||
{
|
||||
public int EntityId;
|
||||
public float3 Position;
|
||||
public float3 Forward;
|
||||
public quaternion Rotation;
|
||||
public float Speed;
|
||||
public float AttackRange;
|
||||
public bool AvoidEnemyOverlap;
|
||||
public float EnemyBodyRadius;
|
||||
public int SeparationIterations;
|
||||
public int TargetType;
|
||||
public int State;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 171936b8a6b84a8fbacc7651ce473b98
|
||||
timeCreated: 1771901146
|
||||
@@ -0,0 +1,22 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct EnemyJobOutputData
|
||||
{
|
||||
public int EntityId;
|
||||
public float3 Position;
|
||||
public float3 Forward;
|
||||
public quaternion Rotation;
|
||||
public float Speed;
|
||||
public float AttackRange;
|
||||
public bool AvoidEnemyOverlap;
|
||||
public float EnemyBodyRadius;
|
||||
public int SeparationIterations;
|
||||
public int TargetType;
|
||||
public int State;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34ef6fbf123f45ca88d8e7ab8311d543
|
||||
timeCreated: 1771901204
|
||||
@@ -0,0 +1,93 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
[BurstCompile]
|
||||
private struct EnemyMovementBurstJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<EnemyJobInputData> Inputs;
|
||||
public NativeArray<EnemyJobOutputData> Outputs;
|
||||
public float DeltaTime;
|
||||
public float3 PlayerPosition;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
ExecuteEnemyMovement(
|
||||
index,
|
||||
Inputs,
|
||||
Outputs,
|
||||
DeltaTime,
|
||||
PlayerPosition
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExecuteEnemyMovement(
|
||||
int index,
|
||||
NativeArray<EnemyJobInputData> inputs,
|
||||
NativeArray<EnemyJobOutputData> outputs,
|
||||
float deltaTime,
|
||||
float3 playerPosition
|
||||
)
|
||||
{
|
||||
EnemyJobInputData input = inputs[index];
|
||||
float attackRange = input.AttackRange > 0f ? input.AttackRange : DefaultAttackRange;
|
||||
float attackRangeSqr = attackRange * attackRange;
|
||||
|
||||
float3 currentPosition = input.Position;
|
||||
float3 horizontalPosition = new float3(currentPosition.x, 0f, currentPosition.z);
|
||||
float3 toPlayer = playerPosition - horizontalPosition;
|
||||
float sqrDistance = math.lengthsq(toPlayer);
|
||||
bool isInAttackRange = sqrDistance <= attackRangeSqr;
|
||||
bool canChase = !isInAttackRange && input.Speed > 0f && sqrDistance > float.Epsilon;
|
||||
|
||||
float3 forward = input.Forward;
|
||||
float3 desiredPosition = currentPosition;
|
||||
quaternion rotation = input.Rotation;
|
||||
|
||||
if (canChase)
|
||||
{
|
||||
forward = math.normalizesafe(toPlayer, forward);
|
||||
desiredPosition = currentPosition + forward * input.Speed * deltaTime;
|
||||
if (math.lengthsq(forward) > float.Epsilon)
|
||||
{
|
||||
rotation = quaternion.LookRotationSafe(forward, math.up());
|
||||
}
|
||||
}
|
||||
|
||||
int nextState;
|
||||
if (isInAttackRange)
|
||||
{
|
||||
nextState = EnemyStateInAttackRange;
|
||||
}
|
||||
else if (canChase)
|
||||
{
|
||||
nextState = EnemyStateChasing;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextState = EnemyStateIdle;
|
||||
}
|
||||
|
||||
outputs[index] = new EnemyJobOutputData
|
||||
{
|
||||
EntityId = input.EntityId,
|
||||
Position = desiredPosition,
|
||||
Forward = forward,
|
||||
Rotation = rotation,
|
||||
Speed = input.Speed,
|
||||
AttackRange = attackRange,
|
||||
AvoidEnemyOverlap = input.AvoidEnemyOverlap,
|
||||
EnemyBodyRadius = input.EnemyBodyRadius,
|
||||
SeparationIterations = input.SeparationIterations,
|
||||
TargetType = input.TargetType,
|
||||
State = nextState
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abd5519779ed41d4947280eec326ffb1
|
||||
timeCreated: 1771901472
|
||||
@@ -0,0 +1,245 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
[BurstCompile]
|
||||
private struct EnemySeparationBurstJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<EnemyJobOutputData> Inputs;
|
||||
[ReadOnly] public NativeParallelMultiHashMap<long, int> Buckets;
|
||||
[ReadOnly] public NativeArray<float2> PreviousPushes;
|
||||
public NativeArray<EnemyJobOutputData> Outputs;
|
||||
public NativeArray<float2> CurrentPushes;
|
||||
public float CellSize;
|
||||
public float MaxRadius;
|
||||
public float3 PlayerPosition;
|
||||
public float PushDamping;
|
||||
public float MaxStepScale;
|
||||
public bool UseTangentialInAttackRange;
|
||||
public float PushSmoothing;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
ExecuteEnemySeparation(
|
||||
index,
|
||||
Inputs,
|
||||
Buckets,
|
||||
Outputs,
|
||||
CellSize,
|
||||
MaxRadius,
|
||||
PlayerPosition,
|
||||
PushDamping,
|
||||
MaxStepScale,
|
||||
UseTangentialInAttackRange,
|
||||
PreviousPushes,
|
||||
CurrentPushes,
|
||||
PushSmoothing
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExecuteEnemySeparation(
|
||||
int index,
|
||||
NativeArray<EnemyJobOutputData> inputs,
|
||||
NativeParallelMultiHashMap<long, int> buckets,
|
||||
NativeArray<EnemyJobOutputData> outputs,
|
||||
float cellSize,
|
||||
float maxRadius,
|
||||
float3 playerPosition,
|
||||
float pushDamping,
|
||||
float maxStepScale,
|
||||
bool useTangentialInAttackRange,
|
||||
NativeArray<float2> previousPushes,
|
||||
NativeArray<float2> currentPushes,
|
||||
float pushSmoothing
|
||||
)
|
||||
{
|
||||
currentPushes[index] = float2.zero;
|
||||
EnemyJobOutputData self = inputs[index];
|
||||
if (!self.AvoidEnemyOverlap)
|
||||
{
|
||||
outputs[index] = self;
|
||||
return;
|
||||
}
|
||||
|
||||
float3 candidate = self.Position;
|
||||
candidate.y = 0f;
|
||||
float3 original = candidate;
|
||||
float3 fallback =
|
||||
math.normalizesafe(new float3(self.Forward.x, 0f, self.Forward.z), new float3(1f, 0f, 0f));
|
||||
float selfRadius = self.EnemyBodyRadius > 0f ? self.EnemyBodyRadius : 0.45f;
|
||||
int iterations = self.SeparationIterations > 0 ? self.SeparationIterations : 1;
|
||||
int queryRange = math.max(1, (int)math.ceil((selfRadius + maxRadius) / cellSize));
|
||||
|
||||
for (int iter = 0; iter < iterations; iter++)
|
||||
{
|
||||
int cellX = (int)math.floor(candidate.x / cellSize);
|
||||
int cellZ = (int)math.floor(candidate.z / cellSize);
|
||||
float3 pushAccumulation = float3.zero;
|
||||
|
||||
for (int dx = -queryRange; dx <= queryRange; dx++)
|
||||
{
|
||||
for (int dz = -queryRange; dz <= queryRange; dz++)
|
||||
{
|
||||
long key = SeparationCellKey(cellX + dx, cellZ + dz);
|
||||
if (!buckets.TryGetFirstValue(key, out int otherIndex,
|
||||
out NativeParallelMultiHashMapIterator<long> iterator))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (otherIndex == index)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EnemyJobOutputData other = inputs[otherIndex];
|
||||
if (!other.AvoidEnemyOverlap)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float otherRadius = other.EnemyBodyRadius > 0f ? other.EnemyBodyRadius : 0.45f;
|
||||
float minDistance = selfRadius + otherRadius;
|
||||
float minDistanceSqr = minDistance * minDistance;
|
||||
|
||||
float3 otherPosition = other.Position;
|
||||
otherPosition.y = 0f;
|
||||
float3 toSelf = candidate - otherPosition;
|
||||
float sqrDistance = math.lengthsq(toSelf);
|
||||
|
||||
if (sqrDistance <= float.Epsilon)
|
||||
{
|
||||
float3 zeroDistanceAxis = GetZeroDistanceSeparationAxis(index, otherIndex);
|
||||
float directionSign = index < otherIndex ? 1f : -1f;
|
||||
pushAccumulation += zeroDistanceAxis * (selfRadius * 0.25f * directionSign);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sqrDistance >= minDistanceSqr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float distance = math.sqrt(sqrDistance);
|
||||
float penetration = minDistance - distance;
|
||||
pushAccumulation += (toSelf / distance) * penetration;
|
||||
} while (buckets.TryGetNextValue(out otherIndex, ref iterator));
|
||||
}
|
||||
}
|
||||
|
||||
if (math.lengthsq(pushAccumulation) <= float.Epsilon)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float3 resolvedPush = pushAccumulation * pushDamping;
|
||||
|
||||
float maxStep = selfRadius * maxStepScale;
|
||||
float pushLength = math.length(resolvedPush);
|
||||
if (pushLength > maxStep && pushLength > float.Epsilon)
|
||||
{
|
||||
resolvedPush = resolvedPush / pushLength * maxStep;
|
||||
}
|
||||
|
||||
candidate += resolvedPush;
|
||||
}
|
||||
|
||||
float3 framePush = candidate - original;
|
||||
float2 previousPush2 = previousPushes[index];
|
||||
float3 previousPush = new float3(previousPush2.x, 0f, previousPush2.y);
|
||||
float3 smoothedPush = SmoothSeparationPush(framePush, previousPush, pushSmoothing);
|
||||
|
||||
if (useTangentialInAttackRange && self.State == EnemyStateInAttackRange)
|
||||
{
|
||||
smoothedPush = ProjectToTangential(smoothedPush, playerPosition, original);
|
||||
}
|
||||
|
||||
float maxTotalStep = selfRadius * maxStepScale * iterations;
|
||||
float smoothedLength = math.length(smoothedPush);
|
||||
if (smoothedLength > maxTotalStep && smoothedLength > float.Epsilon)
|
||||
{
|
||||
smoothedPush = smoothedPush / smoothedLength * maxTotalStep;
|
||||
}
|
||||
|
||||
float3 finalPosition = original + smoothedPush;
|
||||
currentPushes[index] = new float2(smoothedPush.x, smoothedPush.z);
|
||||
self.Position = new float3(finalPosition.x, self.Position.y, finalPosition.z);
|
||||
if (math.lengthsq(smoothedPush) > float.Epsilon)
|
||||
{
|
||||
self.Forward = new float3(fallback.x, self.Forward.y, fallback.z);
|
||||
}
|
||||
|
||||
outputs[index] = self;
|
||||
}
|
||||
|
||||
private static float3 SmoothSeparationPush(float3 framePush, float3 previousPush, float pushSmoothing)
|
||||
{
|
||||
float frameLengthSqr = math.lengthsq(framePush);
|
||||
float previousLengthSqr = math.lengthsq(previousPush);
|
||||
|
||||
if (frameLengthSqr <= float.Epsilon)
|
||||
{
|
||||
return float3.zero;
|
||||
}
|
||||
|
||||
if (previousLengthSqr <= float.Epsilon || pushSmoothing <= 0f)
|
||||
{
|
||||
return framePush;
|
||||
}
|
||||
|
||||
float frameLength = math.sqrt(frameLengthSqr);
|
||||
float previousLength = math.sqrt(previousLengthSqr);
|
||||
float3 frameDirection = framePush / frameLength;
|
||||
float3 previousDirection = previousPush / previousLength;
|
||||
float directionAlignment = math.dot(frameDirection, previousDirection);
|
||||
|
||||
if (directionAlignment >= 0.35f)
|
||||
{
|
||||
return framePush;
|
||||
}
|
||||
|
||||
float directionalFactor = math.saturate((0.35f - directionAlignment) / 1.35f);
|
||||
float smoothingStrength = pushSmoothing * directionalFactor;
|
||||
return math.lerp(framePush, previousPush, smoothingStrength);
|
||||
}
|
||||
|
||||
private static float3 ProjectToTangential(float3 push, float3 playerPosition, float3 currentPosition)
|
||||
{
|
||||
if (math.lengthsq(push) <= float.Epsilon)
|
||||
{
|
||||
return push;
|
||||
}
|
||||
|
||||
float3 toPlayer = playerPosition - currentPosition;
|
||||
float toPlayerSqr = math.lengthsq(toPlayer);
|
||||
if (toPlayerSqr <= float.Epsilon)
|
||||
{
|
||||
return push;
|
||||
}
|
||||
|
||||
float3 radialDirection = toPlayer / math.sqrt(toPlayerSqr);
|
||||
float radialOffset = math.dot(push, radialDirection);
|
||||
return push - radialDirection * radialOffset;
|
||||
}
|
||||
|
||||
private static float3 GetZeroDistanceSeparationAxis(int index, int otherIndex)
|
||||
{
|
||||
int lowIndex = math.min(index, otherIndex);
|
||||
int highIndex = math.max(index, otherIndex);
|
||||
uint pairHash = (uint)(lowIndex * 73856093) ^ (uint)(highIndex * 19349663);
|
||||
|
||||
float axisX = (pairHash & 1023u) / 511.5f - 1f;
|
||||
float axisZ = ((pairHash >> 10) & 1023u) / 511.5f - 1f;
|
||||
float3 axis = new float3(axisX, 0f, axisZ);
|
||||
return math.normalizesafe(axis, new float3(1f, 0f, 0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94a77477c3a8410cb58b5028dd5d2c63
|
||||
timeCreated: 1771901543
|
||||
@@ -0,0 +1,22 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct ProjectileJobInputData
|
||||
{
|
||||
public int EntityId;
|
||||
public int OwnerEntityId;
|
||||
public float3 Position;
|
||||
public float3 Forward;
|
||||
public float3 Velocity;
|
||||
public float Speed;
|
||||
public float LifeTime;
|
||||
public float Age;
|
||||
public bool Active;
|
||||
public float RemainingLifetime;
|
||||
public int State;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 334ca3869d7940058256ba5419573637
|
||||
timeCreated: 1771901235
|
||||
@@ -0,0 +1,22 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
private struct ProjectileJobOutputData
|
||||
{
|
||||
public int EntityId;
|
||||
public int OwnerEntityId;
|
||||
public float3 Position;
|
||||
public float3 Forward;
|
||||
public float3 Velocity;
|
||||
public float Speed;
|
||||
public float LifeTime;
|
||||
public float Age;
|
||||
public bool Active;
|
||||
public float RemainingLifetime;
|
||||
public int State;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5c4a144914548379d9991d8ef061da7
|
||||
timeCreated: 1771901266
|
||||
@@ -0,0 +1,118 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
[BurstCompile]
|
||||
private struct ProjectileMovementBurstJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<ProjectileJobInputData> Inputs;
|
||||
public NativeArray<ProjectileJobOutputData> Outputs;
|
||||
public float DeltaTime;
|
||||
public float3 PlayerPosition;
|
||||
public float MaxSqrDistanceFromPlayer;
|
||||
public float MaxVerticalOffsetFromPlayer;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
ExecuteProjectileMovement(
|
||||
index,
|
||||
Inputs,
|
||||
Outputs,
|
||||
DeltaTime,
|
||||
PlayerPosition,
|
||||
MaxSqrDistanceFromPlayer,
|
||||
MaxVerticalOffsetFromPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExecuteProjectileMovement(
|
||||
int index,
|
||||
NativeArray<ProjectileJobInputData> inputs,
|
||||
NativeArray<ProjectileJobOutputData> outputs,
|
||||
float deltaTime,
|
||||
float3 playerPosition,
|
||||
float maxSqrDistanceFromPlayer,
|
||||
float maxVerticalOffsetFromPlayer)
|
||||
{
|
||||
ProjectileJobInputData input = inputs[index];
|
||||
ProjectileJobOutputData output = new ProjectileJobOutputData
|
||||
{
|
||||
EntityId = input.EntityId,
|
||||
OwnerEntityId = input.OwnerEntityId,
|
||||
Position = input.Position,
|
||||
Forward = input.Forward,
|
||||
Velocity = input.Velocity,
|
||||
Speed = input.Speed,
|
||||
LifeTime = input.LifeTime,
|
||||
Age = input.Age,
|
||||
Active = input.Active,
|
||||
RemainingLifetime = input.RemainingLifetime,
|
||||
State = input.State
|
||||
};
|
||||
|
||||
if (!input.Active)
|
||||
{
|
||||
output.State = ProjectileStateExpired;
|
||||
outputs[index] = output;
|
||||
return;
|
||||
}
|
||||
|
||||
float3 position = input.Position;
|
||||
float3 forward = input.Forward;
|
||||
float3 velocity = input.Velocity;
|
||||
if (math.lengthsq(velocity) <= float.Epsilon && input.Speed > 0f)
|
||||
{
|
||||
float3 moveDirection = math.normalizesafe(forward, new float3(0f, 0f, 1f));
|
||||
velocity = moveDirection * input.Speed;
|
||||
}
|
||||
|
||||
float3 nextPosition = position + velocity * deltaTime;
|
||||
float nextAge = math.max(0f, input.Age + deltaTime);
|
||||
float nextRemainingLifetime = input.RemainingLifetime;
|
||||
bool shouldExpire = false;
|
||||
|
||||
if (input.LifeTime > 0f)
|
||||
{
|
||||
nextRemainingLifetime = math.max(0f, input.LifeTime - nextAge);
|
||||
shouldExpire = nextAge >= input.LifeTime;
|
||||
}
|
||||
else if (input.RemainingLifetime > 0f)
|
||||
{
|
||||
nextRemainingLifetime = math.max(0f, input.RemainingLifetime - deltaTime);
|
||||
shouldExpire = nextRemainingLifetime <= float.Epsilon;
|
||||
}
|
||||
|
||||
if (!shouldExpire && maxSqrDistanceFromPlayer > 0f)
|
||||
{
|
||||
float3 horizontalDelta =
|
||||
new float3(nextPosition.x - playerPosition.x, 0f, nextPosition.z - playerPosition.z);
|
||||
shouldExpire = math.lengthsq(horizontalDelta) > maxSqrDistanceFromPlayer;
|
||||
}
|
||||
|
||||
if (!shouldExpire && maxVerticalOffsetFromPlayer > 0f)
|
||||
{
|
||||
shouldExpire = math.abs(nextPosition.y - playerPosition.y) > maxVerticalOffsetFromPlayer;
|
||||
}
|
||||
|
||||
output.Position = nextPosition;
|
||||
output.Velocity = velocity;
|
||||
output.Age = nextAge;
|
||||
output.RemainingLifetime = nextRemainingLifetime;
|
||||
output.Active = !shouldExpire;
|
||||
output.State = shouldExpire ? ProjectileStateExpired : ProjectileStateActive;
|
||||
|
||||
if (math.lengthsq(velocity) > float.Epsilon)
|
||||
{
|
||||
float3 moveForward = math.normalizesafe(velocity, forward);
|
||||
output.Forward = moveForward;
|
||||
}
|
||||
|
||||
outputs[index] = output;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49001e849c1b4afdaa37d851b9b76866
|
||||
timeCreated: 1771901884
|
||||
@@ -0,0 +1,122 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
[BurstCompile]
|
||||
private struct QueryCollisionCandidatesBurstJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<CollisionQueryData> Queries;
|
||||
[ReadOnly] public NativeParallelMultiHashMap<long, int> EnemyBuckets;
|
||||
[ReadOnly] public NativeArray<EnemyJobOutputData> EnemyOutputs;
|
||||
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];
|
||||
float radiusSqr = query.Radius * query.Radius;
|
||||
int centerCellX = (int)math.floor(query.Position.x / CellSize);
|
||||
int centerCellZ = (int)math.floor(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);
|
||||
if (playerSqrDistance <= radiusSqr)
|
||||
{
|
||||
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 = SeparationCellKey(centerCellX + dx, centerCellZ + dz);
|
||||
if (!EnemyBuckets.TryGetFirstValue(key, out int enemyIndex,
|
||||
out NativeParallelMultiHashMapIterator<long> iterator))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (enemyIndex < 0 || enemyIndex >= EnemyOutputs.Length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EnemyJobOutputData enemy = EnemyOutputs[enemyIndex];
|
||||
if (enemy.EntityId == query.SourceOwnerEntityId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float3 delta = new float3(
|
||||
enemy.Position.x - query.Position.x,
|
||||
enemy.Position.y - query.Position.y,
|
||||
enemy.Position.z - query.Position.z);
|
||||
float sqrDistance = math.lengthsq(delta);
|
||||
if (sqrDistance > radiusSqr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Candidates.AddNoResize(new CollisionCandidateData
|
||||
{
|
||||
QueryId = query.QueryId,
|
||||
SourceType = query.SourceType,
|
||||
SourceEntityId = query.SourceEntityId,
|
||||
SourceOwnerEntityId = query.SourceOwnerEntityId,
|
||||
TargetEntityId = enemy.EntityId,
|
||||
SqrDistance = sqrDistance
|
||||
});
|
||||
|
||||
selectedCount++;
|
||||
|
||||
if (selectedCount >= query.MaxTargets)
|
||||
{
|
||||
reachedLimit = true;
|
||||
break;
|
||||
}
|
||||
} while (EnemyBuckets.TryGetNextValue(out enemyIndex, ref iterator));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81aa43bb35f547c48b867eafa17a3857
|
||||
timeCreated: 1771914779
|
||||
Reference in New Issue
Block a user