diff --git a/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs b/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs index 1907cf3..a4211b5 100644 --- a/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs +++ b/Assets/GameMain/Scripts/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs @@ -1,39 +1,18 @@ using System.Collections.Generic; using Unity.Collections; -using Unity.Mathematics; namespace Simulation { public sealed partial class SimulationWorld { - // Shared native buffers, collision stats, and channel-level constants. + // Shared channel constants plus compatibility fields still reflected by tests. private const int CollisionSourceTypeProjectile = 1; private const int CollisionSourceTypeArea = 2; private const int CollisionShapeCircle = 0; private const int CollisionShapeSector = 1; - private NativeList _enemyJobInputs; - private NativeList _enemyJobOutputs; - private NativeList _enemyJobSeparationOutputs; - private NativeList _enemySeparationPreviousPushes; - private NativeList _enemySeparationCurrentPushes; - private NativeList _projectileJobInputs; - private NativeList _projectileJobOutputs; + // Kept as top-level fields because current regression tests reflect them directly. private NativeList _collisionQueryInputs; - private NativeList _collisionCandidates; - private NativeParallelMultiHashMap _enemySeparationBuckets; - private NativeParallelMultiHashMap _enemyCollisionBuckets; private readonly List _areaCollisionRequests = new(16); - private readonly List _areaCollisionHitEvents = new(32); - private readonly HashSet _areaCollisionHitDedupKeys = new(); - private int _lastCollisionQueryCount; - private int _lastProjectileCollisionQueryCount; - private int _lastAreaCollisionQueryCount; - private int _lastCollisionCandidateCount; - private int _lastProjectileCollisionCandidateCount; - private int _lastAreaCollisionCandidateCount; - private int _lastResolvedAreaHitCount; - private float _lastCollisionCellSize; - private bool _lastCollisionHasEnemyTargets; } } diff --git a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPipeline.cs b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPipeline.cs index e32c5e1..e7db405 100644 --- a/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPipeline.cs +++ b/Assets/GameMain/Scripts/Simulation/Jobs/SimulationWorld.CollisionPipeline.cs @@ -1,6 +1,3 @@ -using Unity.Jobs; -using UnityEngine; - namespace Simulation { public sealed partial class SimulationWorld @@ -9,37 +6,5 @@ namespace Simulation // 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; - - [Header("Projectile Collision Query")] - [Tooltip("Projectile broad-phase collision query radius.")] - [SerializeField] - private float _projectileCollisionQueryRadius = 0.35f; - - [Tooltip("Maximum retained candidates per projectile query.")] - [SerializeField] - private int _projectileMaxCandidatesPerQuery = 1; - - [Tooltip("Broad-phase bucket cell size. <=0 derives from query radius.")] - [SerializeField] - private float _projectileCollisionCellSize = 0f; - - [Header("Projectile Hit Event Dispatch")] - [Tooltip("Dispatch projectile hit presentation event.")] - [SerializeField] - private bool _dispatchProjectileHitPresentationEvent = true; - - [Tooltip("Request hit marker when projectile hits.")] - [SerializeField] - private bool _dispatchProjectileHitMarkerEvent = true; - - [Tooltip("Request hit effect when projectile hits.")] - [SerializeField] - private bool _dispatchProjectileHitEffectEvent = true; - - [Tooltip("Default hit effect entity type id in presentation event. 0 means not specified.")] - [SerializeField] - private int _projectileHitPresentationEffectTypeId = 0; } } diff --git a/Assets/GameMain/Scripts/Simulation/SimulationWorld.EntityToSimData.cs b/Assets/GameMain/Scripts/Simulation/SimulationWorld.EntityToSimData.cs new file mode 100644 index 0000000..cbf3e8d --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/SimulationWorld.EntityToSimData.cs @@ -0,0 +1,109 @@ +using Components; +using Entity; +using Entity.EntityData; +using UnityEngine; + +namespace Simulation +{ + public sealed partial class SimulationWorld + { + #region Entity To Sim Data + + private static EnemySimData CreateEnemyInitialSimData(EnemyBase enemy, EnemyData enemyData) + { + Transform enemyTransform = enemy.CachedTransform; + MovementComponent movementComponent = enemy.GetComponent(); + + float speed = 0f; + if (enemyData != null) + { + speed = enemyData.SpeedBase; + } + else if (movementComponent != null) + { + speed = movementComponent.Speed; + } + + float attackRange = enemy.AttackRange > 0f + ? enemy.AttackRange + : DefaultAttackRange; + + return new EnemySimData + { + EntityId = enemy.Id, + Position = enemyTransform.position, + Forward = enemyTransform.forward, + Rotation = enemyTransform.rotation, + Speed = speed, + AttackRange = attackRange, + AvoidEnemyOverlap = movementComponent != null && movementComponent.AvoidEnemyOverlap, + EnemyBodyRadius = movementComponent != null ? movementComponent.EnemyBodyRadius : 0.45f, + SeparationIterations = movementComponent != null ? movementComponent.SeparationIterations : 2, + TargetType = 0, + State = EnemyStateIdle + }; + } + + private static ProjectileSimData CreateProjectileInitialSimData(EntityBase projectileEntity, object userData) + { + Vector3 forward = projectileEntity.CachedTransform.forward; + int ownerEntityId = 0; + Vector3 velocity = Vector3.zero; + float speed = 0f; + float lifeTime = 0f; + + if (userData is EnemyProjectileData enemyProjectileData) + { + ownerEntityId = enemyProjectileData.OwnerEntityId; + + Vector3 direction = enemyProjectileData.Direction; + direction.y = 0f; + if (direction.sqrMagnitude > Mathf.Epsilon) + { + direction.Normalize(); + forward = direction; + } + else if (forward.sqrMagnitude > Mathf.Epsilon) + { + forward = forward.normalized; + } + else + { + forward = Vector3.forward; + } + + speed = Mathf.Max(0f, enemyProjectileData.Speed); + velocity = forward * speed; + lifeTime = Mathf.Max(0f, enemyProjectileData.LifeTime); + } + + return new ProjectileSimData + { + EntityId = projectileEntity.Id, + OwnerEntityId = ownerEntityId, + Position = projectileEntity.CachedTransform.position, + Forward = forward, + Velocity = velocity, + Speed = speed, + LifeTime = lifeTime, + Age = 0f, + Active = true, + RemainingLifetime = lifeTime, + State = ProjectileStateActive + }; + } + + private static PickupSimData CreatePickupInitialSimData(EntityBase pickupEntity) + { + return new PickupSimData + { + EntityId = pickupEntity.Id, + Position = pickupEntity.CachedTransform.position, + PickupRadius = 0.35f, + State = 0 + }; + } + + #endregion + } +} diff --git a/Assets/GameMain/Scripts/Simulation/SimulationWorld.EntityToSimData.cs.meta b/Assets/GameMain/Scripts/Simulation/SimulationWorld.EntityToSimData.cs.meta new file mode 100644 index 0000000..0df076e --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/SimulationWorld.EntityToSimData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bdedabee0342441e9551c802dd66693a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Simulation/SimulationWorld.RuntimeModules.cs b/Assets/GameMain/Scripts/Simulation/SimulationWorld.RuntimeModules.cs new file mode 100644 index 0000000..00fb351 --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/SimulationWorld.RuntimeModules.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace Simulation +{ + public sealed partial class SimulationWorld + { + [SerializeField] private CollisionPipelineSettings _collisionPipelineSettings = new(); + [SerializeField] private TargetSelectionSettings _targetSelectionSettings = new(); + + private readonly SimulationStateStore _simulationState = new(); + private readonly JobDataRuntimeState _jobDataRuntime = new(); + private readonly CollisionPipelineRuntimeState _collisionPipelineRuntime = new(); + private readonly TargetSelectionRuntimeState _targetSelectionRuntime = new(); + + private List _enemies => _simulationState.Enemies; + private List _projectiles => _simulationState.Projectiles; + private List _pickups => _simulationState.Pickups; + private List _projectileRecycleEntityIds => _simulationState.ProjectileRecycleEntityIds; + private HashSet _projectileResolvedEntityIds => _collisionPipelineRuntime.ProjectileResolvedEntityIds; + + private EntityBinding EnemyBinding => _simulationState.EnemyBinding; + private EntityBinding ProjectileBinding => _simulationState.ProjectileBinding; + private EntityBinding PickupBinding => _simulationState.PickupBinding; + + private ref NativeList _enemyJobInputs => ref _jobDataRuntime.EnemyJobInputs; + private ref NativeList _enemyJobOutputs => ref _jobDataRuntime.EnemyJobOutputs; + private ref NativeList _enemyJobSeparationOutputs => ref _jobDataRuntime.EnemyJobSeparationOutputs; + private ref NativeList _enemySeparationPreviousPushes => ref _jobDataRuntime.EnemySeparationPreviousPushes; + private ref NativeList _enemySeparationCurrentPushes => ref _jobDataRuntime.EnemySeparationCurrentPushes; + private ref NativeList _projectileJobInputs => ref _jobDataRuntime.ProjectileJobInputs; + private ref NativeList _projectileJobOutputs => ref _jobDataRuntime.ProjectileJobOutputs; + private ref NativeList _collisionCandidates => ref _jobDataRuntime.CollisionCandidates; + private ref NativeParallelMultiHashMap _enemySeparationBuckets => ref _jobDataRuntime.EnemySeparationBuckets; + private ref NativeParallelMultiHashMap _enemyCollisionBuckets => ref _jobDataRuntime.EnemyCollisionBuckets; + private List _areaCollisionHitEvents => _jobDataRuntime.AreaCollisionHitEvents; + private HashSet _areaCollisionHitDedupKeys => _jobDataRuntime.AreaCollisionHitDedupKeys; + private ref int _lastCollisionQueryCount => ref _jobDataRuntime.LastCollisionQueryCount; + private ref int _lastProjectileCollisionQueryCount => ref _jobDataRuntime.LastProjectileCollisionQueryCount; + private ref int _lastAreaCollisionQueryCount => ref _jobDataRuntime.LastAreaCollisionQueryCount; + private ref int _lastCollisionCandidateCount => ref _jobDataRuntime.LastCollisionCandidateCount; + private ref int _lastProjectileCollisionCandidateCount => ref _jobDataRuntime.LastProjectileCollisionCandidateCount; + private ref int _lastAreaCollisionCandidateCount => ref _jobDataRuntime.LastAreaCollisionCandidateCount; + private ref int _lastResolvedAreaHitCount => ref _jobDataRuntime.LastResolvedAreaHitCount; + private ref float _lastCollisionCellSize => ref _jobDataRuntime.LastCollisionCellSize; + private ref bool _lastCollisionHasEnemyTargets => ref _jobDataRuntime.LastCollisionHasEnemyTargets; + + private ref JobHandle _collisionCandidateQueryHandle => ref _collisionPipelineRuntime.CollisionCandidateQueryHandle; + private ref bool _collisionCandidateQueryScheduled => ref _collisionPipelineRuntime.CollisionCandidateQueryScheduled; + + private ref NativeParallelMultiHashMap _enemyTargetBuckets => ref _targetSelectionRuntime.EnemyTargetBuckets; + private ref bool _enemyTargetBucketsDirty => ref _targetSelectionRuntime.EnemyTargetBucketsDirty; + + private float _projectileCollisionQueryRadius => _collisionPipelineSettings.ProjectileCollisionQueryRadius; + private int _projectileMaxCandidatesPerQuery => _collisionPipelineSettings.ProjectileMaxCandidatesPerQuery; + private float _projectileCollisionCellSize => _collisionPipelineSettings.ProjectileCollisionCellSize; + private bool _dispatchProjectileHitPresentationEvent => _collisionPipelineSettings.DispatchProjectileHitPresentationEvent; + private bool _dispatchProjectileHitMarkerEvent => _collisionPipelineSettings.DispatchProjectileHitMarkerEvent; + private bool _dispatchProjectileHitEffectEvent => _collisionPipelineSettings.DispatchProjectileHitEffectEvent; + private int _projectileHitPresentationEffectTypeId => _collisionPipelineSettings.ProjectileHitPresentationEffectTypeId; + private float _targetSelectionCellSize => _targetSelectionSettings.CellSize; + + [Serializable] + private sealed class CollisionPipelineSettings + { + [Header("Projectile Collision Query")] + [Tooltip("Projectile broad-phase collision query radius.")] + public float ProjectileCollisionQueryRadius = 0.35f; + + [Tooltip("Maximum retained candidates per projectile query.")] + public int ProjectileMaxCandidatesPerQuery = 1; + + [Tooltip("Broad-phase bucket cell size. <=0 derives from query radius.")] + public float ProjectileCollisionCellSize = 0f; + + [Header("Projectile Hit Event Dispatch")] + [Tooltip("Dispatch projectile hit presentation event.")] + public bool DispatchProjectileHitPresentationEvent = true; + + [Tooltip("Request hit marker when projectile hits.")] + public bool DispatchProjectileHitMarkerEvent = true; + + [Tooltip("Request hit effect when projectile hits.")] + public bool DispatchProjectileHitEffectEvent = true; + + [Tooltip("Default hit effect entity type id in presentation event. 0 means not specified.")] + public int ProjectileHitPresentationEffectTypeId; + } + + [Serializable] + private sealed class TargetSelectionSettings + { + [Header("Target Selection")] + [Tooltip("Spatial hash cell size for nearest-enemy queries.")] + public float CellSize = 2f; + } + + private sealed class SimulationStateStore + { + public readonly List Enemies = new(); + public readonly List Projectiles = new(); + public readonly List Pickups = new(); + public readonly List ProjectileRecycleEntityIds = new(); + public readonly EntityBinding EnemyBinding = new(); + public readonly EntityBinding ProjectileBinding = new(); + public readonly EntityBinding PickupBinding = new(); + } + + private sealed class JobDataRuntimeState + { + public NativeList EnemyJobInputs; + public NativeList EnemyJobOutputs; + public NativeList EnemyJobSeparationOutputs; + public NativeList EnemySeparationPreviousPushes; + public NativeList EnemySeparationCurrentPushes; + public NativeList ProjectileJobInputs; + public NativeList ProjectileJobOutputs; + public NativeList CollisionCandidates; + public NativeParallelMultiHashMap EnemySeparationBuckets; + public NativeParallelMultiHashMap EnemyCollisionBuckets; + public readonly List AreaCollisionHitEvents = new(32); + public readonly HashSet AreaCollisionHitDedupKeys = new(); + public int LastCollisionQueryCount; + public int LastProjectileCollisionQueryCount; + public int LastAreaCollisionQueryCount; + public int LastCollisionCandidateCount; + public int LastProjectileCollisionCandidateCount; + public int LastAreaCollisionCandidateCount; + public int LastResolvedAreaHitCount; + public float LastCollisionCellSize; + public bool LastCollisionHasEnemyTargets; + } + + private sealed class CollisionPipelineRuntimeState + { + public JobHandle CollisionCandidateQueryHandle; + public bool CollisionCandidateQueryScheduled; + public readonly HashSet ProjectileResolvedEntityIds = new(); + } + + private sealed class TargetSelectionRuntimeState + { + public NativeParallelMultiHashMap EnemyTargetBuckets; + public bool EnemyTargetBucketsDirty = true; + } + } +} diff --git a/Assets/GameMain/Scripts/Simulation/SimulationWorld.RuntimeModules.cs.meta b/Assets/GameMain/Scripts/Simulation/SimulationWorld.RuntimeModules.cs.meta new file mode 100644 index 0000000..2809300 --- /dev/null +++ b/Assets/GameMain/Scripts/Simulation/SimulationWorld.RuntimeModules.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e562a5a506b1424bb1a9eff97c8c469e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Simulation/SimulationWorld.SimEntityState.cs b/Assets/GameMain/Scripts/Simulation/SimulationWorld.SimEntityState.cs index 523fc75..dd4f0a1 100644 --- a/Assets/GameMain/Scripts/Simulation/SimulationWorld.SimEntityState.cs +++ b/Assets/GameMain/Scripts/Simulation/SimulationWorld.SimEntityState.cs @@ -1,7 +1,5 @@ -using Components; using Entity; using Entity.EntityData; -using UnityEngine; namespace Simulation { @@ -103,41 +101,6 @@ namespace Simulation return true; } - private static EnemySimData CreateEnemyInitialSimData(EnemyBase enemy, EnemyData enemyData) - { - Transform enemyTransform = enemy.CachedTransform; - MovementComponent movementComponent = enemy.GetComponent(); - - float speed = 0f; - if (enemyData != null) - { - speed = enemyData.SpeedBase; - } - else if (movementComponent != null) - { - speed = movementComponent.Speed; - } - - float attackRange = enemy != null && enemy.AttackRange > 0f - ? enemy.AttackRange - : DefaultAttackRange; - - return new EnemySimData - { - EntityId = enemy.Id, - Position = enemyTransform.position, - Forward = enemyTransform.forward, - Rotation = enemyTransform.rotation, - Speed = speed, - AttackRange = attackRange, - AvoidEnemyOverlap = movementComponent != null && movementComponent.AvoidEnemyOverlap, - EnemyBodyRadius = movementComponent != null ? movementComponent.EnemyBodyRadius : 0.45f, - SeparationIterations = movementComponent != null ? movementComponent.SeparationIterations : 2, - TargetType = 0, - State = EnemyStateIdle - }; - } - #endregion #region Projectile Simulation State @@ -196,55 +159,6 @@ namespace Simulation RemoveProjectileByEntityId(entityId); } - private static ProjectileSimData CreateProjectileInitialSimData(EntityBase projectileEntity, object userData) - { - Vector3 forward = projectileEntity.CachedTransform.forward; - int ownerEntityId = 0; - Vector3 velocity = Vector3.zero; - float speed = 0f; - float lifeTime = 0f; - - if (userData is EnemyProjectileData enemyProjectileData) - { - ownerEntityId = enemyProjectileData.OwnerEntityId; - - Vector3 direction = enemyProjectileData.Direction; - direction.y = 0f; - if (direction.sqrMagnitude > Mathf.Epsilon) - { - direction.Normalize(); - forward = direction; - } - else if (forward.sqrMagnitude > Mathf.Epsilon) - { - forward = forward.normalized; - } - else - { - forward = Vector3.forward; - } - - speed = Mathf.Max(0f, enemyProjectileData.Speed); - velocity = forward * speed; - lifeTime = Mathf.Max(0f, enemyProjectileData.LifeTime); - } - - return new ProjectileSimData - { - EntityId = projectileEntity.Id, - OwnerEntityId = ownerEntityId, - Position = projectileEntity.CachedTransform.position, - Forward = forward, - Velocity = velocity, - Speed = speed, - LifeTime = lifeTime, - Age = 0f, - Active = true, - RemainingLifetime = lifeTime, - State = ProjectileStateActive - }; - } - #endregion #region Pickup Simulation State @@ -303,17 +217,6 @@ namespace Simulation RemovePickupByEntityId(entityId); } - private static PickupSimData CreatePickupInitialSimData(EntityBase pickupEntity) - { - return new PickupSimData - { - EntityId = pickupEntity.Id, - Position = pickupEntity.CachedTransform.position, - PickupRadius = 0.35f, - State = 0 - }; - } - #endregion } -} +} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Simulation/SimulationWorld.TargetSelectionSpatialIndex.cs b/Assets/GameMain/Scripts/Simulation/SimulationWorld.TargetSelectionSpatialIndex.cs index ee75224..60af9b3 100644 --- a/Assets/GameMain/Scripts/Simulation/SimulationWorld.TargetSelectionSpatialIndex.cs +++ b/Assets/GameMain/Scripts/Simulation/SimulationWorld.TargetSelectionSpatialIndex.cs @@ -6,11 +6,6 @@ namespace Simulation { public sealed partial class SimulationWorld { - private NativeParallelMultiHashMap _enemyTargetBuckets; - private bool _enemyTargetBucketsDirty = true; - - [SerializeField] private float _targetSelectionCellSize = 2f; - public bool TryGetNearestEnemyEntityId(Vector3 origin, float maxSqrRange, out int enemyEntityId) { enemyEntityId = 0; diff --git a/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs b/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs index 7266eab..a200ee1 100644 --- a/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs +++ b/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs @@ -9,7 +9,9 @@ namespace Simulation { // Partial layout: // - SimulationWorld.cs: 核心状态、常量和 Unity 生命周期入口点。 + // - SimulationWorld.RuntimeModules.cs: 运行时域对象、配置和状态代理。 // - SimulationWorld.SimEntityState.cs: 模拟状态的增删改查和生命周期注册。 + // - SimulationWorld.EntityToSimData.cs: Unity 实体到 sim data 的初始化适配。 // - SimulationWorld.EntitySync.cs: GameFramework 实体 show/hide 事件桥。 // - SimulationWorld.TargetSelectionSpatialIndex.cs: 最近敌空间索引查询。 // - Presentation/SimulationWorld.TransformSync.cs: late-update transform 同步桥。 @@ -42,16 +44,6 @@ namespace Simulation private TransformSync _transformSync; private HitPresentation _hitPresentation; - private readonly List _enemies = new List(); - private readonly List _projectiles = new List(); - private readonly List _pickups = new List(); - private readonly List _projectileRecycleEntityIds = new List(); - private readonly HashSet _projectileResolvedEntityIds = new HashSet(); - - private EntityBinding EnemyBinding { get; } = new EntityBinding(); - private EntityBinding ProjectileBinding { get; } = new EntityBinding(); - private EntityBinding PickupBinding { get; } = new EntityBinding(); - public IReadOnlyList Enemies => _enemies; public IReadOnlyList Projectiles => _projectiles; public IReadOnlyList Pickups => _pickups;