diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs new file mode 100644 index 0000000..d7a3c90 --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs @@ -0,0 +1,241 @@ +using CustomDebugger; +using Entity; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace Simulation +{ + public sealed partial class SimulationWorld + { + #region Collision Broad Phase + + private void PrepareCollisionCandidatesForFrame() + { + _collisionCandidateQueryScheduled = false; + + if (!_collisionQueryInputs.IsCreated || !_collisionCandidates.IsCreated || + !_enemyCollisionBuckets.IsCreated) + { + ResetCollisionRuntimeStats(); + ClearAreaCollisionTransientBuffers(); + return; + } + + int projectileCount = _projectileJobOutputs.Length; + int areaQueryCount = _areaCollisionRequests.Count; + if (projectileCount == 0 && areaQueryCount == 0) + { + ResetCollisionRuntimeStats(); + return; + } + + float queryRadius = Mathf.Max(0.01f, _projectileCollisionQueryRadius); + int maxCandidatesPerQuery = Mathf.Max(1, _projectileMaxCandidatesPerQuery); + float maxQueryRadius = queryRadius; + int queryId = 0; + int projectileQueryCount = 0; + int builtAreaQueryCount = 0; + using (CustomProfilerMarker.Collision_BuildQueries.Auto()) + { + for (int i = 0; i < projectileCount; i++) + { + ProjectileJobOutputData projectile = _projectileJobOutputs[i]; + if (!projectile.Active || projectile.State != ProjectileStateActive) + { + continue; + } + + AddProjectileCollisionQuery(queryId, in projectile, queryRadius, maxCandidatesPerQuery); + queryId++; + projectileQueryCount++; + } + + for (int i = 0; i < areaQueryCount; i++) + { + AreaCollisionRequestData request = _areaCollisionRequests[i]; + AddAreaCollisionQuery(queryId, request.SourceEntityId, request.SourceOwnerEntityId, + request.SourceWasActiveAtQueryTime, in request.Center, request.Radius, request.MaxTargets, + request.ShapeType, in request.Direction, request.HalfAngleDeg); + queryId++; + builtAreaQueryCount++; + if (request.Radius > maxQueryRadius) + { + maxQueryRadius = request.Radius; + } + } + } + + _lastProjectileCollisionQueryCount = projectileQueryCount; + _lastAreaCollisionQueryCount = builtAreaQueryCount; + _lastCollisionQueryCount = projectileQueryCount + builtAreaQueryCount; + _lastResolvedAreaHitCount = 0; + + if (_collisionQueryInputs.Length == 0) + { + _lastCollisionCandidateCount = 0; + _lastProjectileCollisionCandidateCount = 0; + _lastAreaCollisionCandidateCount = 0; + _lastCollisionCellSize = 0f; + _lastCollisionHasEnemyTargets = _enemyJobOutputs.Length > 0; + return; + } + + float autoCellSize = maxQueryRadius * 2f; + float configuredCellSize = _projectileCollisionCellSize > 0f ? _projectileCollisionCellSize : autoCellSize; + float cellSize = Mathf.Max(0.1f, configuredCellSize); + bool hasEnemyTargets = _enemyJobOutputs.Length > 0; + _lastCollisionCellSize = cellSize; + _lastCollisionHasEnemyTargets = hasEnemyTargets; + + using (CustomProfilerMarker.Collision_BuildBuckets.Auto()) + { + if (hasEnemyTargets) + { + BuildEnemyCollisionBuckets(cellSize); + } + } + + using (CustomProfilerMarker.Collision_QueryCandidates.Auto()) + { + _collisionCandidateQueryScheduled = ScheduleCollisionCandidateQueryJob(cellSize, hasEnemyTargets, + out _collisionCandidateQueryHandle); + } + } + + private void CompleteCollisionCandidatesForFrame() + { + if (_collisionCandidateQueryScheduled) + { + _collisionCandidateQueryHandle.Complete(); + _collisionCandidateQueryScheduled = false; + } + + CountCollisionCandidatesBySourceType(out int projectileCandidateCount, out int areaCandidateCount); + _lastProjectileCollisionCandidateCount = projectileCandidateCount; + _lastAreaCollisionCandidateCount = areaCandidateCount; + _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 EnemyOutputs; + public NativeParallelMultiHashMap.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; + areaCandidateCount = 0; + + if (!_collisionCandidates.IsCreated) + { + return; + } + + for (int i = 0; i < _collisionCandidates.Length; i++) + { + CollisionCandidateData candidate = _collisionCandidates[i]; + if (candidate.SourceType == CollisionSourceTypeProjectile) + { + projectileCandidateCount++; + } + else if (candidate.SourceType == CollisionSourceTypeArea) + { + areaCandidateCount++; + } + } + } + + private static bool TryGetPlayerCollisionTarget(out int playerEntityId, out float3 playerPosition) + { + playerEntityId = PlayerEntityId; + playerPosition = default; + + if (!TryGetAliveTargetableEntity(playerEntityId, out TargetableObject playerTarget)) + { + return false; + } + + Transform playerTransform = playerTarget.CachedTransform; + if (playerTransform == null) + { + return false; + } + + Vector3 position = playerTransform.position; + playerPosition = new float3(position.x, position.y, position.z); + return true; + } + + #endregion + } +} diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs.meta b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs.meta new file mode 100644 index 0000000..e7d2eb5 --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ac6aca660ff944e69ea8f5e06f0609cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPipeline.cs b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPipeline.cs index 6fed3ca..e32c5e1 100644 --- a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPipeline.cs +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPipeline.cs @@ -1,20 +1,13 @@ -using CustomDebugger; -using CustomEvent; -using CustomUtility; -using Definition.DataStruct; -using Definition.Enum; -using Entity; -using Entity.Weapon; -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; +using Unity.Jobs; using UnityEngine; namespace Simulation { public sealed partial class SimulationWorld { + // Shared collision pipeline configuration and runtime state. + // Request buffering, broad-phase scheduling, resolve, and presentation + // dispatch live in dedicated partial files under Jobs/. private const int PlayerEntityId = -1; private JobHandle _collisionCandidateQueryHandle; private bool _collisionCandidateQueryScheduled; @@ -48,753 +41,5 @@ namespace Simulation [Tooltip("Default hit effect entity type id in presentation event. 0 means not specified.")] [SerializeField] private int _projectileHitPresentationEffectTypeId = 0; - - #region Area Query Request - - public bool TryRequestAreaCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center, - float radius, int maxTargets = 16) - { - return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius, - maxTargets, CollisionShapeCircle, Vector3.forward, 180f); - } - - 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); - } - - private bool TryRequestAreaCollisionInternal(int sourceEntityId, int sourceOwnerEntityId, - in Vector3 center, - float radius, int maxTargets, int shapeType, in Vector3 direction, float halfAngleDeg) - { - if (!_useSimulationMovement) - { - return false; - } - - if (sourceEntityId == 0 || radius <= 0f || maxTargets <= 0) - { - return false; - } - - int resolvedOwnerEntityId = sourceOwnerEntityId != 0 ? sourceOwnerEntityId : sourceEntityId; - bool sourceWasActiveAtQueryTime = WasCollisionSourceActiveAtQueryTime(sourceEntityId); - Vector3 normalizedDirection = direction; - normalizedDirection.y = 0f; - if (normalizedDirection.sqrMagnitude <= Mathf.Epsilon) - { - normalizedDirection = Vector3.forward; - } - else - { - normalizedDirection.Normalize(); - } - - _areaCollisionRequests.Add(new AreaCollisionRequestData - { - SourceEntityId = sourceEntityId, - SourceOwnerEntityId = resolvedOwnerEntityId, - SourceWasActiveAtQueryTime = sourceWasActiveAtQueryTime, - Center = center, - Radius = Mathf.Max(0.01f, radius), - MaxTargets = Mathf.Max(1, maxTargets), - ShapeType = shapeType, - Direction = normalizedDirection, - HalfAngleDeg = Mathf.Clamp(halfAngleDeg, 0f, 180f) - }); - - return true; - } - - private int GetPendingAreaCollisionRequestCount() - { - return _areaCollisionRequests.Count; - } - - private int EstimatePendingAreaCollisionCandidateCountFromRequests() - { - int expectedCount = 0; - for (int i = 0; i < _areaCollisionRequests.Count; i++) - { - expectedCount += Mathf.Max(1, _areaCollisionRequests[i].MaxTargets); - } - - return expectedCount; - } - - #endregion - - #region Collision Pipeline - - private void PrepareCollisionCandidatesForFrame() - { - _collisionCandidateQueryScheduled = false; - - if (!_collisionQueryInputs.IsCreated || !_collisionCandidates.IsCreated || - !_enemyCollisionBuckets.IsCreated) - { - ResetCollisionRuntimeStats(); - ClearAreaCollisionTransientBuffers(); - return; - } - - int projectileCount = _projectileJobOutputs.Length; - int areaQueryCount = _areaCollisionRequests.Count; - if (projectileCount == 0 && areaQueryCount == 0) - { - ResetCollisionRuntimeStats(); - return; - } - - float queryRadius = Mathf.Max(0.01f, _projectileCollisionQueryRadius); - int maxCandidatesPerQuery = Mathf.Max(1, _projectileMaxCandidatesPerQuery); - float maxQueryRadius = queryRadius; - int queryId = 0; - int projectileQueryCount = 0; - int builtAreaQueryCount = 0; - using (CustomProfilerMarker.Collision_BuildQueries.Auto()) - { - for (int i = 0; i < projectileCount; i++) - { - ProjectileJobOutputData projectile = _projectileJobOutputs[i]; - if (!projectile.Active || projectile.State != ProjectileStateActive) - { - continue; - } - - AddProjectileCollisionQuery(queryId, in projectile, queryRadius, maxCandidatesPerQuery); - queryId++; - projectileQueryCount++; - } - - for (int i = 0; i < areaQueryCount; i++) - { - AreaCollisionRequestData request = _areaCollisionRequests[i]; - AddAreaCollisionQuery(queryId, request.SourceEntityId, request.SourceOwnerEntityId, - request.SourceWasActiveAtQueryTime, in request.Center, request.Radius, request.MaxTargets, - request.ShapeType, in request.Direction, request.HalfAngleDeg); - queryId++; - builtAreaQueryCount++; - if (request.Radius > maxQueryRadius) - { - maxQueryRadius = request.Radius; - } - } - } - - _lastProjectileCollisionQueryCount = projectileQueryCount; - _lastAreaCollisionQueryCount = builtAreaQueryCount; - _lastCollisionQueryCount = projectileQueryCount + builtAreaQueryCount; - _lastResolvedAreaHitCount = 0; - - if (_collisionQueryInputs.Length == 0) - { - _lastCollisionCandidateCount = 0; - _lastProjectileCollisionCandidateCount = 0; - _lastAreaCollisionCandidateCount = 0; - _lastCollisionCellSize = 0f; - _lastCollisionHasEnemyTargets = _enemyJobOutputs.Length > 0; - return; - } - - float autoCellSize = maxQueryRadius * 2f; - float configuredCellSize = _projectileCollisionCellSize > 0f ? _projectileCollisionCellSize : autoCellSize; - float cellSize = Mathf.Max(0.1f, configuredCellSize); - bool hasEnemyTargets = _enemyJobOutputs.Length > 0; - _lastCollisionCellSize = cellSize; - _lastCollisionHasEnemyTargets = hasEnemyTargets; - - using (CustomProfilerMarker.Collision_BuildBuckets.Auto()) - { - if (hasEnemyTargets) - { - BuildEnemyCollisionBuckets(cellSize); - } - } - - using (CustomProfilerMarker.Collision_QueryCandidates.Auto()) - { - _collisionCandidateQueryScheduled = ScheduleCollisionCandidateQueryJob(cellSize, hasEnemyTargets, - out _collisionCandidateQueryHandle); - } - } - - private void CompleteCollisionCandidatesForFrame() - { - if (_collisionCandidateQueryScheduled) - { - _collisionCandidateQueryHandle.Complete(); - _collisionCandidateQueryScheduled = false; - } - - CountCollisionCandidatesBySourceType(out int projectileCandidateCount, out int areaCandidateCount); - _lastProjectileCollisionCandidateCount = projectileCandidateCount; - _lastAreaCollisionCandidateCount = areaCandidateCount; - _lastCollisionCandidateCount = projectileCandidateCount + areaCandidateCount; - } - - private void ResolveCollisionCandidatesOnMainThread() - { - if (!_collisionCandidates.IsCreated) - { - _lastResolvedAreaHitCount = 0; - ClearAreaCollisionTransientBuffers(); - return; - } - - _projectileResolvedEntityIds.Clear(); - _areaCollisionHitEvents.Clear(); - _areaCollisionHitDedupKeys.Clear(); - - if (_collisionCandidates.Length == 0) - { - _lastResolvedAreaHitCount = 0; - ClearAreaCollisionTransientBuffers(); - return; - } - - using (CustomProfilerMarker.Collision_ResolveProjectile.Auto()) - { - for (int i = 0; i < _collisionCandidates.Length; i++) - { - CollisionCandidateData candidate = _collisionCandidates[i]; - if (candidate.SourceType == CollisionSourceTypeProjectile) - { - int projectileEntityId = candidate.SourceEntityId; - if (_projectileResolvedEntityIds.Contains(projectileEntityId)) - { - continue; - } - - if (!TryGetActiveProjectileSimData(projectileEntityId, out _, out ProjectileSimData projectile)) - { - _projectileResolvedEntityIds.Add(projectileEntityId); - continue; - } - - bool shouldExpireProjectile = true; - bool shouldDispatchPresentation = false; - int damage = 0; - Vector3 hitPosition = projectile.Position; - if (TryGetAliveTargetableEntity(candidate.TargetEntityId, out TargetableObject target)) - { - EntityBase sourceEntity = TryGetEntityById(candidate.SourceEntityId); - EntityBase ownerEntity = TryGetEntityById(candidate.SourceOwnerEntityId); - shouldExpireProjectile = ResolveProjectileHitAgainstTarget(target, sourceEntity, - ownerEntity, - in projectile, - out damage, out hitPosition, out shouldDispatchPresentation); - } - - if (shouldDispatchPresentation) - { - DispatchProjectileHitPresentationEvent(projectileEntityId, candidate.SourceEntityId, - candidate.SourceOwnerEntityId, candidate.TargetEntityId, damage, in hitPosition); - } - - if (shouldExpireProjectile) - { - MarkProjectileAsExpired(projectileEntityId); - _projectileResolvedEntityIds.Add(projectileEntityId); - } - - continue; - } - - if (candidate.SourceType == CollisionSourceTypeArea) - { - long dedupKey = (((long)candidate.QueryId) << 32) ^ (uint)candidate.TargetEntityId; - if (!_areaCollisionHitDedupKeys.Add(dedupKey)) - { - continue; - } - - _areaCollisionHitEvents.Add(new AreaCollisionHitEventData - { - QueryId = candidate.QueryId, - SourceEntityId = candidate.SourceEntityId, - SourceOwnerEntityId = candidate.SourceOwnerEntityId, - TargetEntityId = candidate.TargetEntityId, - SqrDistance = candidate.SqrDistance - }); - } - } - } - - int resolvedAreaHitCount; - using (CustomProfilerMarker.Collision_ResolveArea.Auto()) - { - resolvedAreaHitCount = ResolveAreaCollisionHitsOnMainThread(); - } - - _lastResolvedAreaHitCount = resolvedAreaHitCount; - _projectileResolvedEntityIds.Clear(); - ClearAreaCollisionTransientBuffers(); - } - - #endregion - - #region Collision Resolve Helpers - - private bool ResolveProjectileHitAgainstTarget(TargetableObject target, EntityBase sourceEntity, - EntityBase ownerEntity, - in ProjectileSimData projectile, out int damage, out Vector3 hitPosition, - out bool shouldDispatchPresentation) - { - damage = 0; - hitPosition = projectile.Position; - shouldDispatchPresentation = false; - - if (target == null || !target.Available || target.IsDead) - { - return true; - } - - if (target.CachedTransform != null) - { - hitPosition = target.CachedTransform.position; - } - - if (!TryResolveImpactSource(sourceEntity, ownerEntity, out EntityBase attacker, - out ImpactData sourceImpact)) - { - shouldDispatchPresentation = true; - return true; - } - - ImpactData targetImpact = target.GetImpactData(); - if (AIUtility.GetRelation(targetImpact.Camp, sourceImpact.Camp) == RelationType.Friendly) - { - return false; - } - - damage = AIUtility.CalcDamageHP(sourceImpact.AttackBase, sourceImpact.AttackStat, - targetImpact.DefenseStat, - targetImpact.DodgeStat); - shouldDispatchPresentation = true; - if (damage <= 0) - { - return true; - } - - target.ApplyDamage(attacker ?? sourceEntity ?? ownerEntity, damage); - return true; - } - - private void DispatchProjectileHitPresentationEvent(int projectileEntityId, int sourceEntityId, - int sourceOwnerEntityId, int targetEntityId, int damage, in Vector3 hitPosition) - { - if (!_dispatchProjectileHitPresentationEvent) - { - return; - } - - var eventComponent = GameEntry.Event; - if (eventComponent == null) - { - return; - } - - eventComponent.Fire(this, ProjectileHitPresentationEventArgs.Create( - projectileEntityId, - sourceEntityId, - sourceOwnerEntityId, - targetEntityId, - damage, - hitPosition, - _dispatchProjectileHitMarkerEvent, - _dispatchProjectileHitEffectEvent, - _projectileHitPresentationEffectTypeId)); - } - - private static bool TryResolveImpactSource(EntityBase sourceEntity, EntityBase ownerEntity, - out EntityBase attacker, - out ImpactData impactData) - { - if (TryResolveImpactFromEntity(sourceEntity, out impactData)) - { - attacker = sourceEntity; - return true; - } - - if (TryResolveImpactFromEntity(ownerEntity, out impactData)) - { - attacker = ownerEntity; - return true; - } - - attacker = null; - impactData = default; - return false; - } - - private static bool TryResolveImpactFromEntity(EntityBase entity, out ImpactData impactData) - { - if (entity is WeaponBase weapon) - { - impactData = weapon.GetImpactData(); - return true; - } - - if (entity is EnemyProjectile enemyProjectile) - { - impactData = enemyProjectile.GetImpactData(); - return true; - } - - if (entity is TargetableObject targetableObject) - { - impactData = targetableObject.GetImpactData(); - return true; - } - - impactData = default; - return false; - } - - private static bool TryGetAliveTargetableEntity(int entityId, out TargetableObject target) - { - target = null; - - var enemyManager = GameEntry.EnemyManager; - if (enemyManager != null && enemyManager.TryGetEnemy(entityId, out EntityBase enemyEntity)) - { - if (enemyEntity is TargetableObject enemyTarget && enemyTarget.Available && !enemyTarget.IsDead) - { - target = enemyTarget; - return true; - } - } - - EntityBase entity = TryGetEntityById(entityId); - if (entity is TargetableObject targetable && targetable.Available && !targetable.IsDead) - { - target = targetable; - return true; - } - - return false; - } - - private static EntityBase TryGetEntityById(int entityId) - { - var entityComponent = GameEntry.Entity; - return entityComponent != null ? entityComponent.GetGameEntity(entityId) : null; - } - - private bool TryGetActiveProjectileSimData(int projectileEntityId, out int simulationIndex, - out ProjectileSimData projectile) - { - 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) - { - 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) - { - return false; - } - - ProjectileSimData projectile = _projectiles[simulationIndex]; - projectile.Active = false; - projectile.State = ProjectileStateExpired; - projectile.RemainingLifetime = 0f; - if (projectile.LifeTime > 0f && projectile.Age < projectile.LifeTime) - { - projectile.Age = projectile.LifeTime; - } - - _projectiles[simulationIndex] = projectile; - return true; - } - - private int ResolveAreaCollisionHitsOnMainThread() - { - if (_areaCollisionHitEvents.Count == 0) - { - return 0; - } - - int resolvedHitCount = 0; - for (int i = 0; i < _areaCollisionHitEvents.Count; i++) - { - AreaCollisionHitEventData hitEvent = _areaCollisionHitEvents[i]; - if (!TryGetCollisionQueryByQueryId(hitEvent.QueryId, out CollisionQueryData query) || - query.SourceType != CollisionSourceTypeArea) - { - continue; - } - - if (!query.SourceWasActiveAtQueryTime) - { - continue; - } - - EntityBase sourceEntity = TryGetEntityById(hitEvent.SourceEntityId); - if (sourceEntity == null || !sourceEntity.Available) - { - continue; - } - - if (!TryGetAliveTargetableEntity(hitEvent.TargetEntityId, out TargetableObject target)) - { - continue; - } - - if (!IsAreaTargetInsidePreciseShape(in query, target)) - { - continue; - } - - AIUtility.PerformCollision(target, sourceEntity, true); - resolvedHitCount++; - } - - return resolvedHitCount; - } - - private bool TryGetCollisionQueryByQueryId(int queryId, out CollisionQueryData query) - { - query = default; - if (!_collisionQueryInputs.IsCreated || queryId < 0 || queryId >= _collisionQueryInputs.Length) - { - return false; - } - - CollisionQueryData direct = _collisionQueryInputs[queryId]; - if (direct.QueryId == queryId) - { - query = direct; - return true; - } - - for (int i = 0; i < _collisionQueryInputs.Length; i++) - { - CollisionQueryData candidate = _collisionQueryInputs[i]; - if (candidate.QueryId != queryId) - { - continue; - } - - query = candidate; - return true; - } - - return false; - } - - private static bool IsAreaTargetInsidePreciseShape(in CollisionQueryData query, TargetableObject target) - { - if (target == null || target.CachedTransform == null) - { - return false; - } - - Vector3 center = new Vector3(query.Position.x, query.Position.y, query.Position.z); - Vector3 toTarget = target.CachedTransform.position - center; - toTarget.y = 0f; - - float radius = Mathf.Max(0.01f, query.Radius); - float radiusSqr = radius * radius; - float sqrDistance = toTarget.sqrMagnitude; - if (sqrDistance > radiusSqr) - { - return false; - } - - if (query.ShapeType != CollisionShapeSector) - { - return true; - } - - if (sqrDistance <= Mathf.Epsilon) - { - return true; - } - - Vector3 forward = new Vector3(query.Direction.x, query.Direction.y, query.Direction.z); - forward.y = 0f; - if (forward.sqrMagnitude <= Mathf.Epsilon) - { - forward = Vector3.forward; - } - else - { - forward.Normalize(); - } - - float halfAngle = Mathf.Clamp(query.HalfAngleDeg, 0f, 180f); - float angle = Vector3.Angle(forward, toTarget.normalized); - return angle <= halfAngle; - } - - private void ClearAreaCollisionTransientBuffers() - { - _areaCollisionRequests.Clear(); - _areaCollisionHitEvents.Clear(); - _areaCollisionHitDedupKeys.Clear(); - } - - 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 EnemyOutputs; - public NativeParallelMultiHashMap.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; - areaCandidateCount = 0; - - if (!_collisionCandidates.IsCreated) - { - return; - } - - for (int i = 0; i < _collisionCandidates.Length; i++) - { - CollisionCandidateData candidate = _collisionCandidates[i]; - if (candidate.SourceType == CollisionSourceTypeProjectile) - { - projectileCandidateCount++; - } - else if (candidate.SourceType == CollisionSourceTypeArea) - { - areaCandidateCount++; - } - } - } - - private static bool TryGetPlayerCollisionTarget(out int playerEntityId, out float3 playerPosition) - { - playerEntityId = PlayerEntityId; - playerPosition = default; - - if (!TryGetAliveTargetableEntity(playerEntityId, out TargetableObject playerTarget)) - { - return false; - } - - Transform playerTransform = playerTarget.CachedTransform; - if (playerTransform == null) - { - return false; - } - - Vector3 position = playerTransform.position; - playerPosition = new float3(position.x, position.y, position.z); - return true; - } - - private static bool WasCollisionSourceActiveAtQueryTime(int sourceEntityId) - { - EntityBase sourceEntity = TryGetEntityById(sourceEntityId); - if (sourceEntity == null || !sourceEntity.Available) - { - return false; - } - - if (sourceEntity is WeaponBase weapon) - { - return weapon.IsAttacking; - } - - if (sourceEntity is EnemyProjectile projectile) - { - return projectile.IsActive; - } - - return true; - } - - #endregion } } diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPresentation.cs b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPresentation.cs new file mode 100644 index 0000000..5f36e50 --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPresentation.cs @@ -0,0 +1,115 @@ +using CustomEvent; +using Definition.DataStruct; +using Entity; +using Entity.Weapon; +using UnityEngine; + +namespace Simulation +{ + public sealed partial class SimulationWorld + { + #region Collision Presentation + + private void DispatchProjectileHitPresentationEvent(int projectileEntityId, int sourceEntityId, + int sourceOwnerEntityId, int targetEntityId, int damage, in Vector3 hitPosition) + { + if (!_dispatchProjectileHitPresentationEvent) + { + return; + } + + var eventComponent = GameEntry.Event; + if (eventComponent == null) + { + return; + } + + eventComponent.Fire(this, ProjectileHitPresentationEventArgs.Create( + projectileEntityId, + sourceEntityId, + sourceOwnerEntityId, + targetEntityId, + damage, + hitPosition, + _dispatchProjectileHitMarkerEvent, + _dispatchProjectileHitEffectEvent, + _projectileHitPresentationEffectTypeId)); + } + + private static bool TryResolveImpactSource(EntityBase sourceEntity, EntityBase ownerEntity, + out EntityBase attacker, out ImpactData impactData) + { + if (TryResolveImpactFromEntity(sourceEntity, out impactData)) + { + attacker = sourceEntity; + return true; + } + + if (TryResolveImpactFromEntity(ownerEntity, out impactData)) + { + attacker = ownerEntity; + return true; + } + + attacker = null; + impactData = default; + return false; + } + + private static bool TryResolveImpactFromEntity(EntityBase entity, out ImpactData impactData) + { + if (entity is WeaponBase weapon) + { + impactData = weapon.GetImpactData(); + return true; + } + + if (entity is EnemyProjectile enemyProjectile) + { + impactData = enemyProjectile.GetImpactData(); + return true; + } + + if (entity is TargetableObject targetableObject) + { + impactData = targetableObject.GetImpactData(); + return true; + } + + impactData = default; + return false; + } + + private static bool TryGetAliveTargetableEntity(int entityId, out TargetableObject target) + { + target = null; + + var enemyManager = GameEntry.EnemyManager; + if (enemyManager != null && enemyManager.TryGetEnemy(entityId, out EntityBase enemyEntity)) + { + if (enemyEntity is TargetableObject enemyTarget && enemyTarget.Available && !enemyTarget.IsDead) + { + target = enemyTarget; + return true; + } + } + + EntityBase entity = TryGetEntityById(entityId); + if (entity is TargetableObject targetable && targetable.Available && !targetable.IsDead) + { + target = targetable; + return true; + } + + return false; + } + + private static EntityBase TryGetEntityById(int entityId) + { + var entityComponent = GameEntry.Entity; + return entityComponent != null ? entityComponent.GetGameEntity(entityId) : null; + } + + #endregion + } +} diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPresentation.cs.meta b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPresentation.cs.meta new file mode 100644 index 0000000..27d23f0 --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPresentation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 882102e53dea40a2b5b596e1b41bbed6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionRequests.cs b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionRequests.cs new file mode 100644 index 0000000..84e8ebb --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionRequests.cs @@ -0,0 +1,113 @@ +using Entity; +using Entity.Weapon; +using UnityEngine; + +namespace Simulation +{ + public sealed partial class SimulationWorld + { + #region Collision Requests + + public bool TryRequestAreaCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center, + float radius, int maxTargets = 16) + { + return TryRequestAreaCollisionInternal(sourceEntityId, sourceOwnerEntityId, in center, radius, + maxTargets, CollisionShapeCircle, Vector3.forward, 180f); + } + + 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); + } + + private bool TryRequestAreaCollisionInternal(int sourceEntityId, int sourceOwnerEntityId, + in Vector3 center, float radius, int maxTargets, int shapeType, in Vector3 direction, float halfAngleDeg) + { + if (!_useSimulationMovement) + { + return false; + } + + if (sourceEntityId == 0 || radius <= 0f || maxTargets <= 0) + { + return false; + } + + int resolvedOwnerEntityId = sourceOwnerEntityId != 0 ? sourceOwnerEntityId : sourceEntityId; + bool sourceWasActiveAtQueryTime = WasCollisionSourceActiveAtQueryTime(sourceEntityId); + Vector3 normalizedDirection = direction; + normalizedDirection.y = 0f; + if (normalizedDirection.sqrMagnitude <= Mathf.Epsilon) + { + normalizedDirection = Vector3.forward; + } + else + { + normalizedDirection.Normalize(); + } + + _areaCollisionRequests.Add(new AreaCollisionRequestData + { + SourceEntityId = sourceEntityId, + SourceOwnerEntityId = resolvedOwnerEntityId, + SourceWasActiveAtQueryTime = sourceWasActiveAtQueryTime, + Center = center, + Radius = Mathf.Max(0.01f, radius), + MaxTargets = Mathf.Max(1, maxTargets), + ShapeType = shapeType, + Direction = normalizedDirection, + HalfAngleDeg = Mathf.Clamp(halfAngleDeg, 0f, 180f) + }); + + return true; + } + + private int GetPendingAreaCollisionRequestCount() + { + return _areaCollisionRequests.Count; + } + + private int EstimatePendingAreaCollisionCandidateCountFromRequests() + { + int expectedCount = 0; + for (int i = 0; i < _areaCollisionRequests.Count; i++) + { + expectedCount += Mathf.Max(1, _areaCollisionRequests[i].MaxTargets); + } + + return expectedCount; + } + + private void ClearAreaCollisionTransientBuffers() + { + _areaCollisionRequests.Clear(); + _areaCollisionHitEvents.Clear(); + _areaCollisionHitDedupKeys.Clear(); + } + + private static bool WasCollisionSourceActiveAtQueryTime(int sourceEntityId) + { + EntityBase sourceEntity = TryGetEntityById(sourceEntityId); + if (sourceEntity == null || !sourceEntity.Available) + { + return false; + } + + if (sourceEntity is WeaponBase weapon) + { + return weapon.IsAttacking; + } + + if (sourceEntity is EnemyProjectile projectile) + { + return projectile.IsActive; + } + + return true; + } + + #endregion + } +} diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionRequests.cs.meta b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionRequests.cs.meta new file mode 100644 index 0000000..d8a8a70 --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionRequests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 56086ce3981e4ed5b95fdc54f7db85a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionResolve.cs b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionResolve.cs new file mode 100644 index 0000000..5caeb77 --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionResolve.cs @@ -0,0 +1,324 @@ +using CustomDebugger; +using CustomUtility; +using Definition.DataStruct; +using Definition.Enum; +using Entity; +using UnityEngine; + +namespace Simulation +{ + public sealed partial class SimulationWorld + { + #region Collision Resolve + + private void ResolveCollisionCandidatesOnMainThread() + { + if (!_collisionCandidates.IsCreated) + { + _lastResolvedAreaHitCount = 0; + ClearAreaCollisionTransientBuffers(); + return; + } + + _projectileResolvedEntityIds.Clear(); + _areaCollisionHitEvents.Clear(); + _areaCollisionHitDedupKeys.Clear(); + + if (_collisionCandidates.Length == 0) + { + _lastResolvedAreaHitCount = 0; + ClearAreaCollisionTransientBuffers(); + return; + } + + using (CustomProfilerMarker.Collision_ResolveProjectile.Auto()) + { + for (int i = 0; i < _collisionCandidates.Length; i++) + { + CollisionCandidateData candidate = _collisionCandidates[i]; + if (candidate.SourceType == CollisionSourceTypeProjectile) + { + int projectileEntityId = candidate.SourceEntityId; + if (_projectileResolvedEntityIds.Contains(projectileEntityId)) + { + continue; + } + + if (!TryGetActiveProjectileSimData(projectileEntityId, out _, out ProjectileSimData projectile)) + { + _projectileResolvedEntityIds.Add(projectileEntityId); + continue; + } + + bool shouldExpireProjectile = true; + bool shouldDispatchPresentation = false; + int damage = 0; + Vector3 hitPosition = projectile.Position; + if (TryGetAliveTargetableEntity(candidate.TargetEntityId, out TargetableObject target)) + { + EntityBase sourceEntity = TryGetEntityById(candidate.SourceEntityId); + EntityBase ownerEntity = TryGetEntityById(candidate.SourceOwnerEntityId); + shouldExpireProjectile = ResolveProjectileHitAgainstTarget(target, sourceEntity, + ownerEntity, + in projectile, + out damage, out hitPosition, out shouldDispatchPresentation); + } + + if (shouldDispatchPresentation) + { + DispatchProjectileHitPresentationEvent(projectileEntityId, candidate.SourceEntityId, + candidate.SourceOwnerEntityId, candidate.TargetEntityId, damage, in hitPosition); + } + + if (shouldExpireProjectile) + { + MarkProjectileAsExpired(projectileEntityId); + _projectileResolvedEntityIds.Add(projectileEntityId); + } + + continue; + } + + if (candidate.SourceType == CollisionSourceTypeArea) + { + long dedupKey = (((long)candidate.QueryId) << 32) ^ (uint)candidate.TargetEntityId; + if (!_areaCollisionHitDedupKeys.Add(dedupKey)) + { + continue; + } + + _areaCollisionHitEvents.Add(new AreaCollisionHitEventData + { + QueryId = candidate.QueryId, + SourceEntityId = candidate.SourceEntityId, + SourceOwnerEntityId = candidate.SourceOwnerEntityId, + TargetEntityId = candidate.TargetEntityId, + SqrDistance = candidate.SqrDistance + }); + } + } + } + + int resolvedAreaHitCount; + using (CustomProfilerMarker.Collision_ResolveArea.Auto()) + { + resolvedAreaHitCount = ResolveAreaCollisionHitsOnMainThread(); + } + + _lastResolvedAreaHitCount = resolvedAreaHitCount; + _projectileResolvedEntityIds.Clear(); + ClearAreaCollisionTransientBuffers(); + } + + private bool ResolveProjectileHitAgainstTarget(TargetableObject target, EntityBase sourceEntity, + EntityBase ownerEntity, + in ProjectileSimData projectile, out int damage, out Vector3 hitPosition, + out bool shouldDispatchPresentation) + { + damage = 0; + hitPosition = projectile.Position; + shouldDispatchPresentation = false; + + if (target == null || !target.Available || target.IsDead) + { + return true; + } + + if (target.CachedTransform != null) + { + hitPosition = target.CachedTransform.position; + } + + if (!TryResolveImpactSource(sourceEntity, ownerEntity, out EntityBase attacker, + out ImpactData sourceImpact)) + { + shouldDispatchPresentation = true; + return true; + } + + ImpactData targetImpact = target.GetImpactData(); + if (AIUtility.GetRelation(targetImpact.Camp, sourceImpact.Camp) == RelationType.Friendly) + { + return false; + } + + damage = AIUtility.CalcDamageHP(sourceImpact.AttackBase, sourceImpact.AttackStat, + targetImpact.DefenseStat, + targetImpact.DodgeStat); + shouldDispatchPresentation = true; + if (damage <= 0) + { + return true; + } + + target.ApplyDamage(attacker ?? sourceEntity ?? ownerEntity, damage); + return true; + } + + private int ResolveAreaCollisionHitsOnMainThread() + { + if (_areaCollisionHitEvents.Count == 0) + { + return 0; + } + + int resolvedHitCount = 0; + for (int i = 0; i < _areaCollisionHitEvents.Count; i++) + { + AreaCollisionHitEventData hitEvent = _areaCollisionHitEvents[i]; + if (!TryGetCollisionQueryByQueryId(hitEvent.QueryId, out CollisionQueryData query) || + query.SourceType != CollisionSourceTypeArea) + { + continue; + } + + if (!query.SourceWasActiveAtQueryTime) + { + continue; + } + + EntityBase sourceEntity = TryGetEntityById(hitEvent.SourceEntityId); + if (sourceEntity == null || !sourceEntity.Available) + { + continue; + } + + if (!TryGetAliveTargetableEntity(hitEvent.TargetEntityId, out TargetableObject target)) + { + continue; + } + + if (!IsAreaTargetInsidePreciseShape(in query, target)) + { + continue; + } + + AIUtility.PerformCollision(target, sourceEntity, true); + resolvedHitCount++; + } + + return resolvedHitCount; + } + + private bool TryGetCollisionQueryByQueryId(int queryId, out CollisionQueryData query) + { + query = default; + if (!_collisionQueryInputs.IsCreated || queryId < 0 || queryId >= _collisionQueryInputs.Length) + { + return false; + } + + CollisionQueryData direct = _collisionQueryInputs[queryId]; + if (direct.QueryId == queryId) + { + query = direct; + return true; + } + + for (int i = 0; i < _collisionQueryInputs.Length; i++) + { + CollisionQueryData candidate = _collisionQueryInputs[i]; + if (candidate.QueryId != queryId) + { + continue; + } + + query = candidate; + return true; + } + + return false; + } + + private static bool IsAreaTargetInsidePreciseShape(in CollisionQueryData query, TargetableObject target) + { + if (target == null || target.CachedTransform == null) + { + return false; + } + + Vector3 center = new Vector3(query.Position.x, query.Position.y, query.Position.z); + Vector3 toTarget = target.CachedTransform.position - center; + toTarget.y = 0f; + + float radius = Mathf.Max(0.01f, query.Radius); + float radiusSqr = radius * radius; + float sqrDistance = toTarget.sqrMagnitude; + if (sqrDistance > radiusSqr) + { + return false; + } + + if (query.ShapeType != CollisionShapeSector) + { + return true; + } + + if (sqrDistance <= Mathf.Epsilon) + { + return true; + } + + Vector3 forward = new Vector3(query.Direction.x, query.Direction.y, query.Direction.z); + forward.y = 0f; + if (forward.sqrMagnitude <= Mathf.Epsilon) + { + forward = Vector3.forward; + } + else + { + forward.Normalize(); + } + + float halfAngle = Mathf.Clamp(query.HalfAngleDeg, 0f, 180f); + float angle = Vector3.Angle(forward, toTarget.normalized); + return angle <= halfAngle; + } + + private bool TryGetActiveProjectileSimData(int projectileEntityId, out int simulationIndex, + out ProjectileSimData projectile) + { + 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) + { + 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) + { + return false; + } + + ProjectileSimData projectile = _projectiles[simulationIndex]; + projectile.Active = false; + projectile.State = ProjectileStateExpired; + projectile.RemainingLifetime = 0f; + if (projectile.LifeTime > 0f && projectile.Age < projectile.LifeTime) + { + projectile.Age = projectile.LifeTime; + } + + _projectiles[simulationIndex] = projectile; + return true; + } + + #endregion + } +} diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionResolve.cs.meta b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionResolve.cs.meta new file mode 100644 index 0000000..25015d0 --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionResolve.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5261fa73dd9c4bfc91fcba0d5ca1bcb1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs b/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs index 9fc40d4..b4dd18a 100644 --- a/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs +++ b/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs @@ -17,7 +17,11 @@ namespace Simulation // - DataChannel/SimulationWorld.JobDataChannel.cs: 本地 通道/缓冲区 持有者和数据的相互转换。 // - Jobs/SimulationWorld.EnemyJobs.cs: 模拟通道 编排 + 敌人移动/分离 顺序执行 // - Jobs/SimulationWorld.ProjectileJobs.cs: 投射物移动与回收 - // - Jobs/SimulationWorld.CollisionPipeline.cs: 碰撞请求的构造、过滤、求解流水线 + // - Jobs/SimulationWorld.CollisionPipeline.cs: 碰撞管线共享配置和状态 + // - Jobs/SimulationWorld.CollisionRequests.cs: area/sector 请求缓冲 + // - Jobs/SimulationWorld.CollisionBroadPhase.cs: broad-phase 候选构建和 Job 调度 + // - Jobs/SimulationWorld.CollisionResolve.cs: 主线程命中结算与 area settle + // - Jobs/SimulationWorld.CollisionPresentation.cs: 命中表现事件和实体/impact 解析 // - JobStruct/*.cs: burst job 内核和面向 job 的数据结构 private const float DefaultAttackRange = 1f; private const int EnemyStateIdle = 0; @@ -95,4 +99,4 @@ namespace Simulation #endregion } -} \ No newline at end of file +}