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
@@ -9,6 +9,9 @@ namespace Simulation
public Vector3 Forward;
public float Speed;
public float AttackRange;
public bool AvoidEnemyOverlap;
public float EnemyBodyRadius;
public int SeparationIterations;
public int TargetType;
public int State;
}
@@ -1,10 +1,20 @@
using System.Collections.Generic;
using CustomUtility;
using Entity;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace Simulation
{
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<ProjectileSimData> _projectiles = new List<ProjectileSimData>();
private readonly List<PickupSimData> _pickups = new List<PickupSimData>();
@@ -16,6 +26,12 @@ namespace Simulation
public IReadOnlyList<EnemySimData> Enemies => _enemies;
public IReadOnlyList<ProjectileSimData> Projectiles => _projectiles;
public IReadOnlyList<PickupSimData> Pickups => _pickups;
public bool UseSimulationMovement => _useSimulationMovement;
public void SetUseSimulationMovement(bool enabled)
{
_useSimulationMovement = enabled;
}
public int AddEnemy(in EnemySimData simData)
{
@@ -136,7 +152,12 @@ namespace Simulation
public void Tick(in SimulationTickContext context)
{
_ = context;
if (!_useSimulationMovement)
{
return;
}
TickEnemies(in context);
}
public void Clear()
@@ -149,5 +170,91 @@ namespace Simulation
ProjectileBinding.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;
}
}
}
}