简单重构拆分 SimulationWorld
This commit is contained in:
+16
-85
@@ -1,8 +1,5 @@
|
||||
using SepCore.Debugger;
|
||||
using SepCore.Entity;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -24,7 +21,7 @@ namespace SepCore.Simulation
|
||||
return;
|
||||
}
|
||||
|
||||
int projectileCount = _projectileJobOutputs.Length;
|
||||
int projectileCount = _projectiles.Count;
|
||||
int areaQueryCount = _areaCollisionRequests.Count;
|
||||
if (projectileCount == 0 && areaQueryCount == 0)
|
||||
{
|
||||
@@ -42,13 +39,15 @@ namespace SepCore.Simulation
|
||||
{
|
||||
for (int i = 0; i < projectileCount; i++)
|
||||
{
|
||||
ProjectileJobOutputData projectile = _projectileJobOutputs[i];
|
||||
if (!projectile.Active || projectile.State != ProjectileStateActive)
|
||||
if (!_projectiles.Life[i].Active)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AddProjectileCollisionQuery(queryId, in projectile, queryRadius, maxCandidatesPerQuery);
|
||||
ProjectileTransformSim projectileTransform = _projectiles.Transform[i];
|
||||
ProjectileIdentitySim projectileIdentity = _projectiles.Identity[i];
|
||||
AddProjectileCollisionQuery(queryId, in projectileTransform, in projectileIdentity, queryRadius,
|
||||
maxCandidatesPerQuery);
|
||||
queryId++;
|
||||
projectileQueryCount++;
|
||||
}
|
||||
@@ -80,14 +79,14 @@ namespace SepCore.Simulation
|
||||
_lastProjectileCollisionCandidateCount = 0;
|
||||
_lastAreaCollisionCandidateCount = 0;
|
||||
_lastCollisionCellSize = 0f;
|
||||
_lastCollisionHasEnemyTargets = _enemyJobOutputs.Length > 0;
|
||||
_lastCollisionHasEnemyTargets = _enemies.Count > 0;
|
||||
return;
|
||||
}
|
||||
|
||||
float autoCellSize = maxQueryRadius * 2f;
|
||||
float configuredCellSize = _projectileCollisionCellSize > 0f ? _projectileCollisionCellSize : autoCellSize;
|
||||
float cellSize = Mathf.Max(0.1f, configuredCellSize);
|
||||
bool hasEnemyTargets = _enemyJobOutputs.Length > 0;
|
||||
bool hasEnemyTargets = _enemies.Count > 0;
|
||||
_lastCollisionCellSize = cellSize;
|
||||
_lastCollisionHasEnemyTargets = hasEnemyTargets;
|
||||
|
||||
@@ -95,13 +94,17 @@ namespace SepCore.Simulation
|
||||
{
|
||||
if (hasEnemyTargets)
|
||||
{
|
||||
BuildEnemyCollisionBuckets(cellSize);
|
||||
CollisionQuerySystem.BuildEnemyCollisionBuckets(_enemies, _enemyCollisionBuckets, cellSize);
|
||||
}
|
||||
}
|
||||
|
||||
using (CustomProfilerMarker.Collision_QueryCandidates.Auto())
|
||||
{
|
||||
_collisionCandidateQueryScheduled = ScheduleCollisionCandidateQueryJob(cellSize, hasEnemyTargets,
|
||||
bool hasPlayerTarget = TryGetPlayerCollisionTarget(out int playerTargetEntityId,
|
||||
out float3 playerPosition);
|
||||
_collisionCandidateQueryScheduled = CollisionQuerySystem.ScheduleCandidateQuery(
|
||||
_collisionQueryInputs, _collisionCandidates, _enemyCollisionBuckets, _enemies, hasEnemyTargets,
|
||||
hasPlayerTarget, playerTargetEntityId, playerPosition, cellSize,
|
||||
out _collisionCandidateQueryHandle);
|
||||
}
|
||||
}
|
||||
@@ -120,78 +123,6 @@ namespace SepCore.Simulation
|
||||
_lastCollisionCandidateCount = projectileCandidateCount + areaCandidateCount;
|
||||
}
|
||||
|
||||
private void BuildEnemyCollisionBuckets(float cellSize)
|
||||
{
|
||||
_enemyCollisionBuckets.Clear();
|
||||
int enemyCount = _enemyJobOutputs.Length;
|
||||
if (enemyCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BuildCollisionBucketsBurstJob job = new BuildCollisionBucketsBurstJob
|
||||
{
|
||||
EnemyOutputs = _enemyJobOutputs.AsArray(),
|
||||
Buckets = _enemyCollisionBuckets.AsParallelWriter(),
|
||||
CellSize = cellSize
|
||||
};
|
||||
|
||||
using (CustomProfilerMarker.TickEnemies_Complete.Auto())
|
||||
{
|
||||
JobHandle handle = job.Schedule(enemyCount, 64);
|
||||
handle.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct BuildCollisionBucketsBurstJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<EnemyJobOutputData> EnemyOutputs;
|
||||
public NativeParallelMultiHashMap<long, int>.ParallelWriter Buckets;
|
||||
public float CellSize;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
EnemyJobOutputData enemy = EnemyOutputs[index];
|
||||
int cellX = (int)math.floor(enemy.Position.x / CellSize);
|
||||
int cellZ = (int)math.floor(enemy.Position.z / CellSize);
|
||||
Buckets.Add(SeparationCellKey(cellX, cellZ), index);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ScheduleCollisionCandidateQueryJob(float cellSize, bool hasEnemyTargets, out JobHandle handle)
|
||||
{
|
||||
handle = default;
|
||||
if (!_collisionQueryInputs.IsCreated || !_collisionCandidates.IsCreated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_collisionCandidates.Clear();
|
||||
int queryCount = _collisionQueryInputs.Length;
|
||||
if (queryCount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasPlayerTarget = TryGetPlayerCollisionTarget(out int playerTargetEntityId, out float3 playerPosition);
|
||||
QueryCollisionCandidatesBurstJob job = new QueryCollisionCandidatesBurstJob
|
||||
{
|
||||
Queries = _collisionQueryInputs.AsArray(),
|
||||
EnemyBuckets = _enemyCollisionBuckets,
|
||||
EnemyOutputs = _enemyJobOutputs.AsArray(),
|
||||
Candidates = _collisionCandidates.AsParallelWriter(),
|
||||
HasEnemyTargets = hasEnemyTargets,
|
||||
HasPlayerTarget = hasPlayerTarget,
|
||||
PlayerTargetEntityId = playerTargetEntityId,
|
||||
PlayerPosition = playerPosition,
|
||||
CellSize = cellSize
|
||||
};
|
||||
|
||||
handle = job.Schedule(queryCount, 64);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CountCollisionCandidatesBySourceType(out int projectileCandidateCount, out int areaCandidateCount)
|
||||
{
|
||||
projectileCandidateCount = 0;
|
||||
@@ -205,11 +136,11 @@ namespace SepCore.Simulation
|
||||
for (int i = 0; i < _collisionCandidates.Length; i++)
|
||||
{
|
||||
CollisionCandidateData candidate = _collisionCandidates[i];
|
||||
if (candidate.SourceType == CollisionSourceTypeProjectile)
|
||||
if (candidate.SourceType == SimulationCollision.SourceTypeProjectile)
|
||||
{
|
||||
projectileCandidateCount++;
|
||||
}
|
||||
else if (candidate.SourceType == CollisionSourceTypeArea)
|
||||
else if (candidate.SourceType == SimulationCollision.SourceTypeArea)
|
||||
{
|
||||
areaCandidateCount++;
|
||||
}
|
||||
|
||||
+3
-3
@@ -11,14 +11,14 @@ namespace SepCore.Simulation
|
||||
float radius, int maxTargets = 16)
|
||||
{
|
||||
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius,
|
||||
maxTargets, CollisionShapeCircle, Vector3.forward, 180f, 0f, 0f);
|
||||
maxTargets, SimulationCollision.ShapeCircle, Vector3.forward, 180f, 0f, 0f);
|
||||
}
|
||||
|
||||
public bool TryRequestSectorCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center,
|
||||
float radius, in Vector3 direction, float halfAngleDeg, int maxTargets = 16)
|
||||
{
|
||||
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius,
|
||||
maxTargets, CollisionShapeSector, direction, halfAngleDeg, 0f, 0f);
|
||||
maxTargets, SimulationCollision.ShapeSector, direction, halfAngleDeg, 0f, 0f);
|
||||
}
|
||||
|
||||
public bool TryRequestRectangleCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center,
|
||||
@@ -28,7 +28,7 @@ namespace SepCore.Simulation
|
||||
float safeHalfLength = Mathf.Max(0.01f, halfLength);
|
||||
float boundingRadius = Mathf.Sqrt(safeHalfWidth * safeHalfWidth + safeHalfLength * safeHalfLength);
|
||||
return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, boundingRadius,
|
||||
maxTargets, CollisionShapeRectangle, direction, 0f, safeHalfWidth, safeHalfLength);
|
||||
maxTargets, SimulationCollision.ShapeRectangle, direction, 0f, safeHalfWidth, safeHalfLength);
|
||||
}
|
||||
|
||||
private bool TryRequestAreaCollisionInternal(int sourceEntityId, int sourceOwnerEntityId,
|
||||
|
||||
+22
-34
@@ -36,7 +36,7 @@ namespace SepCore.Simulation
|
||||
for (int i = 0; i < _collisionCandidates.Length; i++)
|
||||
{
|
||||
CollisionCandidateData candidate = _collisionCandidates[i];
|
||||
if (candidate.SourceType == CollisionSourceTypeProjectile)
|
||||
if (candidate.SourceType == SimulationCollision.SourceTypeProjectile)
|
||||
{
|
||||
int projectileEntityId = candidate.SourceEntityId;
|
||||
if (_projectileResolvedEntityIds.Contains(projectileEntityId))
|
||||
@@ -44,7 +44,7 @@ namespace SepCore.Simulation
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryGetActiveProjectileSimData(projectileEntityId, out _, out ProjectileSimData projectile))
|
||||
if (!TryGetActiveProjectileIndex(projectileEntityId, out int projectileIndex))
|
||||
{
|
||||
_projectileResolvedEntityIds.Add(projectileEntityId);
|
||||
continue;
|
||||
@@ -53,14 +53,15 @@ namespace SepCore.Simulation
|
||||
bool shouldExpireProjectile = true;
|
||||
bool shouldDispatchPresentation = false;
|
||||
int damage = 0;
|
||||
Vector3 hitPosition = projectile.Position;
|
||||
Vector3 projectilePosition = ToVector3(_projectiles.Transform[projectileIndex].Position);
|
||||
Vector3 hitPosition = projectilePosition;
|
||||
if (TryGetAliveTargetableEntity(candidate.TargetEntityId, out TargetableObject target))
|
||||
{
|
||||
EntityBase sourceEntity = TryGetEntityById(candidate.SourceEntityId);
|
||||
EntityBase ownerEntity = TryGetEntityById(candidate.SourceOwnerEntityId);
|
||||
shouldExpireProjectile = ResolveProjectileHitAgainstTarget(target, sourceEntity,
|
||||
ownerEntity,
|
||||
in projectile,
|
||||
in projectilePosition,
|
||||
out damage, out hitPosition, out shouldDispatchPresentation);
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ namespace SepCore.Simulation
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidate.SourceType == CollisionSourceTypeArea)
|
||||
if (candidate.SourceType == SimulationCollision.SourceTypeArea)
|
||||
{
|
||||
long dedupKey = (((long)candidate.QueryId) << 32) ^ (uint)candidate.TargetEntityId;
|
||||
if (!_areaCollisionHitDedupKeys.Add(dedupKey))
|
||||
@@ -112,11 +113,11 @@ namespace SepCore.Simulation
|
||||
|
||||
private bool ResolveProjectileHitAgainstTarget(TargetableObject target, EntityBase sourceEntity,
|
||||
EntityBase ownerEntity,
|
||||
in ProjectileSimData projectile, out int damage, out Vector3 hitPosition,
|
||||
in Vector3 projectilePosition, out int damage, out Vector3 hitPosition,
|
||||
out bool shouldDispatchPresentation)
|
||||
{
|
||||
damage = 0;
|
||||
hitPosition = projectile.Position;
|
||||
hitPosition = projectilePosition;
|
||||
shouldDispatchPresentation = false;
|
||||
|
||||
if (target == null || !target.Available || target.IsDead)
|
||||
@@ -167,7 +168,7 @@ namespace SepCore.Simulation
|
||||
{
|
||||
AreaCollisionHitEventData hitEvent = _areaCollisionHitEvents[i];
|
||||
if (!TryGetCollisionQueryByQueryId(hitEvent.QueryId, out CollisionQueryData query) ||
|
||||
query.SourceType != CollisionSourceTypeArea)
|
||||
query.SourceType != SimulationCollision.SourceTypeArea)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -238,9 +239,9 @@ namespace SepCore.Simulation
|
||||
return 0f;
|
||||
}
|
||||
|
||||
if (target is EnemyBase && TryGetEnemyData(target.Id, out EnemySimData enemyData))
|
||||
if (target is EnemyBase && _enemies.TryGetSimulationIndex(target.Id, out int enemyIndex))
|
||||
{
|
||||
return Mathf.Max(0f, enemyData.EnemyBodyRadius);
|
||||
return Mathf.Max(0f, _enemies.MoveConfig[enemyIndex].BodyRadius);
|
||||
}
|
||||
|
||||
MovementComponent movementComponent = target.GetComponent<MovementComponent>();
|
||||
@@ -267,7 +268,7 @@ namespace SepCore.Simulation
|
||||
return false;
|
||||
}
|
||||
|
||||
if (query.ShapeType == CollisionShapeRectangle)
|
||||
if (query.ShapeType == SimulationCollision.ShapeRectangle)
|
||||
{
|
||||
Vector3 forwardRect = new Vector3(query.Direction.x, query.Direction.y, query.Direction.z);
|
||||
forwardRect.y = 0f;
|
||||
@@ -288,7 +289,7 @@ namespace SepCore.Simulation
|
||||
return Mathf.Abs(forwardDistance) <= halfLength && Mathf.Abs(lateralDistance) <= halfWidth;
|
||||
}
|
||||
|
||||
if (query.ShapeType != CollisionShapeSector)
|
||||
if (query.ShapeType != SimulationCollision.ShapeSector)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -314,47 +315,34 @@ namespace SepCore.Simulation
|
||||
return angle <= halfAngle;
|
||||
}
|
||||
|
||||
private bool TryGetActiveProjectileSimData(int projectileEntityId, out int simulationIndex,
|
||||
out ProjectileSimData projectile)
|
||||
private bool TryGetActiveProjectileIndex(int projectileEntityId, out int simulationIndex)
|
||||
{
|
||||
simulationIndex = -1;
|
||||
projectile = default;
|
||||
|
||||
if (!ProjectileBinding.TryGetSimulationIndex(projectileEntityId, out int foundIndex) || foundIndex < 0 ||
|
||||
foundIndex >= _projectiles.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ProjectileSimData data = _projectiles[foundIndex];
|
||||
if (!data.Active || data.State != ProjectileStateActive)
|
||||
if (!_projectiles.TryGetSimulationIndex(projectileEntityId, out int foundIndex) ||
|
||||
!_projectiles.Life[foundIndex].Active)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
simulationIndex = foundIndex;
|
||||
projectile = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool MarkProjectileAsExpired(int projectileEntityId)
|
||||
{
|
||||
if (!ProjectileBinding.TryGetSimulationIndex(projectileEntityId, out int simulationIndex) ||
|
||||
simulationIndex < 0 || simulationIndex >= _projectiles.Count)
|
||||
if (!_projectiles.TryGetSimulationIndex(projectileEntityId, out int simulationIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ProjectileSimData projectile = _projectiles[simulationIndex];
|
||||
projectile.Active = false;
|
||||
projectile.State = ProjectileStateExpired;
|
||||
projectile.RemainingLifetime = 0f;
|
||||
if (projectile.LifeTime > 0f && projectile.Age < projectile.LifeTime)
|
||||
ProjectileLifeSim life = _projectiles.Life[simulationIndex];
|
||||
life.Active = false;
|
||||
if (life.LifeTime > 0f && life.Age < life.LifeTime)
|
||||
{
|
||||
projectile.Age = projectile.LifeTime;
|
||||
life.Age = life.LifeTime;
|
||||
}
|
||||
|
||||
_projectiles[simulationIndex] = projectile;
|
||||
_projectiles.Life[simulationIndex] = life;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+180
-134
@@ -1,5 +1,5 @@
|
||||
using SepCore.Debugger;
|
||||
using Unity.Collections;
|
||||
using SepCore.Debugger;
|
||||
using SepCore.Entity;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
@@ -8,7 +8,36 @@ namespace SepCore.Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
// Orchestrates per-tick simulation pipeline and enemy movement/separation jobs.
|
||||
// Orchestrates the per-tick simulation pipeline over the component systems.
|
||||
private float _playerAbsorbRange = -1f;
|
||||
private float _playerAbsorbSpeed = -1f;
|
||||
private float _playerCollectDistance = -1f;
|
||||
|
||||
public void SyncPlayerAbsorbSettings(float absorbRange, float absorbSpeed, float collectDistance)
|
||||
{
|
||||
_playerAbsorbRange = absorbRange;
|
||||
_playerAbsorbSpeed = absorbSpeed;
|
||||
_playerCollectDistance = collectDistance;
|
||||
}
|
||||
|
||||
private float ResolveAbsorbRange()
|
||||
{
|
||||
if (_playerAbsorbRange >= 0f) return _playerAbsorbRange;
|
||||
return GameEntry.Luban != null ? GameEntry.Luban.Global.DefaultAbsorbRange / 1000f : 2f;
|
||||
}
|
||||
|
||||
private float ResolveAbsorbSpeed()
|
||||
{
|
||||
if (_playerAbsorbSpeed >= 0f) return _playerAbsorbSpeed;
|
||||
return GameEntry.Luban != null ? GameEntry.Luban.Global.AbsorbSpeed / 1000f : 8f;
|
||||
}
|
||||
|
||||
private float ResolveCollectDistance()
|
||||
{
|
||||
if (_playerCollectDistance >= 0f) return _playerCollectDistance;
|
||||
return GameEntry.Luban != null ? GameEntry.Luban.Global.CollectDistance / 1000f : 0.5f;
|
||||
}
|
||||
|
||||
[Header("敌人互斥参数")]
|
||||
[Tooltip("敌人互斥分桶使用的网格尺寸。小于等于 0 时,将根据敌人体积半径自动计算。")]
|
||||
[SerializeField]
|
||||
@@ -46,32 +75,31 @@ namespace SepCore.Simulation
|
||||
JobHandle enemySeparationHandle = default;
|
||||
bool hasEnemySeparationJob = false;
|
||||
bool hasEnemySeparationCandidates = false;
|
||||
int enemySeparationCount = 0;
|
||||
bool hasMovingEnemy = false;
|
||||
int enemyCount = 0;
|
||||
float enemySeparationMaxRadius = 0.45f;
|
||||
|
||||
// 2. BuildInput 阶段:
|
||||
// 把 _enemies/_projectiles 同步到 Native 输入,准备输出缓冲;
|
||||
// 统计是否需要敌人分离 Job;
|
||||
// 预估并准备碰撞查询/候选缓冲。
|
||||
// 扫描敌人组件统计是否需要分离系统以及碰撞查询容量。
|
||||
using (CustomProfilerMarker.TickEnemies_BuildInput.Auto())
|
||||
{
|
||||
SyncSimulationStateToJobInputs();
|
||||
int enemyCount = _enemyJobInputs.Length;
|
||||
int projectileCount = _projectileJobInputs.Length;
|
||||
PrepareEnemyJobOutputBuffer(enemyCount);
|
||||
PrepareProjectileJobOutputBuffer(projectileCount);
|
||||
enemyCount = _enemies.Count;
|
||||
|
||||
enemySeparationCount = enemyCount;
|
||||
for (int i = 0; i < enemyCount; i++)
|
||||
{
|
||||
EnemyJobInputData input = _enemyJobInputs[i];
|
||||
if (!input.AvoidEnemyOverlap)
|
||||
if (_enemies.MoveInput[i].Speed > 0f)
|
||||
{
|
||||
hasMovingEnemy = true;
|
||||
}
|
||||
|
||||
EnemyMoveConfigSim config = _enemies.MoveConfig[i];
|
||||
if (!config.AvoidOverlap)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
hasEnemySeparationCandidates = true;
|
||||
float radius = input.EnemyBodyRadius > 0f ? input.EnemyBodyRadius : 0.45f;
|
||||
float radius = config.BodyRadius > 0f ? config.BodyRadius : 0.45f;
|
||||
if (radius > enemySeparationMaxRadius)
|
||||
{
|
||||
enemySeparationMaxRadius = radius;
|
||||
@@ -90,34 +118,53 @@ namespace SepCore.Simulation
|
||||
int projectileExpectedCount = projectileQueryCount * Mathf.Max(1, _projectileMaxCandidatesPerQuery);
|
||||
int areaExpectedCount = EstimatePendingAreaCollisionCandidateCountFromRequests();
|
||||
int expectedCandidateCount = Mathf.Max(16, projectileExpectedCount + areaExpectedCount);
|
||||
int bucketCapacity = Mathf.Max(256, _enemies.Count * 2 + queryCount);
|
||||
int bucketCapacity = Mathf.Max(256, enemyCount * 2 + queryCount);
|
||||
PrepareCollisionQueryAndCandidateChannels(queryCount, expectedCandidateCount, bucketCapacity);
|
||||
|
||||
int pickupCount = _pickups.Count;
|
||||
if (pickupCount > 0)
|
||||
{
|
||||
EnsureCapacity(ref _collectedPickups, pickupCount);
|
||||
_collectedPickups.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. StateUpdate 阶段(调度两个移动 Job)
|
||||
// 3. StateUpdate 阶段:敌人/投射物/掉落物移动系统
|
||||
JobHandle pickupMovementHandle = default;
|
||||
using (CustomProfilerMarker.TickEnemies_StateUpdate.Auto())
|
||||
{
|
||||
enemyMovementHandle = ExecuteEnemyMovementJob(in context);
|
||||
projectileMovementHandle = ExecuteProjectileMovementJob(in context);
|
||||
float3 planarPlayerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
||||
float3 playerPosition = new float3(context.PlayerPosition.x, context.PlayerPosition.y,
|
||||
context.PlayerPosition.z);
|
||||
enemyMovementHandle = EnemyMovementSystem.Schedule(_enemies, context.DeltaTime, planarPlayerPosition,
|
||||
default);
|
||||
projectileMovementHandle = ProjectileMovementSystem.Schedule(_projectiles, context.DeltaTime,
|
||||
playerPosition, _projectileMaxDistanceFromPlayer, _projectileMaxVerticalOffsetFromPlayer, default);
|
||||
|
||||
if (_pickups.Count > 0)
|
||||
{
|
||||
float absorbRange = ResolveAbsorbRange();
|
||||
float absorbSpeed = ResolveAbsorbSpeed();
|
||||
float collectDistance = ResolveCollectDistance();
|
||||
pickupMovementHandle = PickupMovementSystem.Schedule(_pickups, _collectedPickups, context.DeltaTime,
|
||||
planarPlayerPosition, absorbRange, absorbSpeed, collectDistance, default);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Schedule 阶段(可选分离 Job)
|
||||
// 如果有需要分离的敌人,调度:
|
||||
// BuildEnemySeparationBucketsBurstJob(依赖 EnemyMovement)
|
||||
// EnemySeparationBurstJob(依赖分桶 Job)
|
||||
// 然后把“敌人链路 handle”和“投射物移动 handle”合并。
|
||||
// 4. Schedule 阶段:可选敌人分离系统
|
||||
JobHandle simulationHandle;
|
||||
using (CustomProfilerMarker.TickEnemies_Schedule.Auto())
|
||||
{
|
||||
hasEnemySeparationJob = TryScheduleEnemySeparationFromJobOutput(
|
||||
hasEnemySeparationJob = TryScheduleEnemySeparationJob(
|
||||
in context,
|
||||
enemyMovementHandle,
|
||||
hasEnemySeparationCandidates,
|
||||
enemySeparationCount,
|
||||
enemyCount,
|
||||
enemySeparationMaxRadius,
|
||||
out enemySeparationHandle);
|
||||
JobHandle enemyHandle = hasEnemySeparationJob ? enemySeparationHandle : enemyMovementHandle;
|
||||
simulationHandle = JobHandle.CombineDependencies(enemyHandle, projectileMovementHandle);
|
||||
JobHandle movementHandle = JobHandle.CombineDependencies(enemyHandle, projectileMovementHandle);
|
||||
simulationHandle = JobHandle.CombineDependencies(movementHandle, pickupMovementHandle);
|
||||
}
|
||||
|
||||
// 5. Complete 阶段:等待上述 Job 全部完成。
|
||||
@@ -126,14 +173,19 @@ namespace SepCore.Simulation
|
||||
simulationHandle.Complete();
|
||||
}
|
||||
|
||||
// 6. 主线程后处理阶段:
|
||||
// - 把分离结果覆盖回敌人输出(如果有分离 Job)
|
||||
// - 构建碰撞候选(投射物查询 + area 请求查询 + 网格筛选)
|
||||
// 6. 主线程提交阶段:
|
||||
// - 分离结果写回敌人 Transform 组件
|
||||
// - 有位移时标记目标选择索引失效
|
||||
using (CustomProfilerMarker.TickEnemies_MainThreadCommit.Auto())
|
||||
{
|
||||
if (hasEnemySeparationJob)
|
||||
{
|
||||
CommitEnemySeparationFromJobOutput(enemySeparationCount);
|
||||
EnemySeparationSystem.Commit(_enemies, _enemySeparationTransforms, enemyCount);
|
||||
}
|
||||
|
||||
if (hasMovingEnemy || hasEnemySeparationJob)
|
||||
{
|
||||
MarkEnemyTargetSpatialIndexDirty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,137 +196,131 @@ namespace SepCore.Simulation
|
||||
}
|
||||
|
||||
// 7. MainThreadCommit 阶段
|
||||
// - ApplyJobOutputsToSimulationState(写回 _enemies/_projectiles)
|
||||
// - ResolveCollisionCandidatesOnMainThread(命中结算、事件、范围碰撞)
|
||||
// - RecycleInactiveAndExpiredProjectiles(隐藏并移除失效投射物)
|
||||
using (CustomProfilerMarker.TickEnemies_WriteBack.Auto())
|
||||
{
|
||||
using (CustomProfilerMarker.TickEnemies_MainThreadCommit.Auto())
|
||||
{
|
||||
ApplyJobOutputsToSimulationState();
|
||||
ResolveCollisionCandidatesOnMainThread();
|
||||
RecycleInactiveAndExpiredProjectiles();
|
||||
ResolveCollectedPickupsOnMainThread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JobHandle ExecuteEnemyMovementJob(in SimulationTickContext context)
|
||||
private void ResolveCollectedPickupsOnMainThread()
|
||||
{
|
||||
int enemyCount = _enemyJobInputs.Length;
|
||||
if (enemyCount == 0)
|
||||
if (!_collectedPickups.IsCreated || _collectedPickups.Length == 0)
|
||||
{
|
||||
return default;
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.DeltaTime <= 0f)
|
||||
Player player = TryGetPlayer();
|
||||
for (int i = 0; i < _collectedPickups.Length; i++)
|
||||
{
|
||||
CopyEnemyInputsToOutputs();
|
||||
return default;
|
||||
}
|
||||
|
||||
float3 playerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
||||
NativeArray<EnemyJobInputData> inputArray = _enemyJobInputs.AsArray();
|
||||
NativeArray<EnemyJobOutputData> outputArray = _enemyJobOutputs.AsArray();
|
||||
|
||||
EnemyMovementBurstJob burstJob = new EnemyMovementBurstJob
|
||||
{
|
||||
Inputs = inputArray,
|
||||
Outputs = outputArray,
|
||||
DeltaTime = context.DeltaTime,
|
||||
PlayerPosition = playerPosition
|
||||
};
|
||||
return burstJob.Schedule(enemyCount, 64);
|
||||
}
|
||||
|
||||
private void CopyEnemyInputsToOutputs()
|
||||
{
|
||||
for (int i = 0; i < _enemyJobInputs.Length; i++)
|
||||
{
|
||||
EnemyJobInputData input = _enemyJobInputs[i];
|
||||
_enemyJobOutputs[i] = new EnemyJobOutputData
|
||||
int entityId = _collectedPickups[i];
|
||||
EntityBase entity = TryGetEntityById(entityId);
|
||||
if (entity == null || !entity.Available)
|
||||
{
|
||||
EntityId = input.EntityId,
|
||||
Position = input.Position,
|
||||
Forward = input.Forward,
|
||||
Rotation = input.Rotation,
|
||||
Speed = input.Speed,
|
||||
AttackRange = input.AttackRange,
|
||||
AvoidEnemyOverlap = input.AvoidEnemyOverlap,
|
||||
EnemyBodyRadius = input.EnemyBodyRadius,
|
||||
SeparationIterations = input.SeparationIterations,
|
||||
TargetType = input.TargetType,
|
||||
State = input.State
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity is CoinEntity coin)
|
||||
{
|
||||
if (player != null)
|
||||
{
|
||||
coin.TryCollect(player);
|
||||
}
|
||||
else
|
||||
{
|
||||
var entityComponent = GameEntry.Entity;
|
||||
if (entityComponent != null)
|
||||
{
|
||||
entityComponent.HideEntity(coin);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (entity is ExpEntity exp)
|
||||
{
|
||||
if (player != null)
|
||||
{
|
||||
exp.TryCollect(player);
|
||||
}
|
||||
else
|
||||
{
|
||||
var entityComponent = GameEntry.Entity;
|
||||
if (entityComponent != null)
|
||||
{
|
||||
entityComponent.HideEntity(exp);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var entityComponent = GameEntry.Entity;
|
||||
if (entityComponent != null)
|
||||
{
|
||||
entityComponent.HideEntity(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_collectedPickups.Clear();
|
||||
}
|
||||
|
||||
private bool TryScheduleEnemySeparationFromJobOutput(in SimulationTickContext context, JobHandle dependency,
|
||||
private Player TryGetPlayer()
|
||||
{
|
||||
if (_playerMovementTransform != null)
|
||||
{
|
||||
Player player = _playerMovementTransform.GetComponent<Player>();
|
||||
if (player != null && player.Available)
|
||||
{
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
var entityComponent = GameEntry.Entity;
|
||||
if (entityComponent != null && GameEntry.Luban != null)
|
||||
{
|
||||
var group = entityComponent.GetEntityGroup(GameEntry.Luban.Global.PlayerGroupName);
|
||||
if (group != null && group.EntityCount > 0)
|
||||
{
|
||||
var entities = group.GetAllEntities();
|
||||
for (int i = 0; i < entities.Length; i++)
|
||||
{
|
||||
if (entities[i] is UnityGameFramework.Runtime.Entity ugfEntity &&
|
||||
ugfEntity.Logic is Player player && player.Available)
|
||||
{
|
||||
return player;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryScheduleEnemySeparationJob(in SimulationTickContext context, JobHandle dependency,
|
||||
bool hasSeparationCandidates, int enemyCount, float maxRadius, out JobHandle separationHandle)
|
||||
{
|
||||
separationHandle = dependency;
|
||||
if (enemyCount <= 0 || !hasSeparationCandidates)
|
||||
{
|
||||
separationHandle = dependency;
|
||||
return false;
|
||||
}
|
||||
|
||||
float autoCellSize = maxRadius * 2f;
|
||||
float configuredCellSize = _enemySeparationCellSize > 0f ? _enemySeparationCellSize : autoCellSize;
|
||||
float cellSize = Mathf.Max(0.1f, configuredCellSize);
|
||||
EnemySeparationSettings settings = new EnemySeparationSettings(
|
||||
Mathf.Max(0.1f, configuredCellSize),
|
||||
maxRadius,
|
||||
Mathf.Clamp(_enemySeparationPushDamping, 0f, 2f),
|
||||
Mathf.Max(0.1f, _enemySeparationMaxStepScale),
|
||||
_enemySeparationUseTangentialInAttackRange,
|
||||
Mathf.Clamp01(_enemySeparationPushSmoothing));
|
||||
float3 playerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
||||
float pushDamping = Mathf.Clamp(_enemySeparationPushDamping, 0f, 2f);
|
||||
float maxStepScale = Mathf.Max(0.1f, _enemySeparationMaxStepScale);
|
||||
bool useTangentialInAttackRange = _enemySeparationUseTangentialInAttackRange;
|
||||
float pushSmoothing = Mathf.Clamp01(_enemySeparationPushSmoothing);
|
||||
|
||||
NativeArray<EnemyJobOutputData> inputArray = _enemyJobOutputs.AsArray();
|
||||
NativeArray<EnemyJobOutputData> separatedOutputArray = _enemyJobSeparationOutputs.AsArray();
|
||||
NativeArray<float2> previousPushes = _enemySeparationPreviousPushes.AsArray();
|
||||
NativeArray<float2> currentPushes = _enemySeparationCurrentPushes.AsArray();
|
||||
|
||||
|
||||
BuildEnemySeparationBucketsBurstJob buildJob = new BuildEnemySeparationBucketsBurstJob
|
||||
{
|
||||
Inputs = inputArray,
|
||||
Buckets = _enemySeparationBuckets.AsParallelWriter(),
|
||||
CellSize = cellSize
|
||||
};
|
||||
JobHandle buildHandle = buildJob.Schedule(enemyCount, 64, dependency);
|
||||
EnemySeparationBurstJob separationJob = new EnemySeparationBurstJob
|
||||
{
|
||||
Inputs = inputArray,
|
||||
Buckets = _enemySeparationBuckets,
|
||||
PreviousPushes = previousPushes,
|
||||
Outputs = separatedOutputArray,
|
||||
CurrentPushes = currentPushes,
|
||||
CellSize = cellSize,
|
||||
MaxRadius = maxRadius,
|
||||
PlayerPosition = playerPosition,
|
||||
PushDamping = pushDamping,
|
||||
MaxStepScale = maxStepScale,
|
||||
UseTangentialInAttackRange = useTangentialInAttackRange,
|
||||
PushSmoothing = pushSmoothing
|
||||
};
|
||||
separationHandle = separationJob.Schedule(enemyCount, 64, buildHandle);
|
||||
separationHandle = EnemySeparationSystem.Schedule(_enemies, _enemySeparationBuckets,
|
||||
_enemySeparationTransforms, in settings, playerPosition, dependency);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CommitEnemySeparationFromJobOutput(int enemyCount)
|
||||
{
|
||||
if (enemyCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CommitEnemySeparationTemporalBuffers(enemyCount);
|
||||
for (int i = 0; i < enemyCount; i++)
|
||||
{
|
||||
_enemyJobOutputs[i] = _enemyJobSeparationOutputs[i];
|
||||
}
|
||||
}
|
||||
|
||||
private static long SeparationCellKey(int x, int z)
|
||||
{
|
||||
return ((long)x << 32) ^ (uint)z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-61
@@ -1,7 +1,4 @@
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.Simulation
|
||||
{
|
||||
@@ -16,45 +13,6 @@ namespace SepCore.Simulation
|
||||
[SerializeField]
|
||||
private float _projectileMaxVerticalOffsetFromPlayer = 30f;
|
||||
|
||||
#region Projectile Movement Job
|
||||
|
||||
private JobHandle ExecuteProjectileMovementJob(in SimulationTickContext context)
|
||||
{
|
||||
int projectileCount = _projectileJobInputs.Length;
|
||||
if (projectileCount == 0)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (context.DeltaTime <= 0f)
|
||||
{
|
||||
CopyProjectileInputsToOutputs();
|
||||
return default;
|
||||
}
|
||||
|
||||
float maxDistance = Mathf.Max(0f, _projectileMaxDistanceFromPlayer);
|
||||
float maxSqrDistanceFromPlayer = maxDistance > 0f ? maxDistance * maxDistance : -1f;
|
||||
float maxVerticalOffsetFromPlayer = Mathf.Max(0f, _projectileMaxVerticalOffsetFromPlayer);
|
||||
float3 playerPosition =
|
||||
new float3(context.PlayerPosition.x, context.PlayerPosition.y, context.PlayerPosition.z);
|
||||
NativeArray<ProjectileJobInputData> inputArray = _projectileJobInputs.AsArray();
|
||||
NativeArray<ProjectileJobOutputData> outputArray = _projectileJobOutputs.AsArray();
|
||||
|
||||
|
||||
ProjectileMovementBurstJob burstJob = new ProjectileMovementBurstJob
|
||||
{
|
||||
Inputs = inputArray,
|
||||
Outputs = outputArray,
|
||||
DeltaTime = context.DeltaTime,
|
||||
PlayerPosition = playerPosition,
|
||||
MaxSqrDistanceFromPlayer = maxSqrDistanceFromPlayer,
|
||||
MaxVerticalOffsetFromPlayer = maxVerticalOffsetFromPlayer
|
||||
};
|
||||
return burstJob.Schedule(projectileCount, 64);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Projectile Cleanup
|
||||
|
||||
private void RecycleInactiveAndExpiredProjectiles()
|
||||
@@ -62,13 +20,13 @@ namespace SepCore.Simulation
|
||||
_projectileRecycleEntityIds.Clear();
|
||||
for (int i = 0; i < _projectiles.Count; i++)
|
||||
{
|
||||
ProjectileSimData projectile = _projectiles[i];
|
||||
if (!ShouldRecycleProjectileSimData(projectile))
|
||||
ProjectileLifeSim life = _projectiles.Life[i];
|
||||
if (life.Active && (life.LifeTime <= 0f || life.Age < life.LifeTime))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_projectileRecycleEntityIds.Add(projectile.EntityId);
|
||||
_projectileRecycleEntityIds.Add(_projectiles.Identity[i].EntityId);
|
||||
}
|
||||
|
||||
if (_projectileRecycleEntityIds.Count == 0)
|
||||
@@ -91,21 +49,6 @@ namespace SepCore.Simulation
|
||||
_projectileRecycleEntityIds.Clear();
|
||||
}
|
||||
|
||||
private static bool ShouldRecycleProjectileSimData(in ProjectileSimData projectile)
|
||||
{
|
||||
if (!projectile.Active)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (projectile.State == ProjectileStateExpired)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return projectile.LifeTime > 0f && projectile.Age >= projectile.LifeTime;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user