Checkpoint 3 & Checkpoint 4:
- GameStateBattle 接入 Simulation 主更新入口 - SimulationWorld 增加开关 UseSimulationMovement(默认 false) - SimulationWorld.Tick(...) 现在在开关开启时执行敌人追踪/移动模拟,基本还原 MovementComponent 功能(敌人互斥) - 调整 IEnemySeparationSolver 系列方法,不再依赖 MovementComponent
This commit is contained in:
parent
83f8a356f7
commit
6494ebc5fd
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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` 仅保留表现层或空实现(不再做核心移动计算)。
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue