From 92e1ea0d55f2b3b07dd2031defb06490f4246dc1 Mon Sep 17 00:00:00 2001 From: basil <2428390463@qq.com> Date: Fri, 18 Sep 2026 17:43:41 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8D=95=E9=87=8D=E6=9E=84=E6=8B=86?= =?UTF-8?q?=E5=88=86=20SimulationWorld?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .codegraph/.gitignore | 5 + AGENTS.md | 3 +- .../Simulation/Components.meta | 8 + .../Simulation/Components/EnemyAttackSim.cs | 9 + .../EnemyAttackSim.cs.meta} | 2 +- .../Simulation/Components/EnemyBehaviorSim.cs | 11 + .../EnemyBehaviorSim.cs.meta} | 2 +- .../Simulation/Components/EnemyIdentitySim.cs | 7 + .../EnemyIdentitySim.cs.meta} | 2 +- .../Components/EnemyMoveConfigSim.cs | 9 + .../Components/EnemyMoveConfigSim.cs.meta | 11 + .../Components/EnemyMoveInputSim.cs | 7 + .../Components/EnemyMoveInputSim.cs.meta | 11 + .../Components/EnemySeparationSim.cs | 9 + .../Components/EnemySeparationSim.cs.meta | 11 + .../Simulation/Components/EnemyStore.cs | 65 ++++ .../Simulation/Components/EnemyStore.cs.meta | 11 + .../Components/EnemyTransformSim.cs | 11 + .../Components/EnemyTransformSim.cs.meta | 11 + .../Components/EntityComponentStore.cs | 164 +++++++++ .../Components/EntityComponentStore.cs.meta | 11 + .../Components/ISimulationComponent.cs | 6 + .../Components/ISimulationComponent.cs.meta | 11 + .../Components/PickupIdentitySim.cs | 7 + .../Components/PickupIdentitySim.cs.meta | 11 + .../Simulation/Components/PickupRadiusSim.cs | 7 + .../Components/PickupRadiusSim.cs.meta | 11 + .../Simulation/Components/PickupStateSim.cs | 10 + .../Components/PickupStateSim.cs.meta | 11 + .../Simulation/Components/PickupStore.cs | 50 +++ .../Simulation/Components/PickupStore.cs.meta | 11 + .../Components/PickupTransformSim.cs | 9 + .../Components/PickupTransformSim.cs.meta | 11 + .../Components/ProjectileIdentitySim.cs | 8 + .../Components/ProjectileIdentitySim.cs.meta | 11 + .../Components/ProjectileLifeSim.cs | 9 + .../Components/ProjectileLifeSim.cs.meta | 11 + .../Simulation/Components/ProjectileStore.cs | 50 +++ .../Components/ProjectileStore.cs.meta | 11 + .../Components/ProjectileTransformSim.cs | 10 + .../Components/ProjectileTransformSim.cs.meta | 11 + .../Components/ProjectileVelocitySim.cs | 9 + .../Components/ProjectileVelocitySim.cs.meta | 11 + .../SimulationWorld.CollisionTransient.cs | 39 +- .../SimulationWorld.EnemySeparationBuffers.cs | 23 ++ ...lationWorld.EnemySeparationBuffers.cs.meta | 11 + ...SimulationWorld.EnemySeparationTemporal.cs | 91 ----- .../SimulationWorld.JobDataChannel.cs | 8 - .../SimulationWorld.JobDataConversion.cs | 151 -------- .../SimulationWorld.JobDataLifecycle.cs | 117 ++---- .../SimulationWorld.JobOutputCommit.cs | 40 --- .../Simulation/ISimulationWorld.cs | 35 ++ .../Simulation/ISimulationWorld.cs.meta | 11 + .../JobStruct/AreaCollisionHitEventData.cs | 17 +- .../JobStruct/AreaCollisionRequestData.cs | 27 +- .../BuildEnemySeparationBucketsBurstJob.cs | 48 --- ...uildEnemySeparationBucketsBurstJob.cs.meta | 3 - .../JobStruct/CollisionCandidateData.cs | 19 +- .../JobStruct/CollisionQueryData.cs | 31 +- .../Simulation/JobStruct/EnemyJobInputData.cs | 22 -- .../JobStruct/EnemyJobInputData.cs.meta | 3 - .../JobStruct/EnemyJobOutputData.cs | 22 -- .../JobStruct/EnemyJobOutputData.cs.meta | 3 - .../JobStruct/EnemyMovementBurstJob.cs | 93 ----- .../JobStruct/EnemyMovementBurstJob.cs.meta | 3 - .../JobStruct/EnemySeparationBurstJob.cs | 245 ------------- .../JobStruct/EnemySeparationBurstJob.cs.meta | 3 - .../JobStruct/ProjectileJobInputData.cs | 22 -- .../JobStruct/ProjectileJobInputData.cs.meta | 3 - .../JobStruct/ProjectileJobOutputData.cs | 22 -- .../JobStruct/ProjectileJobOutputData.cs.meta | 3 - .../JobStruct/ProjectileMovementBurstJob.cs | 118 ------ .../ProjectileMovementBurstJob.cs.meta | 3 - .../QueryCollisionCandidatesBurstJob.cs | 132 ------- .../QueryCollisionCandidatesBurstJob.cs.meta | 3 - .../SimulationWorld.CollisionBroadPhase.cs | 101 +----- .../Jobs/SimulationWorld.CollisionRequests.cs | 6 +- .../Jobs/SimulationWorld.CollisionResolve.cs | 56 ++- .../Jobs/SimulationWorld.EnemyJobs.cs | 314 +++++++++------- .../Jobs/SimulationWorld.ProjectileJobs.cs | 65 +--- .../SimulationWorld.TransformSync.cs | 39 +- .../Simulation/SimData/EnemySimData.cs | 1 - .../Simulation/SimData/ProjectileSimData.cs | 3 - .../Simulation/SimulationCell.cs | 17 + .../Simulation/SimulationCell.cs.meta | 11 + .../Simulation/SimulationCollision.cs | 11 + .../Simulation/SimulationCollision.cs.meta | 11 + .../Simulation/SimulationStats.cs | 36 ++ .../Simulation/SimulationStats.cs.meta | 11 + .../Simulation/SimulationTickContext.cs | 4 +- .../SimulationWorld.EntityToSimData.cs | 38 +- .../SimulationWorld.PlayerMovement.cs | 8 +- .../SimulationWorld.RuntimeModules.cs | 38 +- .../SimulationWorld.SimEntityState.cs | 336 ++++++++++++------ ...lationWorld.TargetSelectionSpatialIndex.cs | 35 +- .../Simulation/SimulationWorld.cs | 93 +++-- .../CustomComponent/Simulation/Systems.meta | 8 + .../Systems/CollisionQuerySystem.cs | 215 +++++++++++ .../Systems/CollisionQuerySystem.cs.meta | 11 + .../Simulation/Systems/EnemyMovementSystem.cs | 94 +++++ .../Systems/EnemyMovementSystem.cs.meta | 11 + .../Systems/EnemySeparationSystem.cs | 311 ++++++++++++++++ .../Systems/EnemySeparationSystem.cs.meta | 11 + .../Systems/PickupMovementSystem.cs | 99 ++++++ .../Systems/PickupMovementSystem.cs.meta | 11 + .../Systems/ProjectileMovementSystem.cs | 87 +++++ .../Systems/ProjectileMovementSystem.cs.meta | 11 + .../EditMode/SimulationWorldTickTests.cs | 233 ++++++++++-- .../PlayMode/SimulationWorldPlayModeTests.cs | 39 +- 109 files changed, 2605 insertions(+), 1817 deletions(-) create mode 100644 .codegraph/.gitignore create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyAttackSim.cs rename Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/{DataChannel/SimulationWorld.JobOutputCommit.cs.meta => Components/EnemyAttackSim.cs.meta} (83%) create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyBehaviorSim.cs rename Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/{DataChannel/SimulationWorld.EnemySeparationTemporal.cs.meta => Components/EnemyBehaviorSim.cs.meta} (83%) create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyIdentitySim.cs rename Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/{DataChannel/SimulationWorld.JobDataConversion.cs.meta => Components/EnemyIdentitySim.cs.meta} (83%) create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveConfigSim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveConfigSim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveInputSim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveInputSim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemySeparationSim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemySeparationSim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyStore.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyStore.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyTransformSim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyTransformSim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EntityComponentStore.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EntityComponentStore.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ISimulationComponent.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ISimulationComponent.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupIdentitySim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupIdentitySim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupRadiusSim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupRadiusSim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStateSim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStateSim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStore.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStore.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupTransformSim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupTransformSim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileIdentitySim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileIdentitySim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileLifeSim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileLifeSim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileStore.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileStore.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileTransformSim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileTransformSim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileVelocitySim.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileVelocitySim.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationBuffers.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationBuffers.cs.meta delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationTemporal.cs delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataConversion.cs delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobOutputCommit.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/ISimulationWorld.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/ISimulationWorld.cs.meta delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/BuildEnemySeparationBucketsBurstJob.cs delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/BuildEnemySeparationBucketsBurstJob.cs.meta delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobInputData.cs delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobInputData.cs.meta delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobOutputData.cs delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobOutputData.cs.meta delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyMovementBurstJob.cs delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyMovementBurstJob.cs.meta delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemySeparationBurstJob.cs delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemySeparationBurstJob.cs.meta delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobInputData.cs delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobInputData.cs.meta delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobOutputData.cs delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobOutputData.cs.meta delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileMovementBurstJob.cs delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileMovementBurstJob.cs.meta delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs delete mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCell.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCell.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCollision.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCollision.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationStats.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationStats.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/CollisionQuerySystem.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/CollisionQuerySystem.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemyMovementSystem.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemyMovementSystem.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemySeparationSystem.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemySeparationSystem.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/PickupMovementSystem.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/PickupMovementSystem.cs.meta create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/ProjectileMovementSystem.cs create mode 100644 Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/ProjectileMovementSystem.cs.meta diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 0000000..d20c0fe --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,5 @@ +# CodeGraph data files — local to each machine, not for committing. +# Ignore everything in .codegraph/ except this file itself, so transient +# files (the database, daemon.pid, sockets, logs) never show up in git. +* +!.gitignore diff --git a/AGENTS.md b/AGENTS.md index 8900a17..3d85671 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ -# AGENTS.md — SBE(Seek-Battle-Evacuate) +# AGENTS.md 单人 3d 类吸血鬼幸存者游戏原型。Unity 2022.3.62f3,框架为 GameFramework(UGF,源码在 `Assets/GameFramework/`),配置表用 Luban,插件有 UniTask、DOTween 等。 @@ -36,6 +36,7 @@ - 不做静态回退(不用默认值/兜底逻辑静默继续);异常路径直接 `Log.Error(...)` 抛出(UGF 的 `Log`,支持格式串,如 `Log.Error("Entity data is invalid.")`)。 - UI 的序列化字段(`[SerializeField]` 及 View 生成的引用)**不做判空检查**,默认已在 Inspector 中赋值。 - GameEntry 中提供的静态入口不对其进行判空,默认其不为空。 +- 对于从 Luban 中取出的数据,默认其是有效且不为空的,代码里不对其做任何有效性检测 ## 代码风格 diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components.meta new file mode 100644 index 0000000..278431a --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 67ad529203c03d8419375e2342eb485f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyAttackSim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyAttackSim.cs new file mode 100644 index 0000000..e52597b --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyAttackSim.cs @@ -0,0 +1,9 @@ +namespace SepCore.Simulation +{ + internal struct EnemyAttackSim : ISimulationComponent + { + public const float DefaultAttackRange = 1f; + + public float AttackRange; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobOutputCommit.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyAttackSim.cs.meta similarity index 83% rename from Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobOutputCommit.cs.meta rename to Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyAttackSim.cs.meta index 96ddf4d..c4c2919 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobOutputCommit.cs.meta +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyAttackSim.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 59ae2375305641a6a36d1bea0bf1abc2 +guid: 35d153869faa1494c9fbb7218bbdd5b0 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyBehaviorSim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyBehaviorSim.cs new file mode 100644 index 0000000..3b8a54a --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyBehaviorSim.cs @@ -0,0 +1,11 @@ +namespace SepCore.Simulation +{ + internal struct EnemyBehaviorSim : ISimulationComponent + { + public const int StateIdle = 0; + public const int StateChasing = 1; + public const int StateInAttackRange = 2; + + public int State; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationTemporal.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyBehaviorSim.cs.meta similarity index 83% rename from Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationTemporal.cs.meta rename to Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyBehaviorSim.cs.meta index e0fbe41..43b7b6d 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationTemporal.cs.meta +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyBehaviorSim.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b2c8675358384b21925e6f660ce61a79 +guid: 23b79d0c84ef6904588ff8da30bf4108 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyIdentitySim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyIdentitySim.cs new file mode 100644 index 0000000..63abd1b --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyIdentitySim.cs @@ -0,0 +1,7 @@ +namespace SepCore.Simulation +{ + internal struct EnemyIdentitySim : ISimulationComponent + { + public int EntityId; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataConversion.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyIdentitySim.cs.meta similarity index 83% rename from Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataConversion.cs.meta rename to Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyIdentitySim.cs.meta index a2d85e0..92ccac8 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataConversion.cs.meta +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyIdentitySim.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 57a3f582d9c04803908205e0cd4b5b75 +guid: f89e25340a7475048a94caab1c77e745 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveConfigSim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveConfigSim.cs new file mode 100644 index 0000000..9a65276 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveConfigSim.cs @@ -0,0 +1,9 @@ +namespace SepCore.Simulation +{ + internal struct EnemyMoveConfigSim : ISimulationComponent + { + public bool AvoidOverlap; + public float BodyRadius; + public int SeparationIterations; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveConfigSim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveConfigSim.cs.meta new file mode 100644 index 0000000..ee48b6e --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveConfigSim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa8be64dc4a8e0c4598f5b0be7fa7e03 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveInputSim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveInputSim.cs new file mode 100644 index 0000000..9f31121 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveInputSim.cs @@ -0,0 +1,7 @@ +namespace SepCore.Simulation +{ + internal struct EnemyMoveInputSim : ISimulationComponent + { + public float Speed; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveInputSim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveInputSim.cs.meta new file mode 100644 index 0000000..27bafc6 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyMoveInputSim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8883455a6bb9c94eba26c839184ca0b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemySeparationSim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemySeparationSim.cs new file mode 100644 index 0000000..2f72fd2 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemySeparationSim.cs @@ -0,0 +1,9 @@ +using Unity.Mathematics; + +namespace SepCore.Simulation +{ + internal struct EnemySeparationSim : ISimulationComponent + { + public float2 PreviousPush; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemySeparationSim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemySeparationSim.cs.meta new file mode 100644 index 0000000..65073c2 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemySeparationSim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eaeb6060e86ea424f956a2a478bc6126 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyStore.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyStore.cs new file mode 100644 index 0000000..e9dea32 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyStore.cs @@ -0,0 +1,65 @@ +namespace SepCore.Simulation +{ + internal sealed class EnemyStore : EntityComponentStore + { + private Column _identity; + private Column _transform; + private Column _moveInput; + private Column _moveConfig; + private Column _attack; + private Column _behavior; + private Column _separation; + + public Column Identity => _identity; + public Column Transform => _transform; + public Column MoveInput => _moveInput; + public Column MoveConfig => _moveConfig; + public Column Attack => _attack; + public Column Behavior => _behavior; + public Column Separation => _separation; + + public void Initialize(int capacity) + { + if (IsCreated) + { + return; + } + + Dispose(); + _identity = Register(capacity); + _transform = Register(capacity); + _moveInput = Register(capacity); + _moveConfig = Register(capacity); + _attack = Register(capacity); + _behavior = Register(capacity); + _separation = Register(capacity); + MarkInitialized(); + } + + public int Add(in EnemyIdentitySim identity, in EnemyTransformSim transform, in EnemyMoveInputSim moveInput, + in EnemyMoveConfigSim moveConfig, in EnemyAttackSim attack, in EnemyBehaviorSim behavior) + { + int index = AppendRow(identity.EntityId); + _identity.Data.Add(identity); + _transform.Data.Add(transform); + _moveInput.Data.Add(moveInput); + _moveConfig.Data.Add(moveConfig); + _attack.Data.Add(attack); + _behavior.Data.Add(behavior); + _separation.Data.Add(default); + return index; + } + + public override void Dispose() + { + base.Dispose(); + _identity = null; + _transform = null; + _moveInput = null; + _moveConfig = null; + _attack = null; + _behavior = null; + _separation = null; + } + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyStore.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyStore.cs.meta new file mode 100644 index 0000000..e39f7f6 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyStore.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 21d84c6a187090e46a1ec7637519c5a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyTransformSim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyTransformSim.cs new file mode 100644 index 0000000..c0d4b05 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyTransformSim.cs @@ -0,0 +1,11 @@ +using Unity.Mathematics; + +namespace SepCore.Simulation +{ + internal struct EnemyTransformSim : ISimulationComponent + { + public float3 Position; + public float3 Forward; + public quaternion Rotation; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyTransformSim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyTransformSim.cs.meta new file mode 100644 index 0000000..e10abde --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EnemyTransformSim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d8966797f1b08648886988017716ea3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EntityComponentStore.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EntityComponentStore.cs new file mode 100644 index 0000000..4f3b14b --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EntityComponentStore.cs @@ -0,0 +1,164 @@ +using System.Collections.Generic; +using Unity.Collections; + +namespace SepCore.Simulation +{ + internal abstract class EntityComponentStore + { + internal interface IColumn + { + bool IsCreated { get; } + void EnsureCapacity(int count); + void SwapBackRemove(int index, int lastIndex); + void Clear(); + void Dispose(); + } + + internal sealed class Column : IColumn where T : unmanaged, ISimulationComponent + { + private NativeList _data; + + public Column(int capacity) + { + _data = new NativeList(capacity, Allocator.Persistent); + } + + public NativeList Data => _data; + + public bool IsCreated => _data.IsCreated; + + public ref T this[int index] => ref _data.ElementAt(index); + + public void EnsureCapacity(int count) + { + if (_data.IsCreated && _data.Capacity < count) + { + _data.Capacity = count; + } + } + + public void SwapBackRemove(int index, int lastIndex) + { + if (index != lastIndex) + { + _data[index] = _data[lastIndex]; + } + + _data.RemoveAt(lastIndex); + } + + public void Clear() + { + if (_data.IsCreated) + { + _data.Clear(); + } + } + + public void Dispose() + { + if (_data.IsCreated) + { + _data.Dispose(); + } + + _data = default; + } + } + + private readonly EntityBinding _binding = new(); + private readonly List _columns = new(); + private int _count; + + public int Count => _count; + + public bool IsCreated { get; private set; } + + public void EnsureCapacity(int count) + { + for (int i = 0; i < _columns.Count; i++) + { + _columns[i].EnsureCapacity(count); + } + } + + public bool TryGetSimulationIndex(int entityId, out int index) + { + return _binding.TryGetSimulationIndex(entityId, out index); + } + + public bool RemoveByEntityId(int entityId) + { + if (!_binding.TryGetSimulationIndex(entityId, out int index)) + { + return false; + } + + int lastIndex = _count - 1; + bool hasMovedRow = index != lastIndex; + int movedEntityId = 0; + if (hasMovedRow) + { + _binding.TryGetEntityId(lastIndex, out movedEntityId); + } + + for (int i = 0; i < _columns.Count; i++) + { + _columns[i].SwapBackRemove(index, lastIndex); + } + + _binding.UnbindByEntityId(entityId); + if (hasMovedRow) + { + _binding.RemapIndex(movedEntityId, index); + } + + _count--; + return true; + } + + public void Clear() + { + for (int i = 0; i < _columns.Count; i++) + { + _columns[i].Clear(); + } + + _binding.Clear(); + _count = 0; + } + + public virtual void Dispose() + { + for (int i = 0; i < _columns.Count; i++) + { + _columns[i].Dispose(); + } + + _columns.Clear(); + _binding.Clear(); + _count = 0; + IsCreated = false; + } + + protected Column Register(int capacity) where T : unmanaged, ISimulationComponent + { + Column column = new Column(capacity); + _columns.Add(column); + return column; + } + + protected void MarkInitialized() + { + IsCreated = true; + } + + protected int AppendRow(int entityId) + { + int index = _count; + _binding.Bind(entityId, index); + _count++; + return index; + } + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EntityComponentStore.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EntityComponentStore.cs.meta new file mode 100644 index 0000000..3d926a1 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/EntityComponentStore.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ea8b87c1fbddf6743a7ce3339dc943a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ISimulationComponent.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ISimulationComponent.cs new file mode 100644 index 0000000..0e35df4 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ISimulationComponent.cs @@ -0,0 +1,6 @@ +namespace SepCore.Simulation +{ + internal interface ISimulationComponent + { + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ISimulationComponent.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ISimulationComponent.cs.meta new file mode 100644 index 0000000..99fe56d --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ISimulationComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f605b392ae908a439b2028cf5a4eb05 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupIdentitySim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupIdentitySim.cs new file mode 100644 index 0000000..3c87a4e --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupIdentitySim.cs @@ -0,0 +1,7 @@ +namespace SepCore.Simulation +{ + internal struct PickupIdentitySim : ISimulationComponent + { + public int EntityId; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupIdentitySim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupIdentitySim.cs.meta new file mode 100644 index 0000000..7fc4e06 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupIdentitySim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: be2030806e57ecc4f9276b506c800542 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupRadiusSim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupRadiusSim.cs new file mode 100644 index 0000000..bf8d2d7 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupRadiusSim.cs @@ -0,0 +1,7 @@ +namespace SepCore.Simulation +{ + internal struct PickupRadiusSim : ISimulationComponent + { + public float Radius; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupRadiusSim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupRadiusSim.cs.meta new file mode 100644 index 0000000..d49be06 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupRadiusSim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d5d8bf9445be1f14bba1ccf4093045f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStateSim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStateSim.cs new file mode 100644 index 0000000..abe154d --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStateSim.cs @@ -0,0 +1,10 @@ +namespace SepCore.Simulation +{ + internal struct PickupStateSim : ISimulationComponent + { + public const int StateIdle = 0; + public const int StateAbsorbing = 1; + + public int State; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStateSim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStateSim.cs.meta new file mode 100644 index 0000000..95ad450 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStateSim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e505617febb37c54bbec57f8882d3a8a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStore.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStore.cs new file mode 100644 index 0000000..4cc76ca --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStore.cs @@ -0,0 +1,50 @@ +namespace SepCore.Simulation +{ + internal sealed class PickupStore : EntityComponentStore + { + private Column _identity; + private Column _transform; + private Column _radius; + private Column _state; + + public Column Identity => _identity; + public Column Transform => _transform; + public Column Radius => _radius; + public Column State => _state; + + public void Initialize(int capacity) + { + if (IsCreated) + { + return; + } + + Dispose(); + _identity = Register(capacity); + _transform = Register(capacity); + _radius = Register(capacity); + _state = Register(capacity); + MarkInitialized(); + } + + public int Add(in PickupIdentitySim identity, in PickupTransformSim transform, in PickupRadiusSim radius, + in PickupStateSim state = default) + { + int index = AppendRow(identity.EntityId); + _identity.Data.Add(identity); + _transform.Data.Add(transform); + _radius.Data.Add(radius); + _state.Data.Add(state); + return index; + } + + public override void Dispose() + { + base.Dispose(); + _identity = null; + _transform = null; + _radius = null; + _state = null; + } + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStore.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStore.cs.meta new file mode 100644 index 0000000..51f37a7 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupStore.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88ed8cf0bf6d9cb459e071ff5b93dc54 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupTransformSim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupTransformSim.cs new file mode 100644 index 0000000..20b5f67 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupTransformSim.cs @@ -0,0 +1,9 @@ +using Unity.Mathematics; + +namespace SepCore.Simulation +{ + internal struct PickupTransformSim : ISimulationComponent + { + public float3 Position; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupTransformSim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupTransformSim.cs.meta new file mode 100644 index 0000000..f8c60fe --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/PickupTransformSim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8046df9b31763ac448c52a088f9fd9d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileIdentitySim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileIdentitySim.cs new file mode 100644 index 0000000..e4cc5ca --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileIdentitySim.cs @@ -0,0 +1,8 @@ +namespace SepCore.Simulation +{ + internal struct ProjectileIdentitySim : ISimulationComponent + { + public int EntityId; + public int OwnerEntityId; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileIdentitySim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileIdentitySim.cs.meta new file mode 100644 index 0000000..e262639 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileIdentitySim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 52b2efbbc6a2ee94a81016767ef6a6bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileLifeSim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileLifeSim.cs new file mode 100644 index 0000000..d8efba9 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileLifeSim.cs @@ -0,0 +1,9 @@ +namespace SepCore.Simulation +{ + internal struct ProjectileLifeSim : ISimulationComponent + { + public float Age; + public float LifeTime; + public bool Active; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileLifeSim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileLifeSim.cs.meta new file mode 100644 index 0000000..9e17167 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileLifeSim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4b7fb93b432358948ae81a913129bcf7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileStore.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileStore.cs new file mode 100644 index 0000000..f3d1074 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileStore.cs @@ -0,0 +1,50 @@ +namespace SepCore.Simulation +{ + internal sealed class ProjectileStore : EntityComponentStore + { + private Column _identity; + private Column _transform; + private Column _velocity; + private Column _life; + + public Column Identity => _identity; + public Column Transform => _transform; + public Column Velocity => _velocity; + public Column Life => _life; + + public void Initialize(int capacity) + { + if (IsCreated) + { + return; + } + + Dispose(); + _identity = Register(capacity); + _transform = Register(capacity); + _velocity = Register(capacity); + _life = Register(capacity); + MarkInitialized(); + } + + public int Add(in ProjectileIdentitySim identity, in ProjectileTransformSim transform, + in ProjectileVelocitySim velocity, in ProjectileLifeSim life) + { + int index = AppendRow(identity.EntityId); + _identity.Data.Add(identity); + _transform.Data.Add(transform); + _velocity.Data.Add(velocity); + _life.Data.Add(life); + return index; + } + + public override void Dispose() + { + base.Dispose(); + _identity = null; + _transform = null; + _velocity = null; + _life = null; + } + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileStore.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileStore.cs.meta new file mode 100644 index 0000000..92fd393 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileStore.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6de5ba3f6e96dc845b50ca960aa5ae32 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileTransformSim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileTransformSim.cs new file mode 100644 index 0000000..957faf7 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileTransformSim.cs @@ -0,0 +1,10 @@ +using Unity.Mathematics; + +namespace SepCore.Simulation +{ + internal struct ProjectileTransformSim : ISimulationComponent + { + public float3 Position; + public float3 Forward; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileTransformSim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileTransformSim.cs.meta new file mode 100644 index 0000000..b88c71d --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileTransformSim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 59b7c7f9782759b498e3b1d9a507ca81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileVelocitySim.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileVelocitySim.cs new file mode 100644 index 0000000..a72792d --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileVelocitySim.cs @@ -0,0 +1,9 @@ +using Unity.Mathematics; + +namespace SepCore.Simulation +{ + internal struct ProjectileVelocitySim : ISimulationComponent + { + public float3 Velocity; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileVelocitySim.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileVelocitySim.cs.meta new file mode 100644 index 0000000..1dcc04f --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Components/ProjectileVelocitySim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 01a59e095dd7381469d25c36b4a7e878 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.CollisionTransient.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.CollisionTransient.cs index 094a705..be76ea2 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.CollisionTransient.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.CollisionTransient.cs @@ -7,17 +7,18 @@ namespace SepCore.Simulation { #region Collision Transient - public int CollisionCandidateCount => _collisionCandidates.IsCreated ? _collisionCandidates.Length : 0; - public int PendingAreaCollisionRequestCount => _areaCollisionRequests.Count; - public int LastCollisionQueryCount => _lastCollisionQueryCount; - public int LastProjectileCollisionQueryCount => _lastProjectileCollisionQueryCount; - public int LastAreaCollisionQueryCount => _lastAreaCollisionQueryCount; - public int LastCollisionCandidateCount => _lastCollisionCandidateCount; - public int LastProjectileCollisionCandidateCount => _lastProjectileCollisionCandidateCount; - public int LastAreaCollisionCandidateCount => _lastAreaCollisionCandidateCount; - public int LastResolvedAreaHitCount => _lastResolvedAreaHitCount; - public float LastCollisionCellSize => _lastCollisionCellSize; - public bool LastCollisionHasEnemyTargets => _lastCollisionHasEnemyTargets; + public SimulationStats Stats => new SimulationStats( + _collisionCandidates.IsCreated ? _collisionCandidates.Length : 0, + _areaCollisionRequests.Count, + _lastCollisionQueryCount, + _lastProjectileCollisionQueryCount, + _lastAreaCollisionQueryCount, + _lastCollisionCandidateCount, + _lastProjectileCollisionCandidateCount, + _lastAreaCollisionCandidateCount, + _lastResolvedAreaHitCount, + _lastCollisionCellSize, + _lastCollisionHasEnemyTargets); private void ResetCollisionRuntimeStats() { @@ -45,8 +46,8 @@ namespace SepCore.Simulation _enemyCollisionBuckets.Clear(); } - private void AddProjectileCollisionQuery(int queryId, in ProjectileJobOutputData projectile, float radius, - int maxTargets = 1) + private void AddProjectileCollisionQuery(int queryId, in ProjectileTransformSim transform, + in ProjectileIdentitySim identity, float radius, int maxTargets = 1) { if (!_collisionQueryInputs.IsCreated || radius <= 0f) { @@ -56,14 +57,14 @@ namespace SepCore.Simulation _collisionQueryInputs.Add(new CollisionQueryData { QueryId = queryId, - SourceType = CollisionSourceTypeProjectile, - SourceEntityId = projectile.EntityId, - SourceOwnerEntityId = projectile.OwnerEntityId, + SourceType = SimulationCollision.SourceTypeProjectile, + SourceEntityId = identity.EntityId, + SourceOwnerEntityId = identity.OwnerEntityId, SourceWasActiveAtQueryTime = true, - Position = projectile.Position, + Position = transform.Position, Radius = radius, MaxTargets = math.max(1, maxTargets), - ShapeType = CollisionShapeCircle, + ShapeType = SimulationCollision.ShapeCircle, Direction = new float3(0f, 0f, 1f), HalfAngleDeg = 180f, HalfWidth = 0f, @@ -94,7 +95,7 @@ namespace SepCore.Simulation _collisionQueryInputs.Add(new CollisionQueryData { QueryId = queryId, - SourceType = CollisionSourceTypeArea, + SourceType = SimulationCollision.SourceTypeArea, SourceEntityId = sourceEntityId, SourceOwnerEntityId = sourceOwnerEntityId, SourceWasActiveAtQueryTime = sourceWasActiveAtQueryTime, diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationBuffers.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationBuffers.cs new file mode 100644 index 0000000..7f628dd --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationBuffers.cs @@ -0,0 +1,23 @@ +namespace SepCore.Simulation +{ + public sealed partial class SimulationWorld + { + #region Enemy Separation Buffers + + private void PrepareEnemySeparationJobBuffers(int enemyCount, int bucketCapacity) + { + InitializeJobDataChannels(); + EnsureCapacity(ref _enemySeparationTransforms, enemyCount); + _enemySeparationTransforms.Clear(); + if (enemyCount > 0) + { + _enemySeparationTransforms.ResizeUninitialized(enemyCount); + } + + EnsureCapacity(ref _enemySeparationBuckets, bucketCapacity); + _enemySeparationBuckets.Clear(); + } + + #endregion + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationBuffers.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationBuffers.cs.meta new file mode 100644 index 0000000..e31dcdf --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationBuffers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a7f58c069d920f4698662060cb2db0b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationTemporal.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationTemporal.cs deleted file mode 100644 index 2fada99..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.EnemySeparationTemporal.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Unity.Mathematics; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - #region Enemy Separation Temporal - - private void PrepareEnemySeparationJobBuffers(int enemyCount, int bucketCapacity) - { - InitializeJobDataChannels(); - EnsureCapacity(ref _enemyJobSeparationOutputs, enemyCount); - _enemyJobSeparationOutputs.Clear(); - if (enemyCount > 0) - { - _enemyJobSeparationOutputs.ResizeUninitialized(enemyCount); - } - - EnsureCapacity(ref _enemySeparationBuckets, bucketCapacity); - _enemySeparationBuckets.Clear(); - - EnsureCapacity(ref _enemySeparationPreviousPushes, enemyCount); - EnsureCapacity(ref _enemySeparationCurrentPushes, enemyCount); - - if (_enemySeparationPreviousPushes.Length < enemyCount) - { - int oldLength = _enemySeparationPreviousPushes.Length; - _enemySeparationPreviousPushes.ResizeUninitialized(enemyCount); - for (int i = oldLength; i < enemyCount; i++) - { - _enemySeparationPreviousPushes[i] = float2.zero; - } - } - else if (_enemySeparationPreviousPushes.Length > enemyCount) - { - _enemySeparationPreviousPushes.ResizeUninitialized(enemyCount); - } - - _enemySeparationCurrentPushes.Clear(); - if (enemyCount > 0) - { - _enemySeparationCurrentPushes.ResizeUninitialized(enemyCount); - } - } - - private void CommitEnemySeparationTemporalBuffers(int enemyCount) - { - if (!_enemySeparationPreviousPushes.IsCreated || !_enemySeparationCurrentPushes.IsCreated) - { - return; - } - - int copyCount = math.min(enemyCount, - math.min(_enemySeparationPreviousPushes.Length, _enemySeparationCurrentPushes.Length)); - for (int i = 0; i < copyCount; i++) - { - _enemySeparationPreviousPushes[i] = _enemySeparationCurrentPushes[i]; - } - } - - private void OnEnemyAddedToSeparationTemporalBuffers() - { - if (_enemySeparationPreviousPushes.IsCreated) - { - _enemySeparationPreviousPushes.Add(float2.zero); - } - - if (_enemySeparationCurrentPushes.IsCreated) - { - _enemySeparationCurrentPushes.Add(float2.zero); - } - } - - private void OnEnemyRemovedFromSeparationTemporalBuffers(int removedIndex) - { - if (_enemySeparationPreviousPushes.IsCreated && removedIndex >= 0 && - removedIndex < _enemySeparationPreviousPushes.Length) - { - _enemySeparationPreviousPushes.RemoveAtSwapBack(removedIndex); - } - - if (_enemySeparationCurrentPushes.IsCreated && removedIndex >= 0 && - removedIndex < _enemySeparationCurrentPushes.Length) - { - _enemySeparationCurrentPushes.RemoveAtSwapBack(removedIndex); - } - } - - #endregion - } -} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs index 7c22187..4979b61 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataChannel.cs @@ -5,14 +5,6 @@ namespace SepCore.Simulation { public sealed partial class SimulationWorld { - // 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 const int CollisionShapeRectangle = 2; - - // Kept as top-level fields because current regression tests reflect them directly. private NativeList _collisionQueryInputs; private readonly List _areaCollisionRequests = new(16); } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataConversion.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataConversion.cs deleted file mode 100644 index 222d75e..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataConversion.cs +++ /dev/null @@ -1,151 +0,0 @@ -using Unity.Mathematics; -using UnityEngine; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - #region Job Data Conversion - - private void SyncSimulationStateToJobInputs() - { - InitializeJobDataChannels(); - EnsureCapacity(ref _enemyJobInputs, _enemies.Count); - EnsureCapacity(ref _projectileJobInputs, _projectiles.Count); - - _enemyJobInputs.Clear(); - _projectileJobInputs.Clear(); - - foreach (EnemySimData data in _enemies) - { - _enemyJobInputs.Add(ConvertToEnemyJobInput(data)); - } - - foreach (ProjectileSimData data in _projectiles) - { - _projectileJobInputs.Add(ConvertToProjectileJobInput(data)); - } - } - - private void PrepareEnemyJobOutputBuffer(int enemyCount) - { - InitializeJobDataChannels(); - EnsureCapacity(ref _enemyJobOutputs, enemyCount); - _enemyJobOutputs.Clear(); - - if (enemyCount > 0) - { - _enemyJobOutputs.ResizeUninitialized(enemyCount); - } - } - - private void PrepareProjectileJobOutputBuffer(int projectileCount) - { - InitializeJobDataChannels(); - EnsureCapacity(ref _projectileJobOutputs, projectileCount); - _projectileJobOutputs.Clear(); - - if (projectileCount > 0) - { - _projectileJobOutputs.ResizeUninitialized(projectileCount); - } - } - - private void CopyProjectileInputsToOutputs() - { - for (int i = 0; i < _projectileJobInputs.Length; i++) - { - ProjectileJobInputData input = _projectileJobInputs[i]; - _projectileJobOutputs[i] = new ProjectileJobOutputData - { - EntityId = input.EntityId, - OwnerEntityId = input.OwnerEntityId, - Position = input.Position, - Forward = input.Forward, - Velocity = input.Velocity, - Speed = input.Speed, - LifeTime = input.LifeTime, - Age = input.Age, - Active = input.Active, - RemainingLifetime = input.RemainingLifetime, - State = input.State - }; - } - } - - private static EnemyJobInputData ConvertToEnemyJobInput(in EnemySimData enemy) - { - return new EnemyJobInputData - { - EntityId = enemy.EntityId, - Position = new float3(enemy.Position.x, enemy.Position.y, enemy.Position.z), - Forward = new float3(enemy.Forward.x, enemy.Forward.y, enemy.Forward.z), - Rotation = new quaternion(enemy.Rotation.x, enemy.Rotation.y, enemy.Rotation.z, enemy.Rotation.w), - Speed = enemy.Speed, - AttackRange = enemy.AttackRange, - AvoidEnemyOverlap = enemy.AvoidEnemyOverlap, - EnemyBodyRadius = enemy.EnemyBodyRadius, - SeparationIterations = enemy.SeparationIterations, - TargetType = enemy.TargetType, - State = enemy.State - }; - } - - private static EnemySimData ConvertToEnemySimData(in EnemyJobOutputData enemy) - { - return new EnemySimData - { - EntityId = enemy.EntityId, - Position = new Vector3(enemy.Position.x, enemy.Position.y, enemy.Position.z), - Forward = new Vector3(enemy.Forward.x, enemy.Forward.y, enemy.Forward.z), - Rotation = new Quaternion(enemy.Rotation.value.x, enemy.Rotation.value.y, enemy.Rotation.value.z, - enemy.Rotation.value.w), - Speed = enemy.Speed, - AttackRange = enemy.AttackRange, - AvoidEnemyOverlap = enemy.AvoidEnemyOverlap, - EnemyBodyRadius = enemy.EnemyBodyRadius, - SeparationIterations = enemy.SeparationIterations, - TargetType = enemy.TargetType, - State = enemy.State - }; - } - - private static ProjectileJobInputData ConvertToProjectileJobInput(in ProjectileSimData projectile) - { - return new ProjectileJobInputData - { - EntityId = projectile.EntityId, - OwnerEntityId = projectile.OwnerEntityId, - Position = new float3(projectile.Position.x, projectile.Position.y, projectile.Position.z), - Forward = new float3(projectile.Forward.x, projectile.Forward.y, projectile.Forward.z), - Velocity = new float3(projectile.Velocity.x, projectile.Velocity.y, projectile.Velocity.z), - Speed = projectile.Speed, - LifeTime = projectile.LifeTime, - Age = projectile.Age, - Active = projectile.Active, - RemainingLifetime = projectile.RemainingLifetime, - State = projectile.State - }; - } - - private static ProjectileSimData ConvertToProjectileSimData(in ProjectileJobOutputData projectile) - { - return new ProjectileSimData - { - EntityId = projectile.EntityId, - OwnerEntityId = projectile.OwnerEntityId, - Position = new Vector3(projectile.Position.x, projectile.Position.y, projectile.Position.z), - Forward = new Vector3(projectile.Forward.x, projectile.Forward.y, projectile.Forward.z), - Velocity = new Vector3(projectile.Velocity.x, projectile.Velocity.y, projectile.Velocity.z), - Speed = projectile.Speed, - LifeTime = projectile.LifeTime, - Age = projectile.Age, - Active = projectile.Active, - RemainingLifetime = projectile.RemainingLifetime, - State = projectile.State - }; - } - - #endregion - } -} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataLifecycle.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataLifecycle.cs index e701b45..c094e10 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataLifecycle.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobDataLifecycle.cs @@ -1,6 +1,5 @@ using System; using Unity.Collections; -using Unity.Mathematics; namespace SepCore.Simulation { @@ -16,70 +15,44 @@ namespace SepCore.Simulation } DisposeJobDataChannels(); - _enemyJobInputs = new NativeList(64, Allocator.Persistent); - _enemyJobOutputs = new NativeList(64, Allocator.Persistent); - _enemyJobSeparationOutputs = new NativeList(64, Allocator.Persistent); - _enemySeparationPreviousPushes = new NativeList(64, Allocator.Persistent); - _enemySeparationCurrentPushes = new NativeList(64, Allocator.Persistent); - _projectileJobInputs = new NativeList(64, Allocator.Persistent); - _projectileJobOutputs = new NativeList(64, Allocator.Persistent); + _enemies.Initialize(64); + _projectiles.Initialize(64); + _pickups.Initialize(32); + _enemySeparationTransforms = new NativeList(64, Allocator.Persistent); + _collectedPickups = new NativeList(64, Allocator.Persistent); + _enemySeparationBuckets = new NativeParallelMultiHashMap(256, Allocator.Persistent); _collisionQueryInputs = new NativeList(64, Allocator.Persistent); _collisionCandidates = new NativeList(128, Allocator.Persistent); - _enemySeparationBuckets = new NativeParallelMultiHashMap(256, Allocator.Persistent); _enemyCollisionBuckets = new NativeParallelMultiHashMap(256, Allocator.Persistent); InitializeEnemyTargetSpatialIndex(); } private void DisposeJobDataChannels() { - if (_enemyJobInputs.IsCreated) + _enemies.Dispose(); + _projectiles.Dispose(); + _pickups.Dispose(); + + if (_enemySeparationTransforms.IsCreated) { - _enemyJobInputs.Dispose(); + _enemySeparationTransforms.Dispose(); } - _enemyJobInputs = default; + _enemySeparationTransforms = default; - if (_enemyJobOutputs.IsCreated) + if (_collectedPickups.IsCreated) { - _enemyJobOutputs.Dispose(); + _collectedPickups.Dispose(); } - _enemyJobOutputs = default; + _collectedPickups = default; - if (_enemyJobSeparationOutputs.IsCreated) + if (_enemySeparationBuckets.IsCreated) { - _enemyJobSeparationOutputs.Dispose(); + _enemySeparationBuckets.Dispose(); } - _enemyJobSeparationOutputs = default; - - if (_enemySeparationPreviousPushes.IsCreated) - { - _enemySeparationPreviousPushes.Dispose(); - } - - _enemySeparationPreviousPushes = default; - - if (_enemySeparationCurrentPushes.IsCreated) - { - _enemySeparationCurrentPushes.Dispose(); - } - - _enemySeparationCurrentPushes = default; - - if (_projectileJobInputs.IsCreated) - { - _projectileJobInputs.Dispose(); - } - - _projectileJobInputs = default; - - if (_projectileJobOutputs.IsCreated) - { - _projectileJobOutputs.Dispose(); - } - - _projectileJobOutputs = default; + _enemySeparationBuckets = default; if (_collisionQueryInputs.IsCreated) { @@ -95,13 +68,6 @@ namespace SepCore.Simulation _collisionCandidates = default; - if (_enemySeparationBuckets.IsCreated) - { - _enemySeparationBuckets.Dispose(); - } - - _enemySeparationBuckets = default; - if (_enemyCollisionBuckets.IsCreated) { _enemyCollisionBuckets.Dispose(); @@ -127,24 +93,14 @@ namespace SepCore.Simulation return; } - if (_enemyJobInputs.IsCreated) + if (_enemySeparationTransforms.IsCreated) { - _enemyJobInputs.Clear(); + _enemySeparationTransforms.Clear(); } - if (_enemyJobOutputs.IsCreated) + if (_collectedPickups.IsCreated) { - _enemyJobOutputs.Clear(); - } - - if (_projectileJobInputs.IsCreated) - { - _projectileJobInputs.Clear(); - } - - if (_projectileJobOutputs.IsCreated) - { - _projectileJobOutputs.Clear(); + _collectedPickups.Clear(); } if (_collisionQueryInputs.IsCreated) @@ -157,21 +113,6 @@ namespace SepCore.Simulation _collisionCandidates.Clear(); } - if (_enemyJobSeparationOutputs.IsCreated) - { - _enemyJobSeparationOutputs.Clear(); - } - - if (_enemySeparationPreviousPushes.IsCreated) - { - _enemySeparationPreviousPushes.Clear(); - } - - if (_enemySeparationCurrentPushes.IsCreated) - { - _enemySeparationCurrentPushes.Clear(); - } - if (_enemySeparationBuckets.IsCreated) { _enemySeparationBuckets.Clear(); @@ -191,13 +132,11 @@ namespace SepCore.Simulation private bool AreJobDataChannelsUsable() { - return IsNativeListUsable(_enemyJobInputs) && - IsNativeListUsable(_enemyJobOutputs) && - IsNativeListUsable(_enemyJobSeparationOutputs) && - IsNativeListUsable(_enemySeparationPreviousPushes) && - IsNativeListUsable(_enemySeparationCurrentPushes) && - IsNativeListUsable(_projectileJobInputs) && - IsNativeListUsable(_projectileJobOutputs) && + return _enemies.IsCreated && + _projectiles.IsCreated && + _pickups.IsCreated && + IsNativeListUsable(_enemySeparationTransforms) && + IsNativeListUsable(_collectedPickups) && IsNativeListUsable(_collisionQueryInputs) && IsNativeListUsable(_collisionCandidates) && IsNativeMultiHashMapUsable(_enemySeparationBuckets) && diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobOutputCommit.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobOutputCommit.cs deleted file mode 100644 index 887e592..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/DataChannel/SimulationWorld.JobOutputCommit.cs +++ /dev/null @@ -1,40 +0,0 @@ -using UnityEngine; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - #region Job Output Commit - - private void ApplyJobOutputsToSimulationState() - { - int enemyCount = Mathf.Min(_enemies.Count, _enemyJobOutputs.Length); - bool hasEnemyPositionChanged = false; - for (int i = 0; i < enemyCount; i++) - { - EnemyJobOutputData output = _enemyJobOutputs[i]; - if (!hasEnemyPositionChanged) - { - Vector3 currentPosition = _enemies[i].Position; - hasEnemyPositionChanged = currentPosition.x != output.Position.x || - currentPosition.z != output.Position.z; - } - - _enemies[i] = ConvertToEnemySimData(output); - } - - if (hasEnemyPositionChanged) - { - MarkEnemyTargetSpatialIndexDirty(); - } - - int projectileCount = Mathf.Min(_projectiles.Count, _projectileJobOutputs.Length); - for (int i = 0; i < projectileCount; i++) - { - _projectiles[i] = ConvertToProjectileSimData(_projectileJobOutputs[i]); - } - } - - #endregion - } -} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/ISimulationWorld.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/ISimulationWorld.cs new file mode 100644 index 0000000..9eebf25 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/ISimulationWorld.cs @@ -0,0 +1,35 @@ +using UnityEngine; + +namespace SepCore.Simulation +{ + public interface ISimulationWorld + { + SimulationStats Stats { get; } + + void Tick(in SimulationTickContext context); + + void ClearSimulationState(); + + bool TryGetNearestEnemyEntityId(Vector3 origin, float maxSqrRange, out int enemyEntityId); + + bool TryRequestAreaCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center, float radius, + int maxTargets = 16); + + bool TryRequestSectorCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center, float radius, + in Vector3 direction, float halfAngleDeg, int maxTargets = 16); + + bool TryRequestRectangleCollision(int sourceEntityId, int sourceOwnerEntityId, in Vector3 center, + float halfWidth, float halfLength, in Vector3 direction, int maxTargets = 16); + + void SyncPlayerMovementInput(Transform playerTransform, bool isMoving, in Vector3 direction, float speed); + + void UnregisterPlayerMovement(Transform playerTransform); + + void SyncEnemyMovementInput(int entityId, bool isMoving, in Vector3 direction, float speed, + bool avoidEnemyOverlap, float enemyBodyRadius, int separationIterations); + + void SyncPlayerAbsorbSettings(float absorbRange, float absorbSpeed, float collectDistance); + + void TriggerFullMapMagnet(); + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/ISimulationWorld.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/ISimulationWorld.cs.meta new file mode 100644 index 0000000..c33b46e --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/ISimulationWorld.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f0174cbb31d284749a20c973c0153ca9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/AreaCollisionHitEventData.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/AreaCollisionHitEventData.cs index 25f18fd..f51d3b8 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/AreaCollisionHitEventData.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/AreaCollisionHitEventData.cs @@ -1,14 +1,11 @@ namespace SepCore.Simulation { - public sealed partial class SimulationWorld + internal struct AreaCollisionHitEventData { - private struct AreaCollisionHitEventData - { - public int QueryId; - public int SourceEntityId; - public int SourceOwnerEntityId; - public int TargetEntityId; - public float SqrDistance; - } + public int QueryId; + public int SourceEntityId; + public int SourceOwnerEntityId; + public int TargetEntityId; + public float SqrDistance; } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/AreaCollisionRequestData.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/AreaCollisionRequestData.cs index 8062a83..5431cff 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/AreaCollisionRequestData.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/AreaCollisionRequestData.cs @@ -2,21 +2,18 @@ using UnityEngine; namespace SepCore.Simulation { - public sealed partial class SimulationWorld + internal struct AreaCollisionRequestData { - private struct AreaCollisionRequestData - { - public int SourceEntityId; - public int SourceOwnerEntityId; - public bool SourceWasActiveAtQueryTime; - public Vector3 Center; - public float Radius; - public int MaxTargets; - public int ShapeType; - public Vector3 Direction; - public float HalfAngleDeg; - public float HalfWidth; - public float HalfLength; - } + public int SourceEntityId; + public int SourceOwnerEntityId; + public bool SourceWasActiveAtQueryTime; + public Vector3 Center; + public float Radius; + public int MaxTargets; + public int ShapeType; + public Vector3 Direction; + public float HalfAngleDeg; + public float HalfWidth; + public float HalfLength; } } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/BuildEnemySeparationBucketsBurstJob.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/BuildEnemySeparationBucketsBurstJob.cs deleted file mode 100644 index ff157b9..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/BuildEnemySeparationBucketsBurstJob.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - [BurstCompile] - private struct BuildEnemySeparationBucketsBurstJob : IJobParallelFor - { - [ReadOnly] public NativeArray Inputs; - public NativeParallelMultiHashMap.ParallelWriter Buckets; - public float CellSize; - - public void Execute(int index) - { - BuildEnemySeparationBucket( - index, - Inputs, - Buckets, - CellSize - ); - } - } - - private static void BuildEnemySeparationBucket( - int index, - NativeArray inputs, - NativeParallelMultiHashMap.ParallelWriter buckets, - float cellSize - ) - { - EnemyJobOutputData output = inputs[index]; - if (!output.AvoidEnemyOverlap) - { - return; - } - - float3 position = output.Position; - position.y = 0f; - int cellX = (int)math.floor(position.x / cellSize); - int cellZ = (int)math.floor(position.z / cellSize); - buckets.Add(SeparationCellKey(cellX, cellZ), index); - } - } -} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/BuildEnemySeparationBucketsBurstJob.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/BuildEnemySeparationBucketsBurstJob.cs.meta deleted file mode 100644 index 896729f..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/BuildEnemySeparationBucketsBurstJob.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: e833e91677bb49a4a458c9c8b90099e9 -timeCreated: 1771901500 \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/CollisionCandidateData.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/CollisionCandidateData.cs index 5a32429..23f8bd0 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/CollisionCandidateData.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/CollisionCandidateData.cs @@ -1,15 +1,12 @@ namespace SepCore.Simulation { - public sealed partial class SimulationWorld + internal struct CollisionCandidateData { - private struct CollisionCandidateData - { - public int QueryId; - public int SourceType; - public int SourceEntityId; - public int SourceOwnerEntityId; - public int TargetEntityId; - public float SqrDistance; - } + public int QueryId; + public int SourceType; + public int SourceEntityId; + public int SourceOwnerEntityId; + public int TargetEntityId; + public float SqrDistance; } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/CollisionQueryData.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/CollisionQueryData.cs index 8e134f8..03b94cf 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/CollisionQueryData.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/CollisionQueryData.cs @@ -2,23 +2,20 @@ using Unity.Mathematics; namespace SepCore.Simulation { - public sealed partial class SimulationWorld + internal struct CollisionQueryData { - private struct CollisionQueryData - { - public int QueryId; - public int SourceType; - public int SourceEntityId; - public int SourceOwnerEntityId; - public bool SourceWasActiveAtQueryTime; - public float3 Position; - public float Radius; - public int MaxTargets; - public int ShapeType; - public float3 Direction; - public float HalfAngleDeg; - public float HalfWidth; - public float HalfLength; - } + public int QueryId; + public int SourceType; + public int SourceEntityId; + public int SourceOwnerEntityId; + public bool SourceWasActiveAtQueryTime; + public float3 Position; + public float Radius; + public int MaxTargets; + public int ShapeType; + public float3 Direction; + public float HalfAngleDeg; + public float HalfWidth; + public float HalfLength; } } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobInputData.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobInputData.cs deleted file mode 100644 index e3f3b37..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobInputData.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Unity.Mathematics; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - private struct EnemyJobInputData - { - public int EntityId; - public float3 Position; - public float3 Forward; - public quaternion Rotation; - public float Speed; - public float AttackRange; - public bool AvoidEnemyOverlap; - public float EnemyBodyRadius; - public int SeparationIterations; - public int TargetType; - public int State; - } - } -} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobInputData.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobInputData.cs.meta deleted file mode 100644 index ce66843..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobInputData.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 171936b8a6b84a8fbacc7651ce473b98 -timeCreated: 1771901146 \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobOutputData.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobOutputData.cs deleted file mode 100644 index ead0acd..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobOutputData.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Unity.Mathematics; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - private struct EnemyJobOutputData - { - public int EntityId; - public float3 Position; - public float3 Forward; - public quaternion Rotation; - public float Speed; - public float AttackRange; - public bool AvoidEnemyOverlap; - public float EnemyBodyRadius; - public int SeparationIterations; - public int TargetType; - public int State; - } - } -} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobOutputData.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobOutputData.cs.meta deleted file mode 100644 index d12a3e8..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyJobOutputData.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 34ef6fbf123f45ca88d8e7ab8311d543 -timeCreated: 1771901204 \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyMovementBurstJob.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyMovementBurstJob.cs deleted file mode 100644 index 0f58fa9..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyMovementBurstJob.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - [BurstCompile] - private struct EnemyMovementBurstJob : IJobParallelFor - { - [ReadOnly] public NativeArray Inputs; - public NativeArray Outputs; - public float DeltaTime; - public float3 PlayerPosition; - - public void Execute(int index) - { - ExecuteEnemyMovement( - index, - Inputs, - Outputs, - DeltaTime, - PlayerPosition - ); - } - } - - private static void ExecuteEnemyMovement( - int index, - NativeArray inputs, - NativeArray outputs, - float deltaTime, - float3 playerPosition - ) - { - EnemyJobInputData input = inputs[index]; - float attackRange = input.AttackRange > 0f ? input.AttackRange : DefaultAttackRange; - float attackRangeSqr = attackRange * attackRange; - - float3 currentPosition = input.Position; - float3 horizontalPosition = new float3(currentPosition.x, 0f, currentPosition.z); - float3 toPlayer = playerPosition - horizontalPosition; - float sqrDistance = math.lengthsq(toPlayer); - bool isInAttackRange = sqrDistance <= attackRangeSqr; - bool canChase = !isInAttackRange && input.Speed > 0f && sqrDistance > float.Epsilon; - - float3 forward = input.Forward; - float3 desiredPosition = currentPosition; - quaternion rotation = input.Rotation; - - if (canChase) - { - forward = math.normalizesafe(toPlayer, forward); - desiredPosition = currentPosition + forward * input.Speed * deltaTime; - if (math.lengthsq(forward) > float.Epsilon) - { - rotation = quaternion.LookRotationSafe(forward, math.up()); - } - } - - int nextState; - if (isInAttackRange) - { - nextState = EnemyStateInAttackRange; - } - else if (canChase) - { - nextState = EnemyStateChasing; - } - else - { - nextState = EnemyStateIdle; - } - - outputs[index] = new EnemyJobOutputData - { - EntityId = input.EntityId, - Position = desiredPosition, - Forward = forward, - Rotation = rotation, - Speed = input.Speed, - AttackRange = attackRange, - AvoidEnemyOverlap = input.AvoidEnemyOverlap, - EnemyBodyRadius = input.EnemyBodyRadius, - SeparationIterations = input.SeparationIterations, - TargetType = input.TargetType, - State = nextState - }; - } - } -} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyMovementBurstJob.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyMovementBurstJob.cs.meta deleted file mode 100644 index dbd2ee4..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemyMovementBurstJob.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: abd5519779ed41d4947280eec326ffb1 -timeCreated: 1771901472 \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemySeparationBurstJob.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemySeparationBurstJob.cs deleted file mode 100644 index 53b464a..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemySeparationBurstJob.cs +++ /dev/null @@ -1,245 +0,0 @@ -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - [BurstCompile] - private struct EnemySeparationBurstJob : IJobParallelFor - { - [ReadOnly] public NativeArray Inputs; - [ReadOnly] public NativeParallelMultiHashMap Buckets; - [ReadOnly] public NativeArray PreviousPushes; - public NativeArray Outputs; - public NativeArray CurrentPushes; - public float CellSize; - public float MaxRadius; - public float3 PlayerPosition; - public float PushDamping; - public float MaxStepScale; - public bool UseTangentialInAttackRange; - public float PushSmoothing; - - public void Execute(int index) - { - ExecuteEnemySeparation( - index, - Inputs, - Buckets, - Outputs, - CellSize, - MaxRadius, - PlayerPosition, - PushDamping, - MaxStepScale, - UseTangentialInAttackRange, - PreviousPushes, - CurrentPushes, - PushSmoothing - ); - } - } - - private static void ExecuteEnemySeparation( - int index, - NativeArray inputs, - NativeParallelMultiHashMap buckets, - NativeArray outputs, - float cellSize, - float maxRadius, - float3 playerPosition, - float pushDamping, - float maxStepScale, - bool useTangentialInAttackRange, - NativeArray previousPushes, - NativeArray currentPushes, - float pushSmoothing - ) - { - currentPushes[index] = float2.zero; - EnemyJobOutputData self = inputs[index]; - if (!self.AvoidEnemyOverlap) - { - outputs[index] = self; - return; - } - - float3 candidate = self.Position; - candidate.y = 0f; - float3 original = candidate; - float3 fallback = - math.normalizesafe(new float3(self.Forward.x, 0f, self.Forward.z), new float3(1f, 0f, 0f)); - float selfRadius = self.EnemyBodyRadius > 0f ? self.EnemyBodyRadius : 0.45f; - int iterations = self.SeparationIterations > 0 ? self.SeparationIterations : 1; - int queryRange = math.max(1, (int)math.ceil((selfRadius + maxRadius) / cellSize)); - - for (int iter = 0; iter < iterations; iter++) - { - int cellX = (int)math.floor(candidate.x / cellSize); - int cellZ = (int)math.floor(candidate.z / cellSize); - float3 pushAccumulation = float3.zero; - - for (int dx = -queryRange; dx <= queryRange; dx++) - { - for (int dz = -queryRange; dz <= queryRange; dz++) - { - long key = SeparationCellKey(cellX + dx, cellZ + dz); - if (!buckets.TryGetFirstValue(key, out int otherIndex, - out NativeParallelMultiHashMapIterator iterator)) - { - continue; - } - - do - { - if (otherIndex == index) - { - continue; - } - - EnemyJobOutputData other = inputs[otherIndex]; - if (!other.AvoidEnemyOverlap) - { - continue; - } - - float otherRadius = other.EnemyBodyRadius > 0f ? other.EnemyBodyRadius : 0.45f; - float minDistance = selfRadius + otherRadius; - float minDistanceSqr = minDistance * minDistance; - - float3 otherPosition = other.Position; - otherPosition.y = 0f; - float3 toSelf = candidate - otherPosition; - float sqrDistance = math.lengthsq(toSelf); - - if (sqrDistance <= float.Epsilon) - { - float3 zeroDistanceAxis = GetZeroDistanceSeparationAxis(index, otherIndex); - float directionSign = index < otherIndex ? 1f : -1f; - pushAccumulation += zeroDistanceAxis * (selfRadius * 0.25f * directionSign); - continue; - } - - if (sqrDistance >= minDistanceSqr) - { - continue; - } - - float distance = math.sqrt(sqrDistance); - float penetration = minDistance - distance; - pushAccumulation += (toSelf / distance) * penetration; - } while (buckets.TryGetNextValue(out otherIndex, ref iterator)); - } - } - - if (math.lengthsq(pushAccumulation) <= float.Epsilon) - { - continue; - } - - float3 resolvedPush = pushAccumulation * pushDamping; - - float maxStep = selfRadius * maxStepScale; - float pushLength = math.length(resolvedPush); - if (pushLength > maxStep && pushLength > float.Epsilon) - { - resolvedPush = resolvedPush / pushLength * maxStep; - } - - candidate += resolvedPush; - } - - float3 framePush = candidate - original; - float2 previousPush2 = previousPushes[index]; - float3 previousPush = new float3(previousPush2.x, 0f, previousPush2.y); - float3 smoothedPush = SmoothSeparationPush(framePush, previousPush, pushSmoothing); - - if (useTangentialInAttackRange && self.State == EnemyStateInAttackRange) - { - smoothedPush = ProjectToTangential(smoothedPush, playerPosition, original); - } - - float maxTotalStep = selfRadius * maxStepScale * iterations; - float smoothedLength = math.length(smoothedPush); - if (smoothedLength > maxTotalStep && smoothedLength > float.Epsilon) - { - smoothedPush = smoothedPush / smoothedLength * maxTotalStep; - } - - float3 finalPosition = original + smoothedPush; - currentPushes[index] = new float2(smoothedPush.x, smoothedPush.z); - self.Position = new float3(finalPosition.x, self.Position.y, finalPosition.z); - if (math.lengthsq(smoothedPush) > float.Epsilon) - { - self.Forward = new float3(fallback.x, self.Forward.y, fallback.z); - } - - outputs[index] = self; - } - - private static float3 SmoothSeparationPush(float3 framePush, float3 previousPush, float pushSmoothing) - { - float frameLengthSqr = math.lengthsq(framePush); - float previousLengthSqr = math.lengthsq(previousPush); - - if (frameLengthSqr <= float.Epsilon) - { - return float3.zero; - } - - if (previousLengthSqr <= float.Epsilon || pushSmoothing <= 0f) - { - return framePush; - } - - float frameLength = math.sqrt(frameLengthSqr); - float previousLength = math.sqrt(previousLengthSqr); - float3 frameDirection = framePush / frameLength; - float3 previousDirection = previousPush / previousLength; - float directionAlignment = math.dot(frameDirection, previousDirection); - - if (directionAlignment >= 0.35f) - { - return framePush; - } - - float directionalFactor = math.saturate((0.35f - directionAlignment) / 1.35f); - float smoothingStrength = pushSmoothing * directionalFactor; - return math.lerp(framePush, previousPush, smoothingStrength); - } - - private static float3 ProjectToTangential(float3 push, float3 playerPosition, float3 currentPosition) - { - if (math.lengthsq(push) <= float.Epsilon) - { - return push; - } - - float3 toPlayer = playerPosition - currentPosition; - float toPlayerSqr = math.lengthsq(toPlayer); - if (toPlayerSqr <= float.Epsilon) - { - return push; - } - - float3 radialDirection = toPlayer / math.sqrt(toPlayerSqr); - float radialOffset = math.dot(push, radialDirection); - return push - radialDirection * radialOffset; - } - - private static float3 GetZeroDistanceSeparationAxis(int index, int otherIndex) - { - int lowIndex = math.min(index, otherIndex); - int highIndex = math.max(index, otherIndex); - uint pairHash = (uint)(lowIndex * 73856093) ^ (uint)(highIndex * 19349663); - - float axisX = (pairHash & 1023u) / 511.5f - 1f; - float axisZ = ((pairHash >> 10) & 1023u) / 511.5f - 1f; - float3 axis = new float3(axisX, 0f, axisZ); - return math.normalizesafe(axis, new float3(1f, 0f, 0f)); - } - } -} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemySeparationBurstJob.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemySeparationBurstJob.cs.meta deleted file mode 100644 index 5142e9a..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/EnemySeparationBurstJob.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 94a77477c3a8410cb58b5028dd5d2c63 -timeCreated: 1771901543 \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobInputData.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobInputData.cs deleted file mode 100644 index f9b6c73..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobInputData.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Unity.Mathematics; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - private struct ProjectileJobInputData - { - public int EntityId; - public int OwnerEntityId; - public float3 Position; - public float3 Forward; - public float3 Velocity; - public float Speed; - public float LifeTime; - public float Age; - public bool Active; - public float RemainingLifetime; - public int State; - } - } -} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobInputData.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobInputData.cs.meta deleted file mode 100644 index 5613784..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobInputData.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 334ca3869d7940058256ba5419573637 -timeCreated: 1771901235 \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobOutputData.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobOutputData.cs deleted file mode 100644 index 3fc5279..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobOutputData.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Unity.Mathematics; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - private struct ProjectileJobOutputData - { - public int EntityId; - public int OwnerEntityId; - public float3 Position; - public float3 Forward; - public float3 Velocity; - public float Speed; - public float LifeTime; - public float Age; - public bool Active; - public float RemainingLifetime; - public int State; - } - } -} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobOutputData.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobOutputData.cs.meta deleted file mode 100644 index 62908ee..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileJobOutputData.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: b5c4a144914548379d9991d8ef061da7 -timeCreated: 1771901266 \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileMovementBurstJob.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileMovementBurstJob.cs deleted file mode 100644 index 002a6e8..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileMovementBurstJob.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - [BurstCompile] - private struct ProjectileMovementBurstJob : IJobParallelFor - { - [ReadOnly] public NativeArray Inputs; - public NativeArray Outputs; - public float DeltaTime; - public float3 PlayerPosition; - public float MaxSqrDistanceFromPlayer; - public float MaxVerticalOffsetFromPlayer; - - public void Execute(int index) - { - ExecuteProjectileMovement( - index, - Inputs, - Outputs, - DeltaTime, - PlayerPosition, - MaxSqrDistanceFromPlayer, - MaxVerticalOffsetFromPlayer); - } - } - - private static void ExecuteProjectileMovement( - int index, - NativeArray inputs, - NativeArray outputs, - float deltaTime, - float3 playerPosition, - float maxSqrDistanceFromPlayer, - float maxVerticalOffsetFromPlayer) - { - ProjectileJobInputData input = inputs[index]; - ProjectileJobOutputData output = new ProjectileJobOutputData - { - EntityId = input.EntityId, - OwnerEntityId = input.OwnerEntityId, - Position = input.Position, - Forward = input.Forward, - Velocity = input.Velocity, - Speed = input.Speed, - LifeTime = input.LifeTime, - Age = input.Age, - Active = input.Active, - RemainingLifetime = input.RemainingLifetime, - State = input.State - }; - - if (!input.Active) - { - output.State = ProjectileStateExpired; - outputs[index] = output; - return; - } - - float3 position = input.Position; - float3 forward = input.Forward; - float3 velocity = input.Velocity; - if (math.lengthsq(velocity) <= float.Epsilon && input.Speed > 0f) - { - float3 moveDirection = math.normalizesafe(forward, new float3(0f, 0f, 1f)); - velocity = moveDirection * input.Speed; - } - - float3 nextPosition = position + velocity * deltaTime; - float nextAge = math.max(0f, input.Age + deltaTime); - float nextRemainingLifetime = input.RemainingLifetime; - bool shouldExpire = false; - - if (input.LifeTime > 0f) - { - nextRemainingLifetime = math.max(0f, input.LifeTime - nextAge); - shouldExpire = nextAge >= input.LifeTime; - } - else if (input.RemainingLifetime > 0f) - { - nextRemainingLifetime = math.max(0f, input.RemainingLifetime - deltaTime); - shouldExpire = nextRemainingLifetime <= float.Epsilon; - } - - if (!shouldExpire && maxSqrDistanceFromPlayer > 0f) - { - float3 horizontalDelta = - new float3(nextPosition.x - playerPosition.x, 0f, nextPosition.z - playerPosition.z); - shouldExpire = math.lengthsq(horizontalDelta) > maxSqrDistanceFromPlayer; - } - - if (!shouldExpire && maxVerticalOffsetFromPlayer > 0f) - { - shouldExpire = math.abs(nextPosition.y - playerPosition.y) > maxVerticalOffsetFromPlayer; - } - - output.Position = nextPosition; - output.Velocity = velocity; - output.Age = nextAge; - output.RemainingLifetime = nextRemainingLifetime; - output.Active = !shouldExpire; - output.State = shouldExpire ? ProjectileStateExpired : ProjectileStateActive; - - if (math.lengthsq(velocity) > float.Epsilon) - { - float3 moveForward = math.normalizesafe(velocity, forward); - output.Forward = moveForward; - } - - outputs[index] = output; - } - } -} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileMovementBurstJob.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileMovementBurstJob.cs.meta deleted file mode 100644 index 88c2bb9..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/ProjectileMovementBurstJob.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 49001e849c1b4afdaa37d851b9b76866 -timeCreated: 1771901884 \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs deleted file mode 100644 index 9cfa370..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; - -namespace SepCore.Simulation -{ - public sealed partial class SimulationWorld - { - [BurstCompile] - private struct QueryCollisionCandidatesBurstJob : IJobParallelFor - { - [ReadOnly] public NativeArray Queries; - [ReadOnly] public NativeParallelMultiHashMap EnemyBuckets; - [ReadOnly] public NativeArray EnemyOutputs; - public NativeList.ParallelWriter Candidates; - public bool HasEnemyTargets; - public bool HasPlayerTarget; - public int PlayerTargetEntityId; - public float3 PlayerPosition; - public float CellSize; - - public void Execute(int index) - { - CollisionQueryData query = Queries[index]; - int centerCellX = (int)math.floor(query.Position.x / CellSize); - int centerCellZ = (int)math.floor(query.Position.z / CellSize); - int queryRange = math.max(1, (int)math.ceil(query.Radius / CellSize)); - int selectedCount = 0; - bool reachedLimit = false; - - if (HasPlayerTarget && query.SourceEntityId != PlayerTargetEntityId && - query.SourceOwnerEntityId != PlayerTargetEntityId) - { - float3 playerPosition = PlayerPosition; - if (query.SourceType == CollisionSourceTypeArea) - { - playerPosition.y = query.Position.y; - } - float3 playerDelta = playerPosition - query.Position; - float playerSqrDistance = math.lengthsq(playerDelta); - float playerRadiusSqr = query.Radius * query.Radius; - if (playerSqrDistance <= playerRadiusSqr) - { - Candidates.AddNoResize(new CollisionCandidateData - { - QueryId = query.QueryId, - SourceType = query.SourceType, - SourceEntityId = query.SourceEntityId, - SourceOwnerEntityId = query.SourceOwnerEntityId, - TargetEntityId = PlayerTargetEntityId, - SqrDistance = playerSqrDistance - }); - - selectedCount++; - if (selectedCount >= query.MaxTargets) - { - reachedLimit = true; - } - } - } - - if (!HasEnemyTargets || reachedLimit) - { - return; - } - - for (int dx = -queryRange; dx <= queryRange && !reachedLimit; dx++) - { - for (int dz = -queryRange; dz <= queryRange && !reachedLimit; dz++) - { - long key = SeparationCellKey(centerCellX + dx, centerCellZ + dz); - if (!EnemyBuckets.TryGetFirstValue(key, out int enemyIndex, - out NativeParallelMultiHashMapIterator iterator)) - { - continue; - } - - do - { - if (enemyIndex < 0 || enemyIndex >= EnemyOutputs.Length) - { - continue; - } - - EnemyJobOutputData enemy = EnemyOutputs[enemyIndex]; - if (enemy.EntityId == query.SourceOwnerEntityId) - { - continue; - } - - float deltaY = query.SourceType == CollisionSourceTypeArea - ? 0f - : enemy.Position.y - query.Position.y; - float3 delta = new float3( - enemy.Position.x - query.Position.x, - deltaY, - enemy.Position.z - query.Position.z); - float sqrDistance = math.lengthsq(delta); - float targetRadius = query.SourceType == CollisionSourceTypeArea - ? math.max(0f, enemy.EnemyBodyRadius) - : 0f; - float effectiveRadius = query.Radius + targetRadius; - if (sqrDistance > effectiveRadius * effectiveRadius) - { - continue; - } - - Candidates.AddNoResize(new CollisionCandidateData - { - QueryId = query.QueryId, - SourceType = query.SourceType, - SourceEntityId = query.SourceEntityId, - SourceOwnerEntityId = query.SourceOwnerEntityId, - TargetEntityId = enemy.EntityId, - SqrDistance = sqrDistance - }); - - selectedCount++; - - if (selectedCount >= query.MaxTargets) - { - reachedLimit = true; - break; - } - } while (EnemyBuckets.TryGetNextValue(out enemyIndex, ref iterator)); - } - } - } - } - } -} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs.meta deleted file mode 100644 index aea157e..0000000 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/JobStruct/QueryCollisionCandidatesBurstJob.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 81aa43bb35f547c48b867eafa17a3857 -timeCreated: 1771914779 \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs index 3977adb..6b5eb26 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionBroadPhase.cs @@ -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 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; @@ -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++; } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionRequests.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionRequests.cs index dd1e86b..612aac3 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionRequests.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionRequests.cs @@ -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, diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionResolve.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionResolve.cs index 0542db5..64ef671 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionResolve.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.CollisionResolve.cs @@ -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(); @@ -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; } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.EnemyJobs.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.EnemyJobs.cs index a396aa6..a7dc2f8 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.EnemyJobs.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.EnemyJobs.cs @@ -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 inputArray = _enemyJobInputs.AsArray(); - NativeArray 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(); + 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 inputArray = _enemyJobOutputs.AsArray(); - NativeArray separatedOutputArray = _enemyJobSeparationOutputs.AsArray(); - NativeArray previousPushes = _enemySeparationPreviousPushes.AsArray(); - NativeArray 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; - } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.ProjectileJobs.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.ProjectileJobs.cs index 97a000c..94d35a4 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.ProjectileJobs.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Jobs/SimulationWorld.ProjectileJobs.cs @@ -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 inputArray = _projectileJobInputs.AsArray(); - NativeArray 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 } } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Presentation/SimulationWorld.TransformSync.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Presentation/SimulationWorld.TransformSync.cs index 472f75a..057b615 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Presentation/SimulationWorld.TransformSync.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Presentation/SimulationWorld.TransformSync.cs @@ -32,40 +32,53 @@ namespace SepCore.Simulation continue; } - if (!_world.TryGetEnemyData(enemyEntity.Id, out EnemySimData enemyData)) + if (!_world._enemies.TryGetSimulationIndex(enemyEntity.Id, out int simulationIndex)) { continue; } - ApplyEnemyPresentation(enemyEntity, enemyData); + EnemyTransformSim enemyData = _world._enemies.Transform[simulationIndex]; + ApplyEnemyPresentation(enemyEntity, in enemyData); } } var projectiles = _world._projectiles; for (int i = 0; i < projectiles.Count; i++) { - ProjectileSimData projectileData = projectiles[i]; - if (!projectileData.Active || projectileData.State != ProjectileStateActive) + if (!projectiles.Life[i].Active) { continue; } - EntityBase projectileEntity = TryGetEntityById(projectileData.EntityId); + EntityBase projectileEntity = TryGetEntityById(projectiles.Identity[i].EntityId); if (projectileEntity == null || !projectileEntity.Available) { continue; } - ApplyProjectilePresentation(projectileEntity, projectileData); + ProjectileTransformSim projectileData = projectiles.Transform[i]; + ApplyProjectilePresentation(projectileEntity, in projectileData); + } + + var pickups = _world._pickups; + for (int i = 0; i < pickups.Count; i++) + { + EntityBase pickupEntity = TryGetEntityById(pickups.Identity[i].EntityId); + if (pickupEntity == null || !pickupEntity.Available) + { + continue; + } + + pickupEntity.CachedTransform.position = ToVector3(pickups.Transform[i].Position); } } - private static void ApplyEnemyPresentation(EnemyBase enemyEntity, in EnemySimData enemyData) + private static void ApplyEnemyPresentation(EnemyBase enemyEntity, in EnemyTransformSim enemyData) { Transform enemyTransform = enemyEntity.CachedTransform; - enemyTransform.position = enemyData.Position; + enemyTransform.position = ToVector3(enemyData.Position); - Quaternion rotation = enemyData.Rotation; + Quaternion rotation = ToQuaternion(enemyData.Rotation); float rotationMagnitude = Mathf.Abs(rotation.x) + Mathf.Abs(rotation.y) + Mathf.Abs(rotation.z) + Mathf.Abs(rotation.w); if (rotationMagnitude > float.Epsilon) @@ -74,7 +87,7 @@ namespace SepCore.Simulation return; } - Vector3 forward = enemyData.Forward; + Vector3 forward = ToVector3(enemyData.Forward); forward.y = 0f; if (forward.sqrMagnitude > float.Epsilon) { @@ -83,7 +96,7 @@ namespace SepCore.Simulation } private static void ApplyProjectilePresentation(EntityBase projectileEntity, - in ProjectileSimData projectileData) + in ProjectileTransformSim projectileData) { Transform projectileTransform = projectileEntity.CachedTransform; if (projectileTransform == null) @@ -91,8 +104,8 @@ namespace SepCore.Simulation return; } - projectileTransform.position = projectileData.Position; - Vector3 forward = projectileData.Forward; + projectileTransform.position = ToVector3(projectileData.Position); + Vector3 forward = ToVector3(projectileData.Forward); if (forward.sqrMagnitude <= float.Epsilon) { return; diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimData/EnemySimData.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimData/EnemySimData.cs index 87854ce..29a408c 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimData/EnemySimData.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimData/EnemySimData.cs @@ -13,7 +13,6 @@ namespace SepCore.Simulation public bool AvoidEnemyOverlap; public float EnemyBodyRadius; public int SeparationIterations; - public int TargetType; public int State; } } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimData/ProjectileSimData.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimData/ProjectileSimData.cs index 8dc9f0a..505fbbb 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimData/ProjectileSimData.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimData/ProjectileSimData.cs @@ -9,11 +9,8 @@ namespace SepCore.Simulation public Vector3 Position; public Vector3 Forward; public Vector3 Velocity; - public float Speed; public float LifeTime; public float Age; public bool Active; - public float RemainingLifetime; - public int State; } } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCell.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCell.cs new file mode 100644 index 0000000..3a117fa --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCell.cs @@ -0,0 +1,17 @@ +using Unity.Mathematics; + +namespace SepCore.Simulation +{ + internal static class SimulationCell + { + public static long Key(int x, int z) + { + return ((long)x << 32) ^ (uint)z; + } + + public static int ToCell(float value, float cellSize) + { + return (int)math.floor(value / cellSize); + } + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCell.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCell.cs.meta new file mode 100644 index 0000000..82326b6 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCell.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2908180e0131e63428d1bfdaed65e1ce +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCollision.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCollision.cs new file mode 100644 index 0000000..e502425 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCollision.cs @@ -0,0 +1,11 @@ +namespace SepCore.Simulation +{ + internal static class SimulationCollision + { + public const int SourceTypeProjectile = 1; + public const int SourceTypeArea = 2; + public const int ShapeCircle = 0; + public const int ShapeSector = 1; + public const int ShapeRectangle = 2; + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCollision.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCollision.cs.meta new file mode 100644 index 0000000..7ea415c --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationCollision.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c39f2a2274ea06347970351f25e70c28 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationStats.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationStats.cs new file mode 100644 index 0000000..f59a022 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationStats.cs @@ -0,0 +1,36 @@ +namespace SepCore.Simulation +{ + public readonly struct SimulationStats + { + public SimulationStats(int collisionCandidateCount, int pendingAreaCollisionRequestCount, + int lastCollisionQueryCount, int lastProjectileCollisionQueryCount, int lastAreaCollisionQueryCount, + int lastCollisionCandidateCount, int lastProjectileCollisionCandidateCount, + int lastAreaCollisionCandidateCount, int lastResolvedAreaHitCount, float lastCollisionCellSize, + bool lastCollisionHasEnemyTargets) + { + CollisionCandidateCount = collisionCandidateCount; + PendingAreaCollisionRequestCount = pendingAreaCollisionRequestCount; + LastCollisionQueryCount = lastCollisionQueryCount; + LastProjectileCollisionQueryCount = lastProjectileCollisionQueryCount; + LastAreaCollisionQueryCount = lastAreaCollisionQueryCount; + LastCollisionCandidateCount = lastCollisionCandidateCount; + LastProjectileCollisionCandidateCount = lastProjectileCollisionCandidateCount; + LastAreaCollisionCandidateCount = lastAreaCollisionCandidateCount; + LastResolvedAreaHitCount = lastResolvedAreaHitCount; + LastCollisionCellSize = lastCollisionCellSize; + LastCollisionHasEnemyTargets = lastCollisionHasEnemyTargets; + } + + public int CollisionCandidateCount { get; } + public int PendingAreaCollisionRequestCount { get; } + public int LastCollisionQueryCount { get; } + public int LastProjectileCollisionQueryCount { get; } + public int LastAreaCollisionQueryCount { get; } + public int LastCollisionCandidateCount { get; } + public int LastProjectileCollisionCandidateCount { get; } + public int LastAreaCollisionCandidateCount { get; } + public int LastResolvedAreaHitCount { get; } + public float LastCollisionCellSize { get; } + public bool LastCollisionHasEnemyTargets { get; } + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationStats.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationStats.cs.meta new file mode 100644 index 0000000..f1c2e41 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationStats.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 69cb78d487cd9f54d979790a9b24ee39 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationTickContext.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationTickContext.cs index 1a2c9d0..14e9007 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationTickContext.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationTickContext.cs @@ -4,15 +4,13 @@ namespace SepCore.Simulation { public readonly struct SimulationTickContext { - public SimulationTickContext(float deltaTime, float realDeltaTime, Vector3 playerPosition) + public SimulationTickContext(float deltaTime, Vector3 playerPosition) { DeltaTime = deltaTime; - RealDeltaTime = realDeltaTime; PlayerPosition = playerPosition; } public float DeltaTime { get; } - public float RealDeltaTime { get; } public Vector3 PlayerPosition { get; } } } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.EntityToSimData.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.EntityToSimData.cs index a80feb1..44ac4e9 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.EntityToSimData.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.EntityToSimData.cs @@ -1,5 +1,6 @@ using SepCore.Components; using SepCore.Entity; +using Unity.Mathematics; using UnityEngine; namespace SepCore.Simulation @@ -25,7 +26,7 @@ namespace SepCore.Simulation float attackRange = enemy.AttackRange > 0f ? enemy.AttackRange - : DefaultAttackRange; + : EnemyAttackSim.DefaultAttackRange; return new EnemySimData { @@ -38,8 +39,7 @@ namespace SepCore.Simulation AvoidEnemyOverlap = movementComponent != null && movementComponent.AvoidEnemyOverlap, EnemyBodyRadius = movementComponent != null ? movementComponent.EnemyBodyRadius : 0.45f, SeparationIterations = movementComponent != null ? movementComponent.SeparationIterations : 2, - TargetType = 0, - State = EnemyStateIdle + State = EnemyBehaviorSim.StateIdle }; } @@ -83,12 +83,9 @@ namespace SepCore.Simulation Position = projectileEntity.CachedTransform.position, Forward = forward, Velocity = velocity, - Speed = speed, LifeTime = lifeTime, Age = 0f, - Active = true, - RemainingLifetime = lifeTime, - State = ProjectileStateActive + Active = true }; } @@ -98,11 +95,34 @@ namespace SepCore.Simulation { EntityId = pickupEntity.Id, Position = pickupEntity.CachedTransform.position, - PickupRadius = 0.35f, - State = 0 + PickupRadius = 0.35f }; } #endregion + + #region Component Conversion + + private static float3 ToFloat3(Vector3 value) + { + return new float3(value.x, value.y, value.z); + } + + private static Vector3 ToVector3(float3 value) + { + return new Vector3(value.x, value.y, value.z); + } + + private static quaternion ToQuaternion(Quaternion value) + { + return new quaternion(value.x, value.y, value.z, value.w); + } + + private static Quaternion ToQuaternion(quaternion value) + { + return new Quaternion(value.value.x, value.value.y, value.value.z, value.value.w); + } + + #endregion } } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.PlayerMovement.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.PlayerMovement.cs index 2d05a71..566cda3 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.PlayerMovement.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.PlayerMovement.cs @@ -50,21 +50,21 @@ namespace SepCore.Simulation ClearPlayerMovementState(); } - private Vector3 ResolvePlayerPositionForTick(in SimulationTickContext context) + private Vector3 ResolvePlayerPositionForTick(Vector3 fallbackPlayerPosition, float deltaTime) { if (_playerMovementTransform == null) { - return context.PlayerPosition; + return fallbackPlayerPosition; } if (!_playerMovementActive || _playerMovementSpeed <= 0f || - _playerMovementDirection.sqrMagnitude <= Mathf.Epsilon || context.DeltaTime <= 0f) + _playerMovementDirection.sqrMagnitude <= Mathf.Epsilon || deltaTime <= 0f) { _playerMovementPosition = _playerMovementTransform.position; return _playerMovementPosition; } - _playerMovementPosition += _playerMovementDirection * (_playerMovementSpeed * context.DeltaTime); + _playerMovementPosition += _playerMovementDirection * (_playerMovementSpeed * deltaTime); _playerMovementPosition.y = _playerMovementTransform.position.y; _playerMovementTransform.position = _playerMovementPosition; return _playerMovementPosition; diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.RuntimeModules.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.RuntimeModules.cs index 8900210..846044c 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.RuntimeModules.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.RuntimeModules.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using Unity.Collections; using Unity.Jobs; -using Unity.Mathematics; using UnityEngine; namespace SepCore.Simulation @@ -17,23 +16,14 @@ namespace SepCore.Simulation 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 EnemyStore _enemies => _simulationState.Enemies; + private ProjectileStore _projectiles => _simulationState.Projectiles; + private PickupStore _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 _enemySeparationTransforms => ref _jobDataRuntime.EnemySeparationTransforms; + private ref NativeList _collectedPickups => ref _jobDataRuntime.CollectedPickups; private ref NativeList _collisionCandidates => ref _jobDataRuntime.CollisionCandidates; private ref NativeParallelMultiHashMap _enemySeparationBuckets => ref _jobDataRuntime.EnemySeparationBuckets; private ref NativeParallelMultiHashMap _enemyCollisionBuckets => ref _jobDataRuntime.EnemyCollisionBuckets; @@ -101,24 +91,16 @@ namespace SepCore.Simulation private sealed class SimulationStateStore { - public readonly List Enemies = new(); - public readonly List Projectiles = new(); - public readonly List Pickups = new(); + public readonly EnemyStore Enemies = new(); + public readonly ProjectileStore Projectiles = new(); + public readonly PickupStore 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 EnemySeparationTransforms; + public NativeList CollectedPickups; public NativeList CollisionCandidates; public NativeParallelMultiHashMap EnemySeparationBuckets; public NativeParallelMultiHashMap EnemyCollisionBuckets; diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.SimEntityState.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.SimEntityState.cs index ee62e65..30f121e 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.SimEntityState.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.SimEntityState.cs @@ -1,4 +1,5 @@ using SepCore.Entity; +using UnityEngine; namespace SepCore.Simulation { @@ -16,11 +17,8 @@ namespace SepCore.Simulation _areaCollisionRequests.Clear(); _areaCollisionHitEvents.Clear(); _areaCollisionHitDedupKeys.Clear(); + _fixedStepAccumulator = 0d; ClearJobDataChannels(); - - EnemyBinding.Clear(); - ProjectileBinding.Clear(); - PickupBinding.Clear(); ClearPlayerMovementState(); } @@ -28,46 +26,96 @@ namespace SepCore.Simulation #region Enemy Simulation State - private int AddEnemy(in EnemySimData simData) - { - int simulationIndex = _enemies.Count; - _enemies.Add(simData); - EnemyBinding.Bind(simData.EntityId, simulationIndex); - OnEnemyAddedToSeparationTemporalBuffers(); - MarkEnemyTargetSpatialIndexDirty(); - return simulationIndex; - } + public int EnemyCount => _enemies.Count; - private int UpsertEnemy(in EnemySimData simData) + public bool TryGetEnemySnapshot(int entityId, out EnemySimData snapshot) { - if (!EnemyBinding.TryGetSimulationIndex(simData.EntityId, out int simulationIndex)) + if (!_enemies.TryGetSimulationIndex(entityId, out int index)) { - return AddEnemy(simData); + snapshot = default; + return false; } - _enemies[simulationIndex] = simData; + EnemyTransformSim transform = _enemies.Transform[index]; + EnemyMoveInputSim moveInput = _enemies.MoveInput[index]; + EnemyMoveConfigSim moveConfig = _enemies.MoveConfig[index]; + EnemyAttackSim attack = _enemies.Attack[index]; + EnemyBehaviorSim behavior = _enemies.Behavior[index]; + snapshot = new EnemySimData + { + EntityId = entityId, + Position = ToVector3(transform.Position), + Forward = ToVector3(transform.Forward), + Rotation = ToQuaternion(transform.Rotation), + Speed = moveInput.Speed, + AttackRange = attack.AttackRange, + AvoidEnemyOverlap = moveConfig.AvoidOverlap, + EnemyBodyRadius = moveConfig.BodyRadius, + SeparationIterations = moveConfig.SeparationIterations, + State = behavior.State + }; + return true; + } + + private int AddEnemy(in EnemySimData simData) + { + InitializeJobDataChannels(); + + int index = _enemies.Add( + new EnemyIdentitySim { EntityId = simData.EntityId }, + new EnemyTransformSim + { + Position = ToFloat3(simData.Position), + Forward = ToFloat3(simData.Forward), + Rotation = ToQuaternion(simData.Rotation) + }, + new EnemyMoveInputSim { Speed = simData.Speed }, + new EnemyMoveConfigSim + { + AvoidOverlap = simData.AvoidEnemyOverlap, + BodyRadius = simData.EnemyBodyRadius, + SeparationIterations = simData.SeparationIterations + }, + new EnemyAttackSim { AttackRange = simData.AttackRange }, + new EnemyBehaviorSim { State = simData.State }); MarkEnemyTargetSpatialIndexDirty(); - return simulationIndex; + return index; + } + + internal int UpsertEnemy(in EnemySimData simData) + { + if (!_enemies.TryGetSimulationIndex(simData.EntityId, out int index)) + { + return AddEnemy(in simData); + } + + _enemies.Identity[index] = new EnemyIdentitySim { EntityId = simData.EntityId }; + _enemies.Transform[index] = new EnemyTransformSim + { + Position = ToFloat3(simData.Position), + Forward = ToFloat3(simData.Forward), + Rotation = ToQuaternion(simData.Rotation) + }; + _enemies.MoveInput[index] = new EnemyMoveInputSim { Speed = simData.Speed }; + _enemies.MoveConfig[index] = new EnemyMoveConfigSim + { + AvoidOverlap = simData.AvoidEnemyOverlap, + BodyRadius = simData.EnemyBodyRadius, + SeparationIterations = simData.SeparationIterations + }; + _enemies.Attack[index] = new EnemyAttackSim { AttackRange = simData.AttackRange }; + _enemies.Behavior[index] = new EnemyBehaviorSim { State = simData.State }; + MarkEnemyTargetSpatialIndexDirty(); + return index; } private bool RemoveEnemyByEntityId(int entityId) { - if (!EnemyBinding.TryGetSimulationIndex(entityId, out int simulationIndex)) + if (!_enemies.RemoveByEntityId(entityId)) { return false; } - int lastIndex = _enemies.Count - 1; - if (simulationIndex != lastIndex) - { - EnemySimData movedData = _enemies[lastIndex]; - _enemies[simulationIndex] = movedData; - EnemyBinding.RemapIndex(movedData.EntityId, simulationIndex); - } - - _enemies.RemoveAt(lastIndex); - OnEnemyRemovedFromSeparationTemporalBuffers(simulationIndex); - EnemyBinding.UnbindByEntityId(entityId); MarkEnemyTargetSpatialIndexDirty(); return true; } @@ -88,85 +136,122 @@ namespace SepCore.Simulation RemoveEnemyByEntityId(entityId); } - private bool TryGetEnemyData(int entityId, out EnemySimData enemyData) - { - if (!EnemyBinding.TryGetSimulationIndex(entityId, out int simulationIndex) || simulationIndex < 0 || - simulationIndex >= _enemies.Count) - { - enemyData = default; - return false; - } - - enemyData = _enemies[simulationIndex]; - return true; - } - - public void SyncEnemyMovementInput(int entityId, bool isMoving, in UnityEngine.Vector3 direction, float speed, + public void SyncEnemyMovementInput(int entityId, bool isMoving, in Vector3 direction, float speed, bool avoidEnemyOverlap, float enemyBodyRadius, int separationIterations) { - if (!EnemyBinding.TryGetSimulationIndex(entityId, out int simulationIndex) || simulationIndex < 0 || - simulationIndex >= _enemies.Count) + if (!_enemies.TryGetSimulationIndex(entityId, out int index)) { return; } - EnemySimData enemyData = _enemies[simulationIndex]; - enemyData.Speed = isMoving ? UnityEngine.Mathf.Max(0f, speed) : 0f; - enemyData.AvoidEnemyOverlap = avoidEnemyOverlap; - enemyData.EnemyBodyRadius = UnityEngine.Mathf.Max(0.01f, enemyBodyRadius); - enemyData.SeparationIterations = UnityEngine.Mathf.Max(1, separationIterations); - - UnityEngine.Vector3 planarDirection = direction; - planarDirection.y = 0f; - if (planarDirection.sqrMagnitude > UnityEngine.Mathf.Epsilon) + _enemies.MoveInput[index] = new EnemyMoveInputSim { - enemyData.Forward = planarDirection.normalized; - } + Speed = isMoving ? Mathf.Max(0f, speed) : 0f + }; + _enemies.MoveConfig[index] = new EnemyMoveConfigSim + { + AvoidOverlap = avoidEnemyOverlap, + BodyRadius = Mathf.Max(0.01f, enemyBodyRadius), + SeparationIterations = Mathf.Max(1, separationIterations) + }; - _enemies[simulationIndex] = enemyData; + Vector3 planarDirection = direction; + planarDirection.y = 0f; + if (planarDirection.sqrMagnitude > Mathf.Epsilon) + { + planarDirection.Normalize(); + EnemyTransformSim transform = _enemies.Transform[index]; + transform.Forward = ToFloat3(planarDirection); + _enemies.Transform[index] = transform; + } } #endregion #region Projectile Simulation State - private int AddProjectile(in ProjectileSimData simData) - { - int simulationIndex = _projectiles.Count; - _projectiles.Add(simData); - ProjectileBinding.Bind(simData.EntityId, simulationIndex); - return simulationIndex; - } + public int ProjectileCount => _projectiles.Count; - private int UpsertProjectile(in ProjectileSimData simData) + public bool TryGetProjectileSnapshot(int entityId, out ProjectileSimData snapshot) { - if (!ProjectileBinding.TryGetSimulationIndex(simData.EntityId, out int simulationIndex)) + if (!_projectiles.TryGetSimulationIndex(entityId, out int index)) { - return AddProjectile(simData); + snapshot = default; + return false; } - _projectiles[simulationIndex] = simData; - return simulationIndex; + ProjectileIdentitySim identity = _projectiles.Identity[index]; + ProjectileTransformSim transform = _projectiles.Transform[index]; + ProjectileVelocitySim velocity = _projectiles.Velocity[index]; + ProjectileLifeSim life = _projectiles.Life[index]; + snapshot = new ProjectileSimData + { + EntityId = identity.EntityId, + OwnerEntityId = identity.OwnerEntityId, + Position = ToVector3(transform.Position), + Forward = ToVector3(transform.Forward), + Velocity = ToVector3(velocity.Velocity), + LifeTime = life.LifeTime, + Age = life.Age, + Active = life.Active + }; + return true; + } + + private int AddProjectile(in ProjectileSimData simData) + { + InitializeJobDataChannels(); + + return _projectiles.Add( + new ProjectileIdentitySim + { + EntityId = simData.EntityId, + OwnerEntityId = simData.OwnerEntityId + }, + new ProjectileTransformSim + { + Position = ToFloat3(simData.Position), + Forward = ToFloat3(simData.Forward) + }, + new ProjectileVelocitySim { Velocity = ToFloat3(simData.Velocity) }, + new ProjectileLifeSim + { + Age = simData.Age, + LifeTime = simData.LifeTime, + Active = simData.Active + }); + } + + internal int UpsertProjectile(in ProjectileSimData simData) + { + if (!_projectiles.TryGetSimulationIndex(simData.EntityId, out int index)) + { + return AddProjectile(in simData); + } + + _projectiles.Identity[index] = new ProjectileIdentitySim + { + EntityId = simData.EntityId, + OwnerEntityId = simData.OwnerEntityId + }; + _projectiles.Transform[index] = new ProjectileTransformSim + { + Position = ToFloat3(simData.Position), + Forward = ToFloat3(simData.Forward) + }; + _projectiles.Velocity[index] = new ProjectileVelocitySim { Velocity = ToFloat3(simData.Velocity) }; + _projectiles.Life[index] = new ProjectileLifeSim + { + Age = simData.Age, + LifeTime = simData.LifeTime, + Active = simData.Active + }; + return index; } private bool RemoveProjectileByEntityId(int entityId) { - if (!ProjectileBinding.TryGetSimulationIndex(entityId, out int simulationIndex)) - { - return false; - } - - int lastIndex = _projectiles.Count - 1; - if (simulationIndex != lastIndex) - { - ProjectileSimData movedData = _projectiles[lastIndex]; - _projectiles[simulationIndex] = movedData; - ProjectileBinding.RemapIndex(movedData.EntityId, simulationIndex); - } - - _projectiles.RemoveAt(lastIndex); - ProjectileBinding.UnbindByEntityId(entityId); - return true; + return _projectiles.RemoveByEntityId(entityId); } private void RegisterProjectileLifecycle(EntityBase projectileEntity, object userData) @@ -188,43 +273,66 @@ namespace SepCore.Simulation #region Pickup Simulation State - private int AddPickup(in PickupSimData simData) - { - int simulationIndex = _pickups.Count; - _pickups.Add(simData); - PickupBinding.Bind(simData.EntityId, simulationIndex); - return simulationIndex; - } + public int PickupCount => _pickups.Count; - private int UpsertPickup(in PickupSimData simData) + public bool TryGetPickupSnapshot(int entityId, out PickupSimData snapshot) { - if (!PickupBinding.TryGetSimulationIndex(simData.EntityId, out int simulationIndex)) + if (!_pickups.TryGetSimulationIndex(entityId, out int index)) { - return AddPickup(simData); + snapshot = default; + return false; } - _pickups[simulationIndex] = simData; - return simulationIndex; + PickupTransformSim transform = _pickups.Transform[index]; + PickupRadiusSim radius = _pickups.Radius[index]; + PickupStateSim state = _pickups.State[index]; + snapshot = new PickupSimData + { + EntityId = entityId, + Position = ToVector3(transform.Position), + PickupRadius = radius.Radius, + State = state.State + }; + return true; + } + + private int AddPickup(in PickupSimData simData) + { + InitializeJobDataChannels(); + + return _pickups.Add( + new PickupIdentitySim { EntityId = simData.EntityId }, + new PickupTransformSim { Position = ToFloat3(simData.Position) }, + new PickupRadiusSim { Radius = simData.PickupRadius }, + new PickupStateSim { State = simData.State }); + } + + internal int UpsertPickup(in PickupSimData simData) + { + if (!_pickups.TryGetSimulationIndex(simData.EntityId, out int index)) + { + return AddPickup(in simData); + } + + _pickups.Identity[index] = new PickupIdentitySim { EntityId = simData.EntityId }; + _pickups.Transform[index] = new PickupTransformSim { Position = ToFloat3(simData.Position) }; + _pickups.Radius[index] = new PickupRadiusSim { Radius = simData.PickupRadius }; + _pickups.State[index] = new PickupStateSim { State = simData.State }; + return index; + } + + public void TriggerFullMapMagnet() + { + int pickupCount = _pickups.Count; + for (int i = 0; i < pickupCount; i++) + { + _pickups.State[i] = new PickupStateSim { State = PickupStateSim.StateAbsorbing }; + } } private bool RemovePickupByEntityId(int entityId) { - if (!PickupBinding.TryGetSimulationIndex(entityId, out int simulationIndex)) - { - return false; - } - - int lastIndex = _pickups.Count - 1; - if (simulationIndex != lastIndex) - { - PickupSimData movedData = _pickups[lastIndex]; - _pickups[simulationIndex] = movedData; - PickupBinding.RemapIndex(movedData.EntityId, simulationIndex); - } - - _pickups.RemoveAt(lastIndex); - PickupBinding.UnbindByEntityId(entityId); - return true; + return _pickups.RemoveByEntityId(entityId); } private void RegisterPickupLifecycle(EntityBase pickupEntity) diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.TargetSelectionSpatialIndex.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.TargetSelectionSpatialIndex.cs index 8b525be..c208474 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.TargetSelectionSpatialIndex.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.TargetSelectionSpatialIndex.cs @@ -1,5 +1,6 @@ using SepCore.Debugger; using Unity.Collections; +using Unity.Mathematics; using UnityEngine; namespace SepCore.Simulation @@ -17,8 +18,8 @@ namespace SepCore.Simulation BuildEnemyTargetSpatialIndexIfNeeded(); float cellSize = GetTargetSelectionCellSize(); - int centerCellX = ToCell(origin.x, cellSize); - int centerCellZ = ToCell(origin.z, cellSize); + int centerCellX = SimulationCell.ToCell(origin.x, cellSize); + int centerCellZ = SimulationCell.ToCell(origin.z, cellSize); float range = Mathf.Sqrt(maxSqrRange); int queryRange = Mathf.Max(1, Mathf.CeilToInt(range / cellSize)); @@ -31,7 +32,7 @@ namespace SepCore.Simulation { for (int dz = -queryRange; dz <= queryRange; dz++) { - long key = CellKey(centerCellX + dx, centerCellZ + dz); + long key = SimulationCell.Key(centerCellX + dx, centerCellZ + dz); if (!_enemyTargetBuckets.TryGetFirstValue(key, out int enemyIndex, out NativeParallelMultiHashMapIterator iterator)) { @@ -45,17 +46,17 @@ namespace SepCore.Simulation continue; } - EnemySimData enemy = _enemies[enemyIndex]; - Vector3 delta = enemy.Position - origin; - delta.y = 0f; - float sqrDistance = delta.sqrMagnitude; + float3 enemyPosition = _enemies.Transform[enemyIndex].Position; + float deltaX = enemyPosition.x - origin.x; + float deltaZ = enemyPosition.z - origin.z; + float sqrDistance = deltaX * deltaX + deltaZ * deltaZ; if (sqrDistance >= minSqrDistance) { continue; } minSqrDistance = sqrDistance; - enemyEntityId = enemy.EntityId; + enemyEntityId = _enemies.Identity[enemyIndex].EntityId; found = true; } while (_enemyTargetBuckets.TryGetNextValue(out enemyIndex, ref iterator)); } @@ -125,10 +126,10 @@ namespace SepCore.Simulation for (int i = 0; i < enemyCount; i++) { - EnemySimData enemy = _enemies[i]; - int cellX = ToCell(enemy.Position.x, cellSize); - int cellZ = ToCell(enemy.Position.z, cellSize); - _enemyTargetBuckets.Add(CellKey(cellX, cellZ), i); + float3 enemyPosition = _enemies.Transform[i].Position; + int cellX = SimulationCell.ToCell(enemyPosition.x, cellSize); + int cellZ = SimulationCell.ToCell(enemyPosition.z, cellSize); + _enemyTargetBuckets.Add(SimulationCell.Key(cellX, cellZ), i); } } @@ -139,15 +140,5 @@ namespace SepCore.Simulation { return Mathf.Max(0.1f, _targetSelectionCellSize); } - - private static int ToCell(float value, float cellSize) - { - return Mathf.FloorToInt(value / cellSize); - } - - private static long CellKey(int x, int z) - { - return ((long)x << 32) ^ (uint)z; - } } } diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.cs index 33a48db..cbef637 100644 --- a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.cs +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/SimulationWorld.cs @@ -1,50 +1,45 @@ -using System.Collections.Generic; -using SepCore.Debugger; +using SepCore.Debugger; using UnityEngine; using UnityGameFramework.Runtime; namespace SepCore.Simulation { - public sealed partial class SimulationWorld : GameFrameworkComponent + public sealed partial class SimulationWorld : GameFrameworkComponent, ISimulationWorld { // 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 鍚屾妗ャ€? - // - Presentation/SimulationWorld.HitPresentation.cs: 鎶曞皠鐗╁懡涓簨浠惰〃鐜版ˉ銆? - // - DataChannel/SimulationWorld.JobDataChannel.cs: Job 閫氶亾鍏变韩瀛楁銆佸父閲忓拰杩愯鏃剁姸鎬併€? - // - DataChannel/SimulationWorld.JobDataLifecycle.cs: Native 閫氶亾鍒濆鍖栥€佹竻鐞嗗拰 clear銆? - // - DataChannel/SimulationWorld.JobDataConversion.cs: sim/job 鏁版嵁杞崲涓庤緭鍏ヨ緭鍑虹紦鍐插噯澶囥€? - // - DataChannel/SimulationWorld.CollisionTransient.cs: 纰版挒涓存椂閫氶亾鍜岃繍琛屾椂缁熻銆? - // - DataChannel/SimulationWorld.EnemySeparationTemporal.cs: 鏁屼汉鍒嗙鐨勫抚闂翠复鏃剁姸鎬併€? - // - DataChannel/SimulationWorld.JobOutputCommit.cs: Job 杈撳嚭鍥炲啓涓诲鍣ㄣ€? - // - Jobs/SimulationWorld.EnemyJobs.cs: 妯℃嫙閫氶亾 缂栨帓 + 鏁屼汉绉诲姩/鍒嗙 椤哄簭鎵ц - // - Jobs/SimulationWorld.ProjectileJobs.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 瑙f瀽 - // - JobStruct/*.cs: burst job 鍐呮牳鍜岄潰鍚?job 鐨勬暟鎹粨鏋? - private const float DefaultAttackRange = 1f; - private const int EnemyStateIdle = 0; - private const int EnemyStateChasing = 1; - private const int EnemyStateInAttackRange = 2; - private const int ProjectileStateActive = 0; - private const int ProjectileStateExpired = 1; + // - SimulationWorld.cs: 核心状态、常量和 Unity 生命周期入口点。 + // - SimulationWorld.RuntimeModules.cs: 运行时域对象、配置和状态代理。 + // - SimulationWorld.SimEntityState.cs: 模拟状态的增删改查和生命周期注册。 + // - SimulationWorld.EntityToSimData.cs: Unity 实体与组件列/快照之间的边界转换。 + // - SimulationWorld.EntitySync.cs: GameFramework 实体 show/hide 事件桥。 + // - SimulationWorld.TargetSelectionSpatialIndex.cs: 最近敌空间索引查询。 + // - SimulationWorld.PlayerMovement.cs: 玩家模拟移动输入与步进。 + // - Components/*: 敌人/投射物/掉落物 SoA 组件列与存储。 + // - Presentation/SimulationWorld.TransformSync.cs: late-update transform 同步桥。 + // - Presentation/SimulationWorld.HitPresentation.cs: 投射物命中事件表现桥。 + // - DataChannel/*: Job 通道与帧间临时状态。 + // - Systems/*: 独立数据并行系统(移动/分离/碰撞查询)。 + // - Jobs/*: 模拟管线编排、碰撞调度与结算。 + // - JobStruct/*: 面向 job 的瞬时数据结构。 + // 模拟数据以组件列(SoA)为唯一存储,XxxSimData 只作为生成/查询边界的快照结构。 + private const float MinFixedStepSeconds = 0.0001f; + private const double FixedStepEpsilonScale = 1e-4; + + [Header("固定步长模拟")] + [Tooltip("开启后按固定步长累积时间,一帧内可能执行多次模拟步进。")] + [SerializeField] + private bool _fixedStepEnabled = true; + + [Tooltip("固定步长(秒)。")] + [SerializeField] + private float _fixedStepSeconds = 1f / 60f; + + private double _fixedStepAccumulator; private EntitySync _entitySync; private TransformSync _transformSync; private HitPresentation _hitPresentation; - public IReadOnlyList Enemies => _enemies; - public IReadOnlyList Projectiles => _projectiles; - public IReadOnlyList Pickups => _pickups; - #region Lifecycle protected override void Awake() @@ -64,9 +59,30 @@ namespace SepCore.Simulation public void Tick(in SimulationTickContext context) { - Vector3 playerPosition = ResolvePlayerPositionForTick(in context); - SimulationTickContext resolvedContext = - new SimulationTickContext(context.DeltaTime, context.RealDeltaTime, playerPosition); + if (!_fixedStepEnabled) + { + TickSimulationStep(context.PlayerPosition, context.DeltaTime); + return; + } + + double fixedStep = Mathf.Max(MinFixedStepSeconds, _fixedStepSeconds); + double stepEpsilon = fixedStep * FixedStepEpsilonScale; + _fixedStepAccumulator += context.DeltaTime; + while (_fixedStepAccumulator + stepEpsilon >= fixedStep) + { + TickSimulationStep(context.PlayerPosition, (float)fixedStep); + _fixedStepAccumulator -= fixedStep; + if (_fixedStepAccumulator < 0d) + { + _fixedStepAccumulator = 0d; + } + } + } + + private void TickSimulationStep(Vector3 fallbackPlayerPosition, float deltaTime) + { + Vector3 playerPosition = ResolvePlayerPositionForTick(fallbackPlayerPosition, deltaTime); + SimulationTickContext resolvedContext = new SimulationTickContext(deltaTime, playerPosition); using (CustomProfilerMarker.TickEnemies.Auto()) { TickSimulationPipeline(in resolvedContext); @@ -91,4 +107,3 @@ namespace SepCore.Simulation #endregion } } - diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems.meta new file mode 100644 index 0000000..7715d1f --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 57a5e62d1ac18754e98d0ebf2f2c12c8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/CollisionQuerySystem.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/CollisionQuerySystem.cs new file mode 100644 index 0000000..ccb65ef --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/CollisionQuerySystem.cs @@ -0,0 +1,215 @@ +using SepCore.Debugger; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; + +namespace SepCore.Simulation +{ + internal static class CollisionQuerySystem + { + // 读写集:R EnemyTransform / EnemyMoveConfig / EnemyIdentity / CollisionQueryData(瞬时) + // W CollisionCandidateData(瞬时) / 敌人碰撞分桶 + public static void BuildEnemyCollisionBuckets(EnemyStore enemies, + NativeParallelMultiHashMap buckets, float cellSize) + { + buckets.Clear(); + int enemyCount = enemies.Count; + if (enemyCount <= 0) + { + return; + } + + BuildCollisionBucketsJob job = new BuildCollisionBucketsJob + { + Transforms = enemies.Transform.Data.AsArray(), + Buckets = buckets.AsParallelWriter(), + CellSize = cellSize + }; + + using (CustomProfilerMarker.TickEnemies_Complete.Auto()) + { + job.Schedule(enemyCount, 64).Complete(); + } + } + + public static bool ScheduleCandidateQuery(NativeList queries, + NativeList candidates, NativeParallelMultiHashMap enemyBuckets, + EnemyStore enemies, bool hasEnemyTargets, bool hasPlayerTarget, int playerTargetEntityId, + float3 playerPosition, float cellSize, out JobHandle handle) + { + handle = default; + if (!queries.IsCreated || !candidates.IsCreated) + { + return false; + } + + candidates.Clear(); + int queryCount = queries.Length; + if (queryCount == 0) + { + return false; + } + + QueryCollisionCandidatesJob job = new QueryCollisionCandidatesJob + { + Queries = queries.AsArray(), + EnemyBuckets = enemyBuckets, + EnemyTransforms = enemies.Transform.Data.AsArray(), + EnemyConfigs = enemies.MoveConfig.Data.AsArray(), + EnemyIdentities = enemies.Identity.Data.AsArray(), + Candidates = candidates.AsParallelWriter(), + HasEnemyTargets = hasEnemyTargets, + HasPlayerTarget = hasPlayerTarget, + PlayerTargetEntityId = playerTargetEntityId, + PlayerPosition = playerPosition, + CellSize = cellSize + }; + + handle = job.Schedule(queryCount, 64); + return true; + } + } + + [BurstCompile] + internal struct BuildCollisionBucketsJob : IJobParallelFor + { + [ReadOnly] public NativeArray Transforms; + public NativeParallelMultiHashMap.ParallelWriter Buckets; + public float CellSize; + + public void Execute(int index) + { + float3 position = Transforms[index].Position; + int cellX = SimulationCell.ToCell(position.x, CellSize); + int cellZ = SimulationCell.ToCell(position.z, CellSize); + Buckets.Add(SimulationCell.Key(cellX, cellZ), index); + } + } + + [BurstCompile] + internal struct QueryCollisionCandidatesJob : IJobParallelFor + { + [ReadOnly] public NativeArray Queries; + [ReadOnly] public NativeParallelMultiHashMap EnemyBuckets; + [ReadOnly] public NativeArray EnemyTransforms; + [ReadOnly] public NativeArray EnemyConfigs; + [ReadOnly] public NativeArray EnemyIdentities; + public NativeList.ParallelWriter Candidates; + public bool HasEnemyTargets; + public bool HasPlayerTarget; + public int PlayerTargetEntityId; + public float3 PlayerPosition; + public float CellSize; + + public void Execute(int index) + { + CollisionQueryData query = Queries[index]; + int centerCellX = SimulationCell.ToCell(query.Position.x, CellSize); + int centerCellZ = SimulationCell.ToCell(query.Position.z, CellSize); + int queryRange = math.max(1, (int)math.ceil(query.Radius / CellSize)); + int selectedCount = 0; + bool reachedLimit = false; + + if (HasPlayerTarget && query.SourceEntityId != PlayerTargetEntityId && + query.SourceOwnerEntityId != PlayerTargetEntityId) + { + float3 playerPosition = PlayerPosition; + if (query.SourceType == SimulationCollision.SourceTypeArea) + { + playerPosition.y = query.Position.y; + } + + float3 playerDelta = playerPosition - query.Position; + float playerSqrDistance = math.lengthsq(playerDelta); + float playerRadiusSqr = query.Radius * query.Radius; + if (playerSqrDistance <= playerRadiusSqr) + { + Candidates.AddNoResize(new CollisionCandidateData + { + QueryId = query.QueryId, + SourceType = query.SourceType, + SourceEntityId = query.SourceEntityId, + SourceOwnerEntityId = query.SourceOwnerEntityId, + TargetEntityId = PlayerTargetEntityId, + SqrDistance = playerSqrDistance + }); + + selectedCount++; + if (selectedCount >= query.MaxTargets) + { + reachedLimit = true; + } + } + } + + if (!HasEnemyTargets || reachedLimit) + { + return; + } + + for (int dx = -queryRange; dx <= queryRange && !reachedLimit; dx++) + { + for (int dz = -queryRange; dz <= queryRange && !reachedLimit; dz++) + { + long key = SimulationCell.Key(centerCellX + dx, centerCellZ + dz); + if (!EnemyBuckets.TryGetFirstValue(key, out int enemyIndex, + out NativeParallelMultiHashMapIterator iterator)) + { + continue; + } + + do + { + if (enemyIndex < 0 || enemyIndex >= EnemyTransforms.Length) + { + continue; + } + + EnemyIdentitySim identity = EnemyIdentities[enemyIndex]; + if (identity.EntityId == query.SourceOwnerEntityId) + { + continue; + } + + EnemyTransformSim enemyTransform = EnemyTransforms[enemyIndex]; + float deltaY = query.SourceType == SimulationCollision.SourceTypeArea + ? 0f + : enemyTransform.Position.y - query.Position.y; + float3 delta = new float3( + enemyTransform.Position.x - query.Position.x, + deltaY, + enemyTransform.Position.z - query.Position.z); + float sqrDistance = math.lengthsq(delta); + float targetRadius = query.SourceType == SimulationCollision.SourceTypeArea + ? math.max(0f, EnemyConfigs[enemyIndex].BodyRadius) + : 0f; + float effectiveRadius = query.Radius + targetRadius; + if (sqrDistance > effectiveRadius * effectiveRadius) + { + continue; + } + + Candidates.AddNoResize(new CollisionCandidateData + { + QueryId = query.QueryId, + SourceType = query.SourceType, + SourceEntityId = query.SourceEntityId, + SourceOwnerEntityId = query.SourceOwnerEntityId, + TargetEntityId = identity.EntityId, + SqrDistance = sqrDistance + }); + + selectedCount++; + + if (selectedCount >= query.MaxTargets) + { + reachedLimit = true; + break; + } + } while (EnemyBuckets.TryGetNextValue(out enemyIndex, ref iterator)); + } + } + } + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/CollisionQuerySystem.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/CollisionQuerySystem.cs.meta new file mode 100644 index 0000000..8a0e058 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/CollisionQuerySystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d4cc79b0ecd0c248ac2028cd928dd7c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemyMovementSystem.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemyMovementSystem.cs new file mode 100644 index 0000000..408ce01 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemyMovementSystem.cs @@ -0,0 +1,94 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; + +namespace SepCore.Simulation +{ + internal static class EnemyMovementSystem + { + // 读写集:R EnemyTransform / EnemyMoveInput / EnemyAttack;W EnemyTransform / EnemyBehavior + public static JobHandle Schedule(EnemyStore enemies, float deltaTime, float3 playerPosition, + JobHandle dependency) + { + int enemyCount = enemies.Count; + if (enemyCount == 0) + { + return dependency; + } + + EnemyMovementJob job = new EnemyMovementJob + { + Transforms = enemies.Transform.Data.AsArray(), + MoveInputs = enemies.MoveInput.Data.AsArray(), + Attacks = enemies.Attack.Data.AsArray(), + Behaviors = enemies.Behavior.Data.AsArray(), + DeltaTime = deltaTime, + PlayerPosition = playerPosition + }; + return job.Schedule(enemyCount, 64, dependency); + } + } + + [BurstCompile] + internal struct EnemyMovementJob : IJobParallelFor + { + public NativeArray Transforms; + [ReadOnly] public NativeArray MoveInputs; + [ReadOnly] public NativeArray Attacks; + public NativeArray Behaviors; + public float DeltaTime; + public float3 PlayerPosition; + + public void Execute(int index) + { + EnemyMoveInputSim moveInput = MoveInputs[index]; + EnemyAttackSim attack = Attacks[index]; + EnemyTransformSim transform = Transforms[index]; + + float attackRange = attack.AttackRange > 0f ? attack.AttackRange : EnemyAttackSim.DefaultAttackRange; + float attackRangeSqr = attackRange * attackRange; + + float3 currentPosition = transform.Position; + float3 horizontalPosition = new float3(currentPosition.x, 0f, currentPosition.z); + float3 toPlayer = PlayerPosition - horizontalPosition; + float sqrDistance = math.lengthsq(toPlayer); + bool isInAttackRange = sqrDistance <= attackRangeSqr; + bool canChase = !isInAttackRange && moveInput.Speed > 0f && sqrDistance > float.Epsilon; + + float3 forward = transform.Forward; + float3 desiredPosition = currentPosition; + quaternion rotation = transform.Rotation; + + if (canChase) + { + forward = math.normalizesafe(toPlayer, forward); + desiredPosition = currentPosition + forward * moveInput.Speed * DeltaTime; + if (math.lengthsq(forward) > float.Epsilon) + { + rotation = quaternion.LookRotationSafe(forward, math.up()); + } + } + + int nextState; + if (isInAttackRange) + { + nextState = EnemyBehaviorSim.StateInAttackRange; + } + else if (canChase) + { + nextState = EnemyBehaviorSim.StateChasing; + } + else + { + nextState = EnemyBehaviorSim.StateIdle; + } + + transform.Position = desiredPosition; + transform.Forward = forward; + transform.Rotation = rotation; + Transforms[index] = transform; + Behaviors[index] = new EnemyBehaviorSim { State = nextState }; + } + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemyMovementSystem.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemyMovementSystem.cs.meta new file mode 100644 index 0000000..e2c747d --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemyMovementSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53c87038b72cb9c4bb51d952dabd982e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemySeparationSystem.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemySeparationSystem.cs new file mode 100644 index 0000000..76247b2 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemySeparationSystem.cs @@ -0,0 +1,311 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; + +namespace SepCore.Simulation +{ + internal readonly struct EnemySeparationSettings + { + public EnemySeparationSettings(float cellSize, float maxRadius, float pushDamping, float maxStepScale, + bool useTangentialInAttackRange, float pushSmoothing) + { + CellSize = cellSize; + MaxRadius = maxRadius; + PushDamping = pushDamping; + MaxStepScale = maxStepScale; + UseTangentialInAttackRange = useTangentialInAttackRange; + PushSmoothing = pushSmoothing; + } + + public float CellSize { get; } + public float MaxRadius { get; } + public float PushDamping { get; } + public float MaxStepScale { get; } + public bool UseTangentialInAttackRange { get; } + public float PushSmoothing { get; } + } + + internal static class EnemySeparationSystem + { + // 读写集:R EnemyTransform / EnemyMoveConfig / EnemyBehavior / EnemySeparation(PreviousPush) + // W EnemyTransform(经分离 scratch 由 Commit 写回)/ EnemySeparation(PreviousPush) + public static JobHandle Schedule(EnemyStore enemies, NativeParallelMultiHashMap buckets, + NativeList separatedTransforms, in EnemySeparationSettings settings, + float3 playerPosition, JobHandle dependency) + { + int enemyCount = enemies.Count; + if (enemyCount <= 0) + { + return dependency; + } + + NativeArray transformArray = enemies.Transform.Data.AsArray(); + NativeArray configArray = enemies.MoveConfig.Data.AsArray(); + NativeArray behaviorArray = enemies.Behavior.Data.AsArray(); + NativeArray separatedTransformArray = separatedTransforms.AsArray(); + NativeArray separationArray = enemies.Separation.Data.AsArray(); + + BuildEnemySeparationBucketsJob buildJob = new BuildEnemySeparationBucketsJob + { + Transforms = transformArray, + MoveConfigs = configArray, + Buckets = buckets.AsParallelWriter(), + CellSize = settings.CellSize + }; + JobHandle buildHandle = buildJob.Schedule(enemyCount, 64, dependency); + EnemySeparationJob separationJob = new EnemySeparationJob + { + Transforms = transformArray, + MoveConfigs = configArray, + Behaviors = behaviorArray, + Buckets = buckets, + SeparatedTransforms = separatedTransformArray, + Separations = separationArray, + CellSize = settings.CellSize, + MaxRadius = settings.MaxRadius, + PlayerPosition = playerPosition, + PushDamping = settings.PushDamping, + MaxStepScale = settings.MaxStepScale, + UseTangentialInAttackRange = settings.UseTangentialInAttackRange, + PushSmoothing = settings.PushSmoothing + }; + return separationJob.Schedule(enemyCount, 64, buildHandle); + } + + public static void Commit(EnemyStore enemies, NativeList separatedTransforms, + int enemyCount) + { + for (int i = 0; i < enemyCount; i++) + { + enemies.Transform[i] = separatedTransforms[i]; + } + } + } + + [BurstCompile] + internal struct BuildEnemySeparationBucketsJob : IJobParallelFor + { + [ReadOnly] public NativeArray Transforms; + [ReadOnly] public NativeArray MoveConfigs; + public NativeParallelMultiHashMap.ParallelWriter Buckets; + public float CellSize; + + public void Execute(int index) + { + if (!MoveConfigs[index].AvoidOverlap) + { + return; + } + + float3 position = Transforms[index].Position; + position.y = 0f; + int cellX = SimulationCell.ToCell(position.x, CellSize); + int cellZ = SimulationCell.ToCell(position.z, CellSize); + Buckets.Add(SimulationCell.Key(cellX, cellZ), index); + } + } + + [BurstCompile] + internal struct EnemySeparationJob : IJobParallelFor + { + [ReadOnly] public NativeArray Transforms; + [ReadOnly] public NativeArray MoveConfigs; + [ReadOnly] public NativeArray Behaviors; + [ReadOnly] public NativeParallelMultiHashMap Buckets; + public NativeArray SeparatedTransforms; + public NativeArray Separations; + public float CellSize; + public float MaxRadius; + public float3 PlayerPosition; + public float PushDamping; + public float MaxStepScale; + public bool UseTangentialInAttackRange; + public float PushSmoothing; + + public void Execute(int index) + { + EnemyMoveConfigSim selfConfig = MoveConfigs[index]; + if (!selfConfig.AvoidOverlap) + { + SeparatedTransforms[index] = Transforms[index]; + return; + } + + EnemyTransformSim self = Transforms[index]; + float3 candidate = self.Position; + candidate.y = 0f; + float3 original = candidate; + float3 fallback = + math.normalizesafe(new float3(self.Forward.x, 0f, self.Forward.z), new float3(1f, 0f, 0f)); + float selfRadius = selfConfig.BodyRadius > 0f ? selfConfig.BodyRadius : 0.45f; + int iterations = selfConfig.SeparationIterations > 0 ? selfConfig.SeparationIterations : 1; + int queryRange = math.max(1, (int)math.ceil((selfRadius + MaxRadius) / CellSize)); + + for (int iter = 0; iter < iterations; iter++) + { + int cellX = SimulationCell.ToCell(candidate.x, CellSize); + int cellZ = SimulationCell.ToCell(candidate.z, CellSize); + float3 pushAccumulation = float3.zero; + + for (int dx = -queryRange; dx <= queryRange; dx++) + { + for (int dz = -queryRange; dz <= queryRange; dz++) + { + long key = SimulationCell.Key(cellX + dx, cellZ + dz); + if (!Buckets.TryGetFirstValue(key, out int otherIndex, + out NativeParallelMultiHashMapIterator iterator)) + { + continue; + } + + do + { + if (otherIndex == index) + { + continue; + } + + EnemyMoveConfigSim otherConfig = MoveConfigs[otherIndex]; + if (!otherConfig.AvoidOverlap) + { + continue; + } + + float otherRadius = otherConfig.BodyRadius > 0f ? otherConfig.BodyRadius : 0.45f; + float minDistance = selfRadius + otherRadius; + float minDistanceSqr = minDistance * minDistance; + + float3 otherPosition = Transforms[otherIndex].Position; + otherPosition.y = 0f; + float3 toSelf = candidate - otherPosition; + float sqrDistance = math.lengthsq(toSelf); + + if (sqrDistance <= float.Epsilon) + { + float3 zeroDistanceAxis = GetZeroDistanceSeparationAxis(index, otherIndex); + float directionSign = index < otherIndex ? 1f : -1f; + pushAccumulation += zeroDistanceAxis * (selfRadius * 0.25f * directionSign); + continue; + } + + if (sqrDistance >= minDistanceSqr) + { + continue; + } + + float distance = math.sqrt(sqrDistance); + float penetration = minDistance - distance; + pushAccumulation += (toSelf / distance) * penetration; + } while (Buckets.TryGetNextValue(out otherIndex, ref iterator)); + } + } + + if (math.lengthsq(pushAccumulation) <= float.Epsilon) + { + continue; + } + + float3 resolvedPush = pushAccumulation * PushDamping; + + float maxStep = selfRadius * MaxStepScale; + float pushLength = math.length(resolvedPush); + if (pushLength > maxStep && pushLength > float.Epsilon) + { + resolvedPush = resolvedPush / pushLength * maxStep; + } + + candidate += resolvedPush; + } + + float3 framePush = candidate - original; + float2 previousPush2 = Separations[index].PreviousPush; + float3 previousPush = new float3(previousPush2.x, 0f, previousPush2.y); + float3 smoothedPush = SmoothSeparationPush(framePush, previousPush, PushSmoothing); + + if (UseTangentialInAttackRange && Behaviors[index].State == EnemyBehaviorSim.StateInAttackRange) + { + smoothedPush = ProjectToTangential(smoothedPush, PlayerPosition, original); + } + + float maxTotalStep = selfRadius * MaxStepScale * iterations; + float smoothedLength = math.length(smoothedPush); + if (smoothedLength > maxTotalStep && smoothedLength > float.Epsilon) + { + smoothedPush = smoothedPush / smoothedLength * maxTotalStep; + } + + float3 finalPosition = original + smoothedPush; + Separations[index] = new EnemySeparationSim { PreviousPush = new float2(smoothedPush.x, smoothedPush.z) }; + self.Position = new float3(finalPosition.x, self.Position.y, finalPosition.z); + if (math.lengthsq(smoothedPush) > float.Epsilon) + { + self.Forward = new float3(fallback.x, self.Forward.y, fallback.z); + } + + SeparatedTransforms[index] = self; + } + + private static float3 SmoothSeparationPush(float3 framePush, float3 previousPush, float pushSmoothing) + { + float frameLengthSqr = math.lengthsq(framePush); + float previousLengthSqr = math.lengthsq(previousPush); + + if (frameLengthSqr <= float.Epsilon) + { + return float3.zero; + } + + if (previousLengthSqr <= float.Epsilon || pushSmoothing <= 0f) + { + return framePush; + } + + float frameLength = math.sqrt(frameLengthSqr); + float previousLength = math.sqrt(previousLengthSqr); + float3 frameDirection = framePush / frameLength; + float3 previousDirection = previousPush / previousLength; + float directionAlignment = math.dot(frameDirection, previousDirection); + + if (directionAlignment >= 0.35f) + { + return framePush; + } + + float directionalFactor = math.saturate((0.35f - directionAlignment) / 1.35f); + float smoothingStrength = pushSmoothing * directionalFactor; + return math.lerp(framePush, previousPush, smoothingStrength); + } + + private static float3 ProjectToTangential(float3 push, float3 playerPosition, float3 currentPosition) + { + if (math.lengthsq(push) <= float.Epsilon) + { + return push; + } + + float3 toPlayer = playerPosition - currentPosition; + float toPlayerSqr = math.lengthsq(toPlayer); + if (toPlayerSqr <= float.Epsilon) + { + return push; + } + + float3 radialDirection = toPlayer / math.sqrt(toPlayerSqr); + float radialOffset = math.dot(push, radialDirection); + return push - radialDirection * radialOffset; + } + + private static float3 GetZeroDistanceSeparationAxis(int index, int otherIndex) + { + int lowIndex = math.min(index, otherIndex); + int highIndex = math.max(index, otherIndex); + uint pairHash = (uint)(lowIndex * 73856093) ^ (uint)(highIndex * 19349663); + + float axisX = (pairHash & 1023u) / 511.5f - 1f; + float axisZ = ((pairHash >> 10) & 1023u) / 511.5f - 1f; + float3 axis = new float3(axisX, 0f, axisZ); + return math.normalizesafe(axis, new float3(1f, 0f, 0f)); + } + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemySeparationSystem.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemySeparationSystem.cs.meta new file mode 100644 index 0000000..b24d71b --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/EnemySeparationSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f125a6671430b4a45b0447eb4e6541af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/PickupMovementSystem.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/PickupMovementSystem.cs new file mode 100644 index 0000000..ec631bf --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/PickupMovementSystem.cs @@ -0,0 +1,99 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; + +namespace SepCore.Simulation +{ + internal static class PickupMovementSystem + { + // 读写集:R/W PickupTransform / PickupState; R PickupIdentity / PickupRadius + // W CollectedPickups + public static JobHandle Schedule(PickupStore pickups, NativeList collectedPickups, + float deltaTime, float3 playerPosition, float absorbRange, float absorbSpeed, float collectDistance, + JobHandle dependency) + { + int pickupCount = pickups.Count; + if (pickupCount == 0) + { + return dependency; + } + + PickupMovementJob job = new PickupMovementJob + { + Transforms = pickups.Transform.Data.AsArray(), + States = pickups.State.Data.AsArray(), + Identities = pickups.Identity.Data.AsArray(), + Radii = pickups.Radius.Data.AsArray(), + CollectedPickups = collectedPickups.AsParallelWriter(), + PlayerPosition = playerPosition, + AbsorbRange = math.max(0f, absorbRange), + AbsorbSpeed = math.max(0f, absorbSpeed), + CollectDistance = math.max(0f, collectDistance), + DeltaTime = deltaTime + }; + return job.Schedule(pickupCount, 64, dependency); + } + } + + [BurstCompile] + internal struct PickupMovementJob : IJobParallelFor + { + public NativeArray Transforms; + public NativeArray States; + [ReadOnly] public NativeArray Identities; + [ReadOnly] public NativeArray Radii; + public NativeList.ParallelWriter CollectedPickups; + public float3 PlayerPosition; + public float AbsorbRange; + public float AbsorbSpeed; + public float CollectDistance; + public float DeltaTime; + + public void Execute(int index) + { + PickupTransformSim transform = Transforms[index]; + PickupStateSim state = States[index]; + PickupIdentitySim identity = Identities[index]; + PickupRadiusSim radius = Radii[index]; + + float3 currentPosition = transform.Position; + float3 horizontalPosition = new float3(currentPosition.x, 0f, currentPosition.z); + float3 toPlayer = PlayerPosition - horizontalPosition; + float sqrDistance = math.lengthsq(toPlayer); + + float effectiveCollectDistance = math.max(CollectDistance, radius.Radius); + float sqrCollectDistance = effectiveCollectDistance * effectiveCollectDistance; + + if (sqrDistance <= sqrCollectDistance) + { + CollectedPickups.AddNoResize(identity.EntityId); + return; + } + + bool isAbsorbing = state.State == PickupStateSim.StateAbsorbing; + bool shouldAbsorb = isAbsorbing || (AbsorbRange > 0f && sqrDistance <= AbsorbRange * AbsorbRange); + + if (shouldAbsorb && AbsorbSpeed > 0f && sqrDistance > float.Epsilon) + { + state.State = PickupStateSim.StateAbsorbing; + float distance = math.sqrt(sqrDistance); + float step = AbsorbSpeed * DeltaTime; + + if (step >= distance) + { + transform.Position = new float3(PlayerPosition.x, currentPosition.y, PlayerPosition.z); + CollectedPickups.AddNoResize(identity.EntityId); + } + else + { + float3 direction = toPlayer / distance; + transform.Position = currentPosition + direction * step; + } + + Transforms[index] = transform; + States[index] = state; + } + } + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/PickupMovementSystem.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/PickupMovementSystem.cs.meta new file mode 100644 index 0000000..2c3506b --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/PickupMovementSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cb91b00a6bb60d6429df916850580597 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/ProjectileMovementSystem.cs b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/ProjectileMovementSystem.cs new file mode 100644 index 0000000..b0dd8fd --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/ProjectileMovementSystem.cs @@ -0,0 +1,87 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; + +namespace SepCore.Simulation +{ + internal static class ProjectileMovementSystem + { + // 读写集:R/W ProjectileTransform / ProjectileVelocity / ProjectileLife + public static JobHandle Schedule(ProjectileStore projectiles, float deltaTime, float3 playerPosition, + float maxDistanceFromPlayer, float maxVerticalOffsetFromPlayer, JobHandle dependency) + { + int projectileCount = projectiles.Count; + if (projectileCount == 0) + { + return dependency; + } + + float maxDistance = math.max(0f, maxDistanceFromPlayer); + float maxSqrDistanceFromPlayer = maxDistance > 0f ? maxDistance * maxDistance : -1f; + float maxVerticalOffset = math.max(0f, maxVerticalOffsetFromPlayer); + + ProjectileMovementJob job = new ProjectileMovementJob + { + Transforms = projectiles.Transform.Data.AsArray(), + Velocities = projectiles.Velocity.Data.AsArray(), + Lives = projectiles.Life.Data.AsArray(), + DeltaTime = deltaTime, + PlayerPosition = playerPosition, + MaxSqrDistanceFromPlayer = maxSqrDistanceFromPlayer, + MaxVerticalOffsetFromPlayer = maxVerticalOffset + }; + return job.Schedule(projectileCount, 64, dependency); + } + } + + [BurstCompile] + internal struct ProjectileMovementJob : IJobParallelFor + { + public NativeArray Transforms; + public NativeArray Velocities; + public NativeArray Lives; + public float DeltaTime; + public float3 PlayerPosition; + public float MaxSqrDistanceFromPlayer; + public float MaxVerticalOffsetFromPlayer; + + public void Execute(int index) + { + ProjectileLifeSim life = Lives[index]; + if (!life.Active) + { + return; + } + + ProjectileTransformSim transform = Transforms[index]; + float3 velocity = Velocities[index].Velocity; + float3 nextPosition = transform.Position + velocity * DeltaTime; + float nextAge = math.max(0f, life.Age + DeltaTime); + bool shouldExpire = life.LifeTime > 0f && nextAge >= life.LifeTime; + + if (!shouldExpire && MaxSqrDistanceFromPlayer > 0f) + { + float3 horizontalDelta = + new float3(nextPosition.x - PlayerPosition.x, 0f, nextPosition.z - PlayerPosition.z); + shouldExpire = math.lengthsq(horizontalDelta) > MaxSqrDistanceFromPlayer; + } + + if (!shouldExpire && MaxVerticalOffsetFromPlayer > 0f) + { + shouldExpire = math.abs(nextPosition.y - PlayerPosition.y) > MaxVerticalOffsetFromPlayer; + } + + transform.Position = nextPosition; + if (math.lengthsq(velocity) > float.Epsilon) + { + transform.Forward = math.normalizesafe(velocity, transform.Forward); + } + + Transforms[index] = transform; + life.Age = nextAge; + life.Active = !shouldExpire; + Lives[index] = life; + } + } +} diff --git a/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/ProjectileMovementSystem.cs.meta b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/ProjectileMovementSystem.cs.meta new file mode 100644 index 0000000..5944c38 --- /dev/null +++ b/Assets/GameMain/Scripts/Runtime/CustomComponent/Simulation/Systems/ProjectileMovementSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b026d9036f330c84a8f047a80e2e16b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/Simulation/EditMode/SimulationWorldTickTests.cs b/Assets/Tests/Simulation/EditMode/SimulationWorldTickTests.cs index f9cd50a..38783d2 100644 --- a/Assets/Tests/Simulation/EditMode/SimulationWorldTickTests.cs +++ b/Assets/Tests/Simulation/EditMode/SimulationWorldTickTests.cs @@ -1,4 +1,3 @@ -using System.Reflection; using NUnit.Framework; using SepCore.Components; using SepCore.Simulation; @@ -8,15 +7,6 @@ namespace SepCore.Tests.Editor { public class SimulationWorldTickTests { - private static readonly BindingFlags NonPublicInstance = - BindingFlags.Instance | BindingFlags.NonPublic; - - private static readonly MethodInfo UpsertEnemyMethod = - typeof(SimulationWorld).GetMethod("UpsertEnemy", NonPublicInstance); - - private static readonly MethodInfo UpsertProjectileMethod = - typeof(SimulationWorld).GetMethod("UpsertProjectile", NonPublicInstance); - private GameObject _worldObject; private SimulationWorld _world; @@ -45,7 +35,7 @@ namespace SepCore.Tests.Editor MovementComponent movement = moverObject.AddComponent(); moverObject.transform.position = new Vector3(1f, 0f, 2f); - movement.OnInit(5f, moverObject.transform); + movement.OnInit(5f, moverObject.transform, null, false, 0.45f, 2); movement.SetMove(true); movement.SetDirection(Vector3.right); @@ -64,7 +54,7 @@ namespace SepCore.Tests.Editor [Test] public void Tick_AdvancesEnemyAndUpdatesNearestTargetQuery() { - UpsertEnemy(new EnemySimData + _world.UpsertEnemy(new EnemySimData { EntityId = 1001, Position = new Vector3(0f, 0f, 0f), @@ -77,10 +67,10 @@ namespace SepCore.Tests.Editor SeparationIterations = 2 }); - _world.Tick(new SimulationTickContext(1f, 1f, new Vector3(10f, 0f, 0f))); + _world.Tick(new SimulationTickContext(1f, new Vector3(10f, 0f, 0f))); - Assert.That(_world.Enemies.Count, Is.EqualTo(1)); - EnemySimData enemy = _world.Enemies[0]; + Assert.That(_world.EnemyCount, Is.EqualTo(1)); + Assert.That(_world.TryGetEnemySnapshot(1001, out EnemySimData enemy), Is.True); Assert.That(enemy.State, Is.EqualTo(1)); Assert.That(enemy.Position.x, Is.EqualTo(3f).Within(0.001f)); Assert.That(_world.TryGetNearestEnemyEntityId(new Vector3(3.2f, 0f, 0f), 1f, out int enemyEntityId), @@ -88,10 +78,37 @@ namespace SepCore.Tests.Editor Assert.That(enemyEntityId, Is.EqualTo(1001)); } + [Test] + public void Tick_AccumulatesTimeAcrossTicksBeforeRunningFixedStep() + { + _world.UpsertEnemy(new EnemySimData + { + EntityId = 1003, + Position = Vector3.zero, + Forward = Vector3.forward, + Rotation = Quaternion.identity, + Speed = 6f, + AttackRange = 1f, + AvoidEnemyOverlap = false, + EnemyBodyRadius = 0.45f, + SeparationIterations = 1 + }); + + _world.Tick(new SimulationTickContext(0.01f, new Vector3(10f, 0f, 0f))); + + Assert.That(_world.TryGetEnemySnapshot(1003, out EnemySimData enemy), Is.True); + Assert.That(enemy.Position.x, Is.EqualTo(0f).Within(0.0001f)); + + _world.Tick(new SimulationTickContext(0.01f, new Vector3(10f, 0f, 0f))); + + Assert.That(_world.TryGetEnemySnapshot(1003, out enemy), Is.True); + Assert.That(enemy.Position.x, Is.EqualTo(0.1f).Within(0.0001f)); + } + [Test] public void SyncEnemyMovementInput_DisablesEnemyMovement() { - UpsertEnemy(new EnemySimData + _world.UpsertEnemy(new EnemySimData { EntityId = 1002, Position = Vector3.zero, @@ -105,9 +122,9 @@ namespace SepCore.Tests.Editor }); _world.SyncEnemyMovementInput(1002, false, Vector3.left, 4f, true, 0.45f, 2); - _world.Tick(new SimulationTickContext(1f, 1f, new Vector3(10f, 0f, 0f))); + _world.Tick(new SimulationTickContext(1f, new Vector3(10f, 0f, 0f))); - EnemySimData enemy = _world.Enemies[0]; + Assert.That(_world.TryGetEnemySnapshot(1002, out EnemySimData enemy), Is.True); Assert.That(enemy.State, Is.EqualTo(0)); Assert.That(enemy.Position.x, Is.EqualTo(0f).Within(0.0001f)); Assert.That(enemy.Position.y, Is.EqualTo(0f).Within(0.0001f)); @@ -117,50 +134,192 @@ namespace SepCore.Tests.Editor [Test] public void Tick_ExpiresProjectileThroughSimulationLifetime() { - UpsertProjectile(new ProjectileSimData + _world.UpsertProjectile(new ProjectileSimData { EntityId = 2001, OwnerEntityId = 1001, Position = Vector3.zero, Forward = Vector3.right, Velocity = Vector3.right * 4f, - Speed = 4f, LifeTime = 0.25f, Age = 0f, - Active = true, - RemainingLifetime = 0.25f, - State = 0 + Active = true }); - _world.Tick(new SimulationTickContext(0.5f, 0.5f, Vector3.zero)); + _world.Tick(new SimulationTickContext(0.5f, Vector3.zero)); - Assert.That(_world.Projectiles, Is.Empty); + Assert.That(_world.ProjectileCount, Is.EqualTo(0)); + } + + [Test] + public void Tick_WithIdenticalInput_ProducesEquivalentSnapshots() + { + const int tickCount = 30; + GameObject firstWorldObject = new GameObject("SimulationWorldDeterminismA"); + GameObject secondWorldObject = new GameObject("SimulationWorldDeterminismB"); + try + { + SimulationWorld firstWorld = firstWorldObject.AddComponent(); + SimulationWorld secondWorld = secondWorldObject.AddComponent(); + SeedDeterminismScene(firstWorld); + SeedDeterminismScene(secondWorld); + + SimulationTickContext tickContext = + new SimulationTickContext(1f / 30f, new Vector3(3.5f, 0f, -2.5f)); + for (int tick = 0; tick < tickCount; tick++) + { + firstWorld.Tick(in tickContext); + secondWorld.Tick(in tickContext); + } + + Assert.That(firstWorld.EnemyCount, Is.EqualTo(DeterminismEnemyIds.Length)); + Assert.That(secondWorld.EnemyCount, Is.EqualTo(firstWorld.EnemyCount)); + for (int i = 0; i < DeterminismEnemyIds.Length; i++) + { + Assert.That(firstWorld.TryGetEnemySnapshot(DeterminismEnemyIds[i], out EnemySimData first), + Is.True); + Assert.That(secondWorld.TryGetEnemySnapshot(DeterminismEnemyIds[i], out EnemySimData second), + Is.True); + Assert.That(second.Position.x, Is.EqualTo(first.Position.x).Within(0.01f)); + Assert.That(second.Position.z, Is.EqualTo(first.Position.z).Within(0.01f)); + Assert.That(second.State, Is.EqualTo(first.State)); + } + + Assert.That(secondWorld.ProjectileCount, Is.EqualTo(firstWorld.ProjectileCount)); + for (int i = 0; i < DeterminismProjectileIds.Length; i++) + { + Assert.That( + firstWorld.TryGetProjectileSnapshot(DeterminismProjectileIds[i], out ProjectileSimData first), + Is.True); + Assert.That( + secondWorld.TryGetProjectileSnapshot(DeterminismProjectileIds[i], out ProjectileSimData second), + Is.True); + Assert.That(second.Position.x, Is.EqualTo(first.Position.x).Within(0.01f)); + Assert.That(second.Position.z, Is.EqualTo(first.Position.z).Within(0.01f)); + Assert.That(second.Age, Is.EqualTo(first.Age).Within(0.001f)); + } + } + finally + { + Object.DestroyImmediate(firstWorldObject); + Object.DestroyImmediate(secondWorldObject); + } } [Test] public void ClearSimulationState_ClearsPublicStateContainers() { - UpsertEnemy(new EnemySimData { EntityId = 1, Position = Vector3.zero, Forward = Vector3.forward }); - UpsertProjectile(new ProjectileSimData { EntityId = 2, Active = true, State = 0 }); + _world.UpsertEnemy(new EnemySimData { EntityId = 1, Position = Vector3.zero, Forward = Vector3.forward }); + _world.UpsertProjectile(new ProjectileSimData { EntityId = 2, Active = true }); _world.ClearSimulationState(); - Assert.That(_world.Enemies, Is.Empty); - Assert.That(_world.Projectiles, Is.Empty); - Assert.That(_world.Pickups, Is.Empty); + Assert.That(_world.EnemyCount, Is.EqualTo(0)); + Assert.That(_world.ProjectileCount, Is.EqualTo(0)); + Assert.That(_world.PickupCount, Is.EqualTo(0)); } - private void UpsertEnemy(EnemySimData simData) + [Test] + public void Pickup_WhenOutsideAbsorbRange_DoesNotMove() { - Assert.NotNull(UpsertEnemyMethod); - UpsertEnemyMethod.Invoke(_world, new object[] { simData }); + _world.UpsertPickup(new PickupSimData + { + EntityId = 7001, + Position = new Vector3(10f, 0f, 10f), + PickupRadius = 0.35f, + State = 0 + }); + + _world.SyncPlayerAbsorbSettings(3f, 5f, 0.5f); + _world.Tick(new SimulationTickContext(0.1f, Vector3.zero)); + + Assert.That(_world.TryGetPickupSnapshot(7001, out PickupSimData snapshot), Is.True); + Assert.That(snapshot.Position.x, Is.EqualTo(10f).Within(0.001f)); + Assert.That(snapshot.Position.z, Is.EqualTo(10f).Within(0.001f)); + Assert.That(snapshot.State, Is.EqualTo(0)); } - private void UpsertProjectile(ProjectileSimData simData) + [Test] + public void Pickup_WhenInsideAbsorbRange_MovesTowardsPlayerAndTransitionsState() { - Assert.NotNull(UpsertProjectileMethod); - UpsertProjectileMethod.Invoke(_world, new object[] { simData }); + _world.UpsertPickup(new PickupSimData + { + EntityId = 7002, + Position = new Vector3(2f, 0f, 0f), + PickupRadius = 0.2f, + State = 0 + }); + + _world.SyncPlayerAbsorbSettings(5f, 4f, 0.5f); + _world.Tick(new SimulationTickContext(0.25f, Vector3.zero)); + + Assert.That(_world.TryGetPickupSnapshot(7002, out PickupSimData snapshot), Is.True); + // Moved from 2.0 towards 0 by 4.0 * 0.25 = 1.0 -> remaining distance is 1.0 + Assert.That(snapshot.Position.x, Is.EqualTo(1.0f).Within(0.01f)); + Assert.That(snapshot.Position.z, Is.EqualTo(0f).Within(0.01f)); + Assert.That(snapshot.State, Is.EqualTo(1)); + } + + [Test] + public void Pickup_TriggerFullMapMagnet_ActivatesDistantPickups() + { + _world.UpsertPickup(new PickupSimData + { + EntityId = 7003, + Position = new Vector3(50f, 0f, 0f), + PickupRadius = 0.2f, + State = 0 + }); + + _world.SyncPlayerAbsorbSettings(5f, 10f, 0.5f); + _world.TriggerFullMapMagnet(); + + Assert.That(_world.TryGetPickupSnapshot(7003, out PickupSimData snapshotBefore), Is.True); + Assert.That(snapshotBefore.State, Is.EqualTo(1)); + + _world.Tick(new SimulationTickContext(0.5f, Vector3.zero)); + + Assert.That(_world.TryGetPickupSnapshot(7003, out PickupSimData snapshotAfter), Is.True); + // Moved from 50 towards 0 by 10 * 0.5 = 5 -> remaining distance is 45 + Assert.That(snapshotAfter.Position.x, Is.EqualTo(45f).Within(0.01f)); + } + + private static readonly int[] DeterminismEnemyIds = { 5001, 5002, 5003, 5004, 5005, 5006 }; + private static readonly int[] DeterminismProjectileIds = { 6001, 6002, 6003 }; + + private static void SeedDeterminismScene(SimulationWorld world) + { + for (int i = 0; i < DeterminismEnemyIds.Length; i++) + { + float angle = i * 0.9f; + world.UpsertEnemy(new EnemySimData + { + EntityId = DeterminismEnemyIds[i], + Position = new Vector3(Mathf.Cos(angle) * 1.5f, 0f, Mathf.Sin(angle) * 1.5f), + Forward = Vector3.forward, + Rotation = Quaternion.identity, + Speed = 2f + i * 0.25f, + AttackRange = 0.5f, + AvoidEnemyOverlap = true, + EnemyBodyRadius = 0.4f + i * 0.05f, + SeparationIterations = 2 + }); + } + + for (int i = 0; i < DeterminismProjectileIds.Length; i++) + { + world.UpsertProjectile(new ProjectileSimData + { + EntityId = DeterminismProjectileIds[i], + OwnerEntityId = DeterminismEnemyIds[i], + Position = new Vector3(0f, 1f, 0f), + Forward = Vector3.right, + Velocity = Vector3.right * (3f + i), + LifeTime = 5f, + Age = 0f, + Active = true + }); + } } } } - diff --git a/Assets/Tests/Simulation/PlayMode/SimulationWorldPlayModeTests.cs b/Assets/Tests/Simulation/PlayMode/SimulationWorldPlayModeTests.cs index ee60d13..f398263 100644 --- a/Assets/Tests/Simulation/PlayMode/SimulationWorldPlayModeTests.cs +++ b/Assets/Tests/Simulation/PlayMode/SimulationWorldPlayModeTests.cs @@ -57,7 +57,7 @@ namespace SepCore.Tests.PlayMode playerObject.transform.position = Vector3.zero; _world.SyncPlayerMovementInput(playerObject.transform, true, Vector3.right, 6f); - _world.Tick(new SimulationTickContext(0.5f, 0.5f, playerObject.transform.position)); + _world.Tick(new SimulationTickContext(0.5f, playerObject.transform.position)); yield return null; @@ -104,33 +104,30 @@ namespace SepCore.Tests.PlayMode { _world.SyncEnemyMovementInput(4001, false, Vector3.left, 5f, true, 0.45f, 2); - MethodInfo upsertEnemyMethod = typeof(SimulationWorld).GetMethod("UpsertEnemy", NonPublicInstance); - upsertEnemyMethod.Invoke(_world, new object[] + _world.UpsertEnemy(new EnemySimData { - new EnemySimData - { - EntityId = 4001, - Position = Vector3.zero, - Forward = Vector3.forward, - Rotation = Quaternion.identity, - Speed = 5f, - AttackRange = 0.5f, - AvoidEnemyOverlap = true, - EnemyBodyRadius = 0.45f, - SeparationIterations = 2 - } + EntityId = 4001, + Position = Vector3.zero, + Forward = Vector3.forward, + Rotation = Quaternion.identity, + Speed = 5f, + AttackRange = 0.5f, + AvoidEnemyOverlap = true, + EnemyBodyRadius = 0.45f, + SeparationIterations = 2 }); _world.SyncEnemyMovementInput(4001, false, Vector3.left, 5f, true, 0.45f, 2); - _world.Tick(new SimulationTickContext(1f, 1f, new Vector3(8f, 0f, 0f))); + _world.Tick(new SimulationTickContext(1f, new Vector3(8f, 0f, 0f))); yield return null; - Assert.That(_world.Enemies.Count, Is.EqualTo(1)); - Assert.That(_world.Enemies[0].Position.x, Is.EqualTo(0f).Within(0.0001f)); - Assert.That(_world.Enemies[0].Position.y, Is.EqualTo(0f).Within(0.0001f)); - Assert.That(_world.Enemies[0].Position.z, Is.EqualTo(0f).Within(0.0001f)); - Assert.That(_world.Enemies[0].State, Is.EqualTo(0)); + Assert.That(_world.EnemyCount, Is.EqualTo(1)); + Assert.That(_world.TryGetEnemySnapshot(4001, out EnemySimData enemy), Is.True); + Assert.That(enemy.Position.x, Is.EqualTo(0f).Within(0.0001f)); + Assert.That(enemy.Position.y, Is.EqualTo(0f).Within(0.0001f)); + Assert.That(enemy.Position.z, Is.EqualTo(0f).Within(0.0001f)); + Assert.That(enemy.State, Is.EqualTo(0)); } } }