简单重构拆分 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,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++;
}
@@ -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,
@@ -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;
}
@@ -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;
}
}
}
}
@@ -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
}
}