简单重构拆分 SimulationWorld

This commit is contained in:
2026-09-18 17:43:41 +08:00
parent 1e4d3ef8d7
commit 92e1ea0d55
109 changed files with 2605 additions and 1817 deletions
@@ -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;
}
}
}
}