Checkpoint 3 & Checkpoint 4:

- GameStateBattle 接入 Simulation 主更新入口
- SimulationWorld 增加开关 UseSimulationMovement(默认 false)
- SimulationWorld.Tick(...) 现在在开关开启时执行敌人追踪/移动模拟,基本还原 MovementComponent 功能(敌人互斥)
- 调整 IEnemySeparationSolver 系列方法,不再依赖 MovementComponent
This commit is contained in:
SepComet 2026-02-20 19:54:44 +08:00
parent 83f8a356f7
commit 6494ebc5fd
15 changed files with 229 additions and 87 deletions

View File

@ -151,7 +151,10 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
_isMoving: 0 _isMoving: 0
_direction: {x: 0, y: 0, z: 0} _direction: {x: 0, y: 0, z: 0}
_cachedTransform: {fileID: 0} _cachedTransform: {fileID: 7683855655592166216}
_avoidEnemyOverlap: 0
_enemyBodyRadius: 0.45
_separationIterations: 2
_speedBase: 0 _speedBase: 0
--- !u!114 &6353753365317756414 --- !u!114 &6353753365317756414
MonoBehaviour: MonoBehaviour:

View File

@ -191,8 +191,8 @@ Camera:
near clip plane: 0.3 near clip plane: 0.3
far clip plane: 100 far clip plane: 100
field of view: 80 field of view: 80
orthographic: 0 orthographic: 1
orthographic size: 5 orthographic size: 15
m_Depth: 0 m_Depth: 0
m_CullingMask: m_CullingMask:
serializedVersion: 2 serializedVersion: 2

View File

@ -18,6 +18,9 @@ namespace Components
[SerializeField] private int _separationIterations = 2; [SerializeField] private int _separationIterations = 2;
public float Speed => (_speedBase + _movementStat.Value) * _movementStat.Percent; public float Speed => (_speedBase + _movementStat.Value) * _movementStat.Percent;
public bool AvoidEnemyOverlap => _avoidEnemyOverlap;
public float EnemyBodyRadius => _enemyBodyRadius;
public int SeparationIterations => _separationIterations;
[SerializeField] private float _speedBase; [SerializeField] private float _speedBase;
private StatComponent _statComponent; private StatComponent _statComponent;
@ -61,6 +64,8 @@ namespace Components
public void OnReset() public void OnReset()
{ {
Transform transformToUnregister = _cachedTransform;
_speedBase = 0; _speedBase = 0;
_cachedTransform = null; _cachedTransform = null;
_direction = Vector3.zero; _direction = Vector3.zero;
@ -77,7 +82,7 @@ namespace Components
_statComponent = null; _statComponent = null;
UnregisterEnemyMover(); UnregisterEnemyMover(transformToUnregister);
} }
private void Move(float deltaTime = 0) private void Move(float deltaTime = 0)
@ -91,7 +96,7 @@ namespace Components
if (_avoidEnemyOverlap) if (_avoidEnemyOverlap)
{ {
nextPosition = EnemySeparationSolverProvider.Resolve( nextPosition = EnemySeparationSolverProvider.Resolve(
this, _cachedTransform,
nextPosition, nextPosition,
_direction, _direction,
_separationIterations); _separationIterations);
@ -108,12 +113,12 @@ namespace Components
{ {
UnregisterEnemyMover(); UnregisterEnemyMover();
if (!_avoidEnemyOverlap) return; if (!_avoidEnemyOverlap) return;
EnemySeparationSolverProvider.Register(this, _cachedTransform, _enemyBodyRadius); EnemySeparationSolverProvider.Register(_cachedTransform, _enemyBodyRadius);
} }
private void UnregisterEnemyMover() private void UnregisterEnemyMover(Transform transform = null)
{ {
EnemySeparationSolverProvider.Unregister(this); EnemySeparationSolverProvider.Unregister(transform ?? _cachedTransform);
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Components;
using DataTable; using DataTable;
using Definition.Enum; using Definition.Enum;
using Entity; using Entity;
@ -252,6 +253,8 @@ namespace CustomComponent
private static EnemySimData CreateEnemySimData(EnemyBase enemy, EnemyData enemyData) private static EnemySimData CreateEnemySimData(EnemyBase enemy, EnemyData enemyData)
{ {
MovementComponent movementComponent = enemy != null ? enemy.GetComponent<MovementComponent>() : null;
return new EnemySimData return new EnemySimData
{ {
EntityId = enemy.Id, EntityId = enemy.Id,
@ -259,6 +262,9 @@ namespace CustomComponent
Forward = enemy.CachedTransform.forward, Forward = enemy.CachedTransform.forward,
Speed = enemyData != null ? enemyData.SpeedBase : 0f, Speed = enemyData != null ? enemyData.SpeedBase : 0f,
AttackRange = 1f, AttackRange = 1f,
AvoidEnemyOverlap = movementComponent != null && movementComponent.AvoidEnemyOverlap,
EnemyBodyRadius = movementComponent != null ? movementComponent.EnemyBodyRadius : 0.45f,
SeparationIterations = movementComponent != null ? movementComponent.SeparationIterations : 2,
TargetType = 0, TargetType = 0,
State = 0 State = 0
}; };

View File

@ -9,4 +9,10 @@ public abstract class EnemyBase : TargetableObject
public abstract override ImpactData GetImpactData(); public abstract override ImpactData GetImpactData();
public virtual void SetTarget(Transform target) => _target = target; public virtual void SetTarget(Transform target) => _target = target;
protected bool IsSimulationMovementEnabled()
{
var simulationWorld = GameEntry.SimulationWorld;
return simulationWorld != null && simulationWorld.UseSimulationMovement;
}
} }

View File

@ -54,6 +54,11 @@ namespace Entity
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{ {
if (IsSimulationMovementEnabled())
{
return;
}
base.OnUpdate(elapseSeconds, realElapseSeconds); base.OnUpdate(elapseSeconds, realElapseSeconds);
if (_target == null) if (_target == null)

View File

@ -50,6 +50,11 @@ namespace Entity
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{ {
if (IsSimulationMovementEnabled())
{
return;
}
base.OnUpdate(elapseSeconds, realElapseSeconds); base.OnUpdate(elapseSeconds, realElapseSeconds);
if (_target == null) if (_target == null)

View File

@ -5,7 +5,8 @@ using DataTable;
using Entity; using Entity;
using GameFramework.Fsm; using GameFramework.Fsm;
using GameFramework.Procedure; using GameFramework.Procedure;
using UnityGameFramework.Runtime; using Simulation;
using UnityEngine;
namespace Procedure namespace Procedure
{ {
@ -13,15 +14,15 @@ namespace Procedure
{ {
public override GameStateType GameStateType => GameStateType.Battle; public override GameStateType GameStateType => GameStateType.Battle;
private EnemyManagerComponent _enemyManager = null; private EnemyManagerComponent _enemyManager;
private int _currentLevel = 0; private int _currentLevel;
private float _levelTimeLeft = 0; private float _levelTimeLeft;
private Player Player => _procedureGame.Player; private Player Player => _procedureGame.Player;
private ProcedureGame _procedureGame = null; private ProcedureGame _procedureGame;
public void AddBattleDuration(float seconds) public void AddBattleDuration(float seconds)
{ {
@ -65,6 +66,13 @@ namespace Procedure
_enemyManager.OnUpdate(elapseSeconds, realElapseSeconds); _enemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
SimulationWorld simulationWorld = GameEntry.SimulationWorld;
if (simulationWorld != null)
{
Vector3 playerPosition = Player != null ? Player.CachedTransform.position : Vector3.zero;
simulationWorld.Tick(new SimulationTickContext(elapseSeconds, realElapseSeconds, playerPosition));
}
_levelTimeLeft -= elapseSeconds; _levelTimeLeft -= elapseSeconds;
GameEntry.Event.Fire(this, LevelProcessEventArgs.Create((int)_levelTimeLeft)); GameEntry.Event.Fire(this, LevelProcessEventArgs.Create((int)_levelTimeLeft));
} }

View File

@ -9,6 +9,9 @@ namespace Simulation
public Vector3 Forward; public Vector3 Forward;
public float Speed; public float Speed;
public float AttackRange; public float AttackRange;
public bool AvoidEnemyOverlap;
public float EnemyBodyRadius;
public int SeparationIterations;
public int TargetType; public int TargetType;
public int State; public int State;
} }

View File

@ -1,10 +1,20 @@
using System.Collections.Generic; using System.Collections.Generic;
using CustomUtility;
using Entity;
using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
namespace Simulation namespace Simulation
{ {
public sealed class SimulationWorld : GameFrameworkComponent public sealed class SimulationWorld : GameFrameworkComponent
{ {
private const float DefaultAttackRange = 1f;
private const int EnemyStateIdle = 0;
private const int EnemyStateChasing = 1;
private const int EnemyStateInAttackRange = 2;
[SerializeField] private bool _useSimulationMovement;
private readonly List<EnemySimData> _enemies = new List<EnemySimData>(); private readonly List<EnemySimData> _enemies = new List<EnemySimData>();
private readonly List<ProjectileSimData> _projectiles = new List<ProjectileSimData>(); private readonly List<ProjectileSimData> _projectiles = new List<ProjectileSimData>();
private readonly List<PickupSimData> _pickups = new List<PickupSimData>(); private readonly List<PickupSimData> _pickups = new List<PickupSimData>();
@ -16,6 +26,12 @@ namespace Simulation
public IReadOnlyList<EnemySimData> Enemies => _enemies; public IReadOnlyList<EnemySimData> Enemies => _enemies;
public IReadOnlyList<ProjectileSimData> Projectiles => _projectiles; public IReadOnlyList<ProjectileSimData> Projectiles => _projectiles;
public IReadOnlyList<PickupSimData> Pickups => _pickups; public IReadOnlyList<PickupSimData> Pickups => _pickups;
public bool UseSimulationMovement => _useSimulationMovement;
public void SetUseSimulationMovement(bool enabled)
{
_useSimulationMovement = enabled;
}
public int AddEnemy(in EnemySimData simData) public int AddEnemy(in EnemySimData simData)
{ {
@ -136,7 +152,12 @@ namespace Simulation
public void Tick(in SimulationTickContext context) public void Tick(in SimulationTickContext context)
{ {
_ = context; if (!_useSimulationMovement)
{
return;
}
TickEnemies(in context);
} }
public void Clear() public void Clear()
@ -149,5 +170,91 @@ namespace Simulation
ProjectileBinding.Clear(); ProjectileBinding.Clear();
PickupBinding.Clear(); PickupBinding.Clear();
} }
private void TickEnemies(in SimulationTickContext context)
{
if (_enemies.Count == 0 || context.DeltaTime <= 0f)
{
return;
}
Vector3 playerPosition = context.PlayerPosition;
playerPosition.y = 0f;
EntityComponent entityComponent = GameEntry.Entity;
for (int i = 0; i < _enemies.Count; i++)
{
EnemySimData enemy = _enemies[i];
EnemyBase enemyEntity = null;
Transform enemyTransform = null;
if (entityComponent != null &&
entityComponent.GetGameEntity(enemy.EntityId) is EnemyBase runtimeEnemy &&
runtimeEnemy.Available)
{
enemyEntity = runtimeEnemy;
enemyTransform = runtimeEnemy.CachedTransform;
}
Vector3 currentPosition = enemy.Position;
currentPosition.y = 0f;
Vector3 toPlayer = playerPosition - currentPosition;
float sqrDistance = toPlayer.sqrMagnitude;
float attackRange = enemy.AttackRange > 0f ? enemy.AttackRange : DefaultAttackRange;
float attackRangeSqr = attackRange * attackRange;
if (sqrDistance <= attackRangeSqr)
{
enemy.State = EnemyStateInAttackRange;
}
else if (enemy.Speed <= 0f || sqrDistance <= float.Epsilon)
{
enemy.State = EnemyStateIdle;
}
else
{
Vector3 forward = toPlayer.normalized;
enemy.Forward = forward;
Vector3 desiredPosition = enemy.Position + forward * enemy.Speed * context.DeltaTime;
if (enemy.AvoidEnemyOverlap && enemyTransform != null)
{
int separationIterations = enemy.SeparationIterations > 0 ? enemy.SeparationIterations : 1;
desiredPosition = EnemySeparationSolverProvider.Resolve(
enemyTransform,
desiredPosition,
forward,
separationIterations);
}
enemy.Position = desiredPosition;
enemy.State = EnemyStateChasing;
}
_enemies[i] = enemy;
if (enemyEntity != null)
{
ApplyEnemyPresentation(enemyEntity, enemy);
}
}
}
private static void ApplyEnemyPresentation(EnemyBase enemyEntity, in EnemySimData enemyData)
{
if (enemyEntity == null || !enemyEntity.Available)
{
return;
}
enemyEntity.CachedTransform.position = enemyData.Position;
Vector3 forward = enemyData.Forward;
forward.y = 0f;
if (forward.sqrMagnitude > float.Epsilon)
{
enemyEntity.CachedTransform.forward = forward.normalized;
}
}
} }
} }

View File

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Components;
using UnityEngine; using UnityEngine;
namespace CustomUtility namespace CustomUtility
@ -9,12 +8,11 @@ namespace CustomUtility
{ {
private struct Registration private struct Registration
{ {
public Transform Transform;
public float BodyRadius; public float BodyRadius;
} }
private static IEnemySeparationSolver _current = new GridBucketEnemySeparationSolver(); private static IEnemySeparationSolver _current = new GridBucketEnemySeparationSolver();
private static readonly Dictionary<MovementComponent, Registration> Registrations = new(); private static readonly Dictionary<Transform, Registration> Registrations = new();
public static IEnemySeparationSolver Current => _current; public static IEnemySeparationSolver Current => _current;
public static string CurrentSolverName => _current.GetType().Name; public static string CurrentSolverName => _current.GetType().Name;
@ -36,42 +34,41 @@ namespace CustomUtility
SetSolver(new NaiveEnemySeparationSolver()); SetSolver(new NaiveEnemySeparationSolver());
} }
public static void Register(MovementComponent mover, Transform transform, float bodyRadius) public static void Register(Transform transform, float bodyRadius)
{ {
if (mover == null || transform == null) return; if (transform == null) return;
var registration = new Registration var registration = new Registration
{ {
Transform = transform,
BodyRadius = bodyRadius BodyRadius = bodyRadius
}; };
Registrations[mover] = registration; Registrations[transform] = registration;
_current.Register(mover, transform, bodyRadius); _current.Register(transform, bodyRadius);
} }
public static void Unregister(MovementComponent mover) public static void Unregister(Transform transform)
{ {
if (mover == null) return; if (transform == null) return;
_current.Unregister(mover); _current.Unregister(transform);
Registrations.Remove(mover); Registrations.Remove(transform);
} }
public static Vector3 Resolve(MovementComponent mover, Vector3 desiredPosition, Vector3 fallbackDirection, public static Vector3 Resolve(Transform transform, Vector3 desiredPosition, Vector3 fallbackDirection,
int iterations) int iterations)
{ {
return _current.Resolve(mover, desiredPosition, fallbackDirection, iterations); return _current.Resolve(transform, desiredPosition, fallbackDirection, iterations);
} }
private static void ReRegisterAll() private static void ReRegisterAll()
{ {
foreach (var pair in Registrations) foreach (var pair in Registrations)
{ {
MovementComponent mover = pair.Key; Transform transform = pair.Key;
Registration registration = pair.Value; Registration registration = pair.Value;
if (mover == null || registration.Transform == null) continue; if (transform == null) continue;
_current.Register(mover, registration.Transform, registration.BodyRadius); _current.Register(transform, registration.BodyRadius);
} }
} }
} }

View File

@ -1,4 +1,3 @@
using Components;
using UnityEngine; using UnityEngine;
namespace CustomUtility namespace CustomUtility
@ -14,12 +13,12 @@ namespace CustomUtility
public int CellZ; public int CellZ;
} }
private readonly System.Collections.Generic.Dictionary<MovementComponent, Agent> _agents = new(); private readonly System.Collections.Generic.Dictionary<Transform, Agent> _agents = new();
private readonly System.Collections.Generic.Dictionary<long, System.Collections.Generic.List<MovementComponent>> private readonly System.Collections.Generic.Dictionary<long, System.Collections.Generic.List<Transform>>
_buckets = new(); _buckets = new();
private readonly System.Collections.Generic.List<MovementComponent> _recycle = new(); private readonly System.Collections.Generic.List<Transform> _recycle = new();
private readonly float _cellSize; private readonly float _cellSize;
private int _snapshotFrame = -1; private int _snapshotFrame = -1;
@ -30,14 +29,14 @@ namespace CustomUtility
_cellSize = Mathf.Max(0.1f, cellSize); _cellSize = Mathf.Max(0.1f, cellSize);
} }
public void Register(MovementComponent mover, Transform transform, float bodyRadius) public void Register(Transform transform, float bodyRadius)
{ {
if (mover == null || transform == null) return; if (transform == null) return;
if (!_agents.TryGetValue(mover, out var agent)) if (!_agents.TryGetValue(transform, out var agent))
{ {
agent = new Agent(); agent = new Agent();
_agents.Add(mover, agent); _agents.Add(transform, agent);
} }
agent.Transform = transform; agent.Transform = transform;
@ -50,22 +49,22 @@ namespace CustomUtility
_snapshotFrame = -1; _snapshotFrame = -1;
} }
public void Unregister(MovementComponent mover) public void Unregister(Transform transform)
{ {
if (mover == null) return; if (transform == null) return;
if (!_agents.TryGetValue(mover, out var agent)) return; if (!_agents.TryGetValue(transform, out var agent)) return;
RemoveFromBucket(mover, agent.CellX, agent.CellZ); RemoveFromBucket(transform, agent.CellX, agent.CellZ);
_agents.Remove(mover); _agents.Remove(transform);
RecalculateMaxRadius(); RecalculateMaxRadius();
_snapshotFrame = -1; _snapshotFrame = -1;
} }
public Vector3 Resolve(MovementComponent mover, Vector3 desiredPosition, Vector3 fallbackDirection, public Vector3 Resolve(Transform transform, Vector3 desiredPosition, Vector3 fallbackDirection,
int iterations) int iterations)
{ {
if (mover == null) return desiredPosition; if (transform == null) return desiredPosition;
if (!_agents.TryGetValue(mover, out var self)) return desiredPosition; if (!_agents.TryGetValue(transform, out var self)) return desiredPosition;
EnsureSnapshot(); EnsureSnapshot();
@ -90,9 +89,9 @@ namespace CustomUtility
for (int i = 0; i < bucket.Count; i++) for (int i = 0; i < bucket.Count; i++)
{ {
MovementComponent otherMover = bucket[i]; Transform otherTransform = bucket[i];
if (otherMover == mover) continue; if (otherTransform == transform) continue;
if (!_agents.TryGetValue(otherMover, out var other)) continue; if (!_agents.TryGetValue(otherTransform, out var other)) continue;
Vector3 toSelf = candidate - other.Position; Vector3 toSelf = candidate - other.Position;
float minDistance = self.Radius + other.Radius; float minDistance = self.Radius + other.Radius;
@ -115,7 +114,7 @@ namespace CustomUtility
} }
} }
SyncAgentPosition(mover, self, candidate); SyncAgentPosition(transform, self, candidate);
candidate.y = desiredPosition.y; candidate.y = desiredPosition.y;
return candidate; return candidate;
@ -132,11 +131,11 @@ namespace CustomUtility
foreach (var pair in _agents) foreach (var pair in _agents)
{ {
MovementComponent mover = pair.Key; Transform transform = pair.Key;
Agent agent = pair.Value; Agent agent = pair.Value;
if (mover == null || agent.Transform == null) if (transform == null || agent.Transform == null)
{ {
_recycle.Add(mover); _recycle.Add(transform);
continue; continue;
} }
@ -146,7 +145,7 @@ namespace CustomUtility
agent.CellX = ToCell(position.x); agent.CellX = ToCell(position.x);
agent.CellZ = ToCell(position.z); agent.CellZ = ToCell(position.z);
AddToBucket(mover, agent.CellX, agent.CellZ); AddToBucket(transform, agent.CellX, agent.CellZ);
} }
for (int i = 0; i < _recycle.Count; i++) for (int i = 0; i < _recycle.Count; i++)
@ -155,15 +154,15 @@ namespace CustomUtility
} }
} }
private void SyncAgentPosition(MovementComponent mover, Agent agent, Vector3 position) private void SyncAgentPosition(Transform transform, Agent agent, Vector3 position)
{ {
int newCellX = ToCell(position.x); int newCellX = ToCell(position.x);
int newCellZ = ToCell(position.z); int newCellZ = ToCell(position.z);
if (agent.CellX != newCellX || agent.CellZ != newCellZ) if (agent.CellX != newCellX || agent.CellZ != newCellZ)
{ {
RemoveFromBucket(mover, agent.CellX, agent.CellZ); RemoveFromBucket(transform, agent.CellX, agent.CellZ);
AddToBucket(mover, newCellX, newCellZ); AddToBucket(transform, newCellX, newCellZ);
agent.CellX = newCellX; agent.CellX = newCellX;
agent.CellZ = newCellZ; agent.CellZ = newCellZ;
} }
@ -171,24 +170,24 @@ namespace CustomUtility
agent.Position = position; agent.Position = position;
} }
private void AddToBucket(MovementComponent mover, int cellX, int cellZ) private void AddToBucket(Transform transform, int cellX, int cellZ)
{ {
long key = CellKey(cellX, cellZ); long key = CellKey(cellX, cellZ);
if (!_buckets.TryGetValue(key, out var list)) if (!_buckets.TryGetValue(key, out var list))
{ {
list = new System.Collections.Generic.List<MovementComponent>(8); list = new System.Collections.Generic.List<Transform>(8);
_buckets.Add(key, list); _buckets.Add(key, list);
} }
list.Add(mover); list.Add(transform);
} }
private void RemoveFromBucket(MovementComponent mover, int cellX, int cellZ) private void RemoveFromBucket(Transform transform, int cellX, int cellZ)
{ {
long key = CellKey(cellX, cellZ); long key = CellKey(cellX, cellZ);
if (!_buckets.TryGetValue(key, out var list)) return; if (!_buckets.TryGetValue(key, out var list)) return;
list.Remove(mover); list.Remove(transform);
if (list.Count == 0) if (list.Count == 0)
{ {
_buckets.Remove(key); _buckets.Remove(key);

View File

@ -1,12 +1,11 @@
using Components;
using UnityEngine; using UnityEngine;
namespace CustomUtility namespace CustomUtility
{ {
public interface IEnemySeparationSolver public interface IEnemySeparationSolver
{ {
void Register(MovementComponent mover, Transform transform, float bodyRadius); void Register(Transform transform, float bodyRadius);
void Unregister(MovementComponent mover); void Unregister(Transform transform);
Vector3 Resolve(MovementComponent mover, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations); Vector3 Resolve(Transform transform, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations);
} }
} }

View File

@ -1,4 +1,3 @@
using Components;
using UnityEngine; using UnityEngine;
namespace CustomUtility namespace CustomUtility
@ -11,33 +10,33 @@ namespace CustomUtility
public float Radius; public float Radius;
} }
private readonly System.Collections.Generic.Dictionary<MovementComponent, Agent> _agents = new(); private readonly System.Collections.Generic.Dictionary<Transform, Agent> _agents = new();
private readonly System.Collections.Generic.List<MovementComponent> _agentKeys = new(); private readonly System.Collections.Generic.List<Transform> _agentKeys = new();
public void Register(MovementComponent mover, Transform transform, float bodyRadius) public void Register(Transform transform, float bodyRadius)
{ {
if (mover == null || transform == null) return; if (transform == null) return;
if (!_agents.TryGetValue(mover, out var agent)) if (!_agents.TryGetValue(transform, out var agent))
{ {
agent = new Agent(); agent = new Agent();
_agents.Add(mover, agent); _agents.Add(transform, agent);
} }
agent.Transform = transform; agent.Transform = transform;
agent.Radius = Mathf.Max(0.01f, bodyRadius); agent.Radius = Mathf.Max(0.01f, bodyRadius);
} }
public void Unregister(MovementComponent mover) public void Unregister(Transform transform)
{ {
if (mover == null) return; if (transform == null) return;
_agents.Remove(mover); _agents.Remove(transform);
} }
public Vector3 Resolve(MovementComponent mover, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations) public Vector3 Resolve(Transform transform, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations)
{ {
if (mover == null) return desiredPosition; if (transform == null) return desiredPosition;
if (!_agents.TryGetValue(mover, out var self)) return desiredPosition; if (!_agents.TryGetValue(transform, out var self)) return desiredPosition;
Vector3 candidate = desiredPosition; Vector3 candidate = desiredPosition;
candidate.y = 0f; candidate.y = 0f;
@ -56,9 +55,9 @@ namespace CustomUtility
{ {
for (int i = 0; i < _agentKeys.Count; i++) for (int i = 0; i < _agentKeys.Count; i++)
{ {
MovementComponent otherMover = _agentKeys[i]; Transform otherTransform = _agentKeys[i];
if (otherMover == mover) continue; if (otherTransform == transform) continue;
if (!_agents.TryGetValue(otherMover, out var other)) continue; if (!_agents.TryGetValue(otherTransform, out var other)) continue;
if (other.Transform == null) continue; if (other.Transform == null) continue;
Vector3 otherPosition = other.Transform.position; Vector3 otherPosition = other.Transform.position;

View File

@ -37,13 +37,13 @@
- `EnemyManagerComponent` 继续负责刷怪与实体显隐,不改外部调用方式。 - `EnemyManagerComponent` 继续负责刷怪与实体显隐,不改外部调用方式。
- 完成标准:敌人数量统计与当前一致,无重复注册、无悬空索引。 - 完成标准:敌人数量统计与当前一致,无重复注册、无悬空索引。
- [ ] Checkpoint 3建立 Simulation 主更新入口并接入 Battle 状态 - [x] Checkpoint 3建立 Simulation 主更新入口并接入 Battle 状态
- 在 `GameStateBattle.OnUpdate` 中增加 `SimulationWorld.Tick(...)` 调用。 - 在 `GameStateBattle.OnUpdate` 中增加 `SimulationWorld.Tick(...)` 调用。
- 先只接“敌人移动/追踪”系统,其他逻辑保持原路径。 - 先只接“敌人移动/追踪”系统,其他逻辑保持原路径。
- 增加开关(建议 `UseSimulationMovement`)用于 A/B 对比与回滚。 - 增加开关(建议 `UseSimulationMovement`)用于 A/B 对比与回滚。
- 完成标准:关闭开关与当前行为一致;开启开关后敌人仍能正常追踪玩家。 - 完成标准:关闭开关与当前行为一致;开启开关后敌人仍能正常追踪玩家。
- [ ] Checkpoint 4迁移敌人核心移动逻辑到 Simulation去 MonoBehaviour 核心逻辑) - [x] Checkpoint 4迁移敌人核心移动逻辑到 Simulation去 MonoBehaviour 核心逻辑)
- 将 `MeleeEnemy/RemoteEnemy` 的目标追踪、移动方向、攻击距离判定迁至 Simulation。 - 将 `MeleeEnemy/RemoteEnemy` 的目标追踪、移动方向、攻击距离判定迁至 Simulation。
- `EnemySimData` 至少包含:`position`、`forward`、`speed`、`attackRange`、`targetType`、`state`。 - `EnemySimData` 至少包含:`position`、`forward`、`speed`、`attackRange`、`targetType`、`state`。
- `MeleeEnemy/RemoteEnemy.OnUpdate` 仅保留表现层或空实现(不再做核心移动计算)。 - `MeleeEnemy/RemoteEnemy.OnUpdate` 仅保留表现层或空实现(不再做核心移动计算)。