refactor(enemy): 消费者切换到 IEnemyNavigator(Wander/Waypoints 去导航图化,调试面板简化)
This commit is contained in:
@@ -13,7 +13,7 @@ namespace BaseGames.Enemies
|
||||
/// 敌人基类(架构 07_EnemyModule §1)。
|
||||
/// 实现 IDamageable,为 Behavior Designer 任务提供统一虚方法接口。
|
||||
/// 包含:BD 接口、受击、死亡流程。
|
||||
/// ⚠️ _nav 字段类型为 IPathAgent(在 BaseGames.Enemies.Navigation 中实现具体类)。
|
||||
/// ⚠️ _nav 字段类型为 IEnemyNavigator(在 BaseGames.Enemies.Navigation 中实现具体类)。
|
||||
/// 实现 IPoolable:配合 PooledObject 支持对象池复用,避免频繁 Destroy/Instantiate。
|
||||
/// </summary>
|
||||
public class EnemyBase : MonoBehaviour, IDamageable, IPoolable
|
||||
@@ -60,10 +60,10 @@ namespace BaseGames.Enemies
|
||||
/// </summary>
|
||||
[SerializeField] private BaseGames.Core.Events.TransformEventChannelSO _onPlayerSpawned;
|
||||
|
||||
// ── 导航代理(IPathAgent;由 EnemyNavAgent 实现)───────────────────
|
||||
// ── 导航代理(IEnemyNavigator;由 GroundNavigator/FlyingNavigator 实现)─────
|
||||
// 通过接口引用,避免对 Navigation 程序集的直接依赖。
|
||||
// 由子类 / Inspector 注入,或者运行时 GetComponent<IPathAgent>() 获取。
|
||||
protected IPathAgent _nav;
|
||||
// 由子类 / Inspector 注入,或者运行时 GetComponent<IEnemyNavigator>() 获取。
|
||||
protected IEnemyNavigator _nav;
|
||||
|
||||
// 移动执行器(IEnemyLocomotion;由 EnemyLocomotion 在 Navigation 程序集实现,运行时发现)
|
||||
protected IEnemyLocomotion _locomotion;
|
||||
@@ -216,7 +216,7 @@ namespace BaseGames.Enemies
|
||||
}
|
||||
|
||||
// BD 任务访问接口(公共只读属性)────────────────────────────────
|
||||
public IPathAgent Nav => _nav;
|
||||
public IEnemyNavigator Nav => _nav;
|
||||
// 惰性发现兜底:关域重载后 Awake 设的实例字段可能残留为 null(编辑器快速迭代),
|
||||
// 首次访问时按需补发现,保证编辑器与构建下都不为空。
|
||||
public IEnemyLocomotion Locomotion => _locomotion ?? (_locomotion = GetComponentInChildren<IEnemyLocomotion>(true));
|
||||
@@ -256,7 +256,7 @@ namespace BaseGames.Enemies
|
||||
// ── BD 行为树接口(虚方法)────────────────────────────────────────
|
||||
|
||||
public virtual void MoveTo(Vector2 target)
|
||||
=> _nav?.RequestMoveTo(target);
|
||||
=> _nav?.MoveTowards(target);
|
||||
|
||||
public virtual void MoveInDirection(float dir)
|
||||
{
|
||||
@@ -284,7 +284,7 @@ namespace BaseGames.Enemies
|
||||
|
||||
public virtual void StopMovement()
|
||||
{
|
||||
_nav?.StopNavigation();
|
||||
_nav?.Stop();
|
||||
if (_movement != null) _movement.PendingInput.WantStop = true;
|
||||
}
|
||||
|
||||
@@ -547,7 +547,7 @@ namespace BaseGames.Enemies
|
||||
_stateObjs[EnemyStateType.KnockUp] = new EnemyKnockUpState();
|
||||
_stateObjs[EnemyStateType.Dead] = new EnemyDeadState();
|
||||
|
||||
_nav = GetComponent<IPathAgent>() ?? new NullPathAgent();
|
||||
_nav = GetComponent<IEnemyNavigator>() ?? new NullEnemyNavigator();
|
||||
_locomotion = GetComponentInChildren<IEnemyLocomotion>(true);
|
||||
if (_locomotion == null)
|
||||
Debug.LogError($"EnemyBase 未找到 EnemyLocomotion 组件:{name}", this);
|
||||
@@ -771,7 +771,7 @@ namespace BaseGames.Enemies
|
||||
public virtual void OnDespawn()
|
||||
{
|
||||
_abilities.InterruptAll(InterruptReason.Dead);
|
||||
_nav?.StopNavigation();
|
||||
_nav?.Stop();
|
||||
// GO 停用时 EnemyAiBrain.Update 自然停止,无需显式处理。
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace BaseGames.Enemies
|
||||
/// <summary>
|
||||
/// 飞行敌人基类。
|
||||
///
|
||||
/// 导航由 <see cref="Navigation.FlyingDirectNavigator"/> 实现(IPathAgent),
|
||||
/// 导航由 <see cref="Navigation.FlyingNavigator"/> 实现(IEnemyNavigator),
|
||||
/// AI 行为逻辑由挂载的 Behavior Designer 树驱动。
|
||||
/// 本类仅负责:
|
||||
/// <list type="bullet">
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// 敌人移动执行器:唯一的移动/朝向入口。AI 状态与能力经 IEnemyLocomotion
|
||||
/// 声明意图,本组件每帧把当前模式翻译为对 EnemyBase/IPathAgent 的调用。
|
||||
/// 声明意图,本组件每帧把当前模式翻译为对 EnemyBase/IEnemyNavigator 的调用。
|
||||
/// 取代散落的 MoveTo/StopMovement/FacePlayer/FaceTarget 直调与三个协程能力。
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
@@ -38,9 +38,8 @@ namespace BaseGames.Enemies
|
||||
private int _wpIndex = -1;
|
||||
private int _wpDir = 1;
|
||||
private bool _warnedNoWaypoints;
|
||||
// 当前路点吸附后的"站得住"目标(宽度由 Nav 层解析,本层不感知身体尺寸)
|
||||
// 当前路点坐标(导航图已移除,直接用原始坐标;走不到会被夹停)
|
||||
private Vector2 _wpResolvedGoal;
|
||||
private bool _wpResolveOk; // 上次路点是否成功吸附到导航段(调试用)
|
||||
// Wander 到点停顿状态
|
||||
private bool _wanderPausing;
|
||||
private float _wanderPauseTimer;
|
||||
@@ -141,17 +140,19 @@ namespace BaseGames.Enemies
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 段内随机游走:随机点限制在当前所站的导航段上(不跨 NavLink,避免崖边夹停/卡 link),
|
||||
/// 到达一个点后原地停顿 [_wanderPauseMin, _wanderPauseMax] 秒(期间播 Idle),再挑下一个点。
|
||||
/// 就近随机游走:朝随机取的目标点走,**到达或被地形夹停**即视为本次游走结束,
|
||||
/// 停顿 [_wanderPauseMin, _wanderPauseMax] 秒(期间播 Idle)后再挑下一个点。
|
||||
/// 走到平台边缘停下转身是预期表现——可达性由移动层夹紧决定,不做地形扫描。
|
||||
/// </summary>
|
||||
private void TickWander()
|
||||
{
|
||||
if (_enemy.Nav == null) return;
|
||||
var nav = _enemy.Nav;
|
||||
if (nav == null) return;
|
||||
|
||||
// 移动中:清停顿态,等到达
|
||||
if (_enemy.Nav.IsMoving) { _wanderPausing = false; return; }
|
||||
// 仍在推进:清停顿态,等到达/受阻
|
||||
if (nav.IsMoving) { _wanderPausing = false; return; }
|
||||
|
||||
// 已到达(或尚未出发):先停顿计时,再挑下一个点
|
||||
// 到达、受阻或尚未出发:先停顿计时,再挑下一个点
|
||||
if (!_wanderPausing)
|
||||
{
|
||||
_wanderPausing = true;
|
||||
@@ -162,7 +163,7 @@ namespace BaseGames.Enemies
|
||||
if (_wanderPauseTimer <= 0f)
|
||||
{
|
||||
_wanderPausing = false;
|
||||
_enemy.Nav.WalkToRandomOnSegment();
|
||||
if (nav.TryPickWanderPoint(out var point)) nav.MoveTowards(point);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +214,7 @@ namespace BaseGames.Enemies
|
||||
Vector2.Distance(_enemy.transform.position, _wpResolvedGoal) <= _waypointArriveRadius;
|
||||
|
||||
// 受阻结算:执行层已把敌人带到最近可达点并停下(或原地停) → 视为本路点完成。
|
||||
bool obstructedSettled = nav != null && nav.LastMoveObstructed && !nav.IsMoving;
|
||||
bool obstructedSettled = nav != null && nav.IsBlocked;
|
||||
|
||||
if (_wpIndex < 0 || wpArrived || obstructedSettled)
|
||||
{
|
||||
@@ -232,31 +233,21 @@ namespace BaseGames.Enemies
|
||||
AdvanceWaypoint();
|
||||
SetWaypointGoal();
|
||||
}
|
||||
else if (nav != null && !nav.IsMoving && !nav.LastMoveObstructed)
|
||||
else if (nav != null && !nav.IsMoving && !nav.IsBlocked && !nav.HasArrived)
|
||||
{
|
||||
// 瞬态:刚出生尚未映射那一帧的 MoveTo 失败 → 重发(RequestMoveTo 自带 0.25s 防抖)。
|
||||
// 瞬态:刚出生尚未映射那一帧的 MoveTo 失败 → 重发(MoveTowards 自带防抖)。
|
||||
// 仅"未受阻"时重发;受阻由上面 obstructedSettled 分支结算,不再无限重发不可达点。
|
||||
_enemy.MoveTo(_wpResolvedGoal);
|
||||
nav.MoveTowards(_wpResolvedGoal);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析当前路点为"身体站得住的可达点"并出发。宽度处理下沉给 Nav 层(<see cref="IPathAgent.ResolveStandablePoint"/>),
|
||||
/// 本层只递原始路点坐标、不感知身体尺寸。吸附失败=路点摆得离导航面太远,显式报错暴露根因,不静默兜底。
|
||||
/// </summary>
|
||||
/// <summary>取当前路点坐标并出发。路点若摆在崖外/墙后,敌人会走到边缘被夹停 → IsBlocked → 结算推进下一个。</summary>
|
||||
private void SetWaypointGoal()
|
||||
{
|
||||
Vector2 raw = _waypoints[_wpIndex].position;
|
||||
_wpResolveOk = _enemy.Nav != null && _enemy.Nav.ResolveStandablePoint(raw, out _wpResolvedGoal);
|
||||
if (!_wpResolveOk)
|
||||
{
|
||||
_wpResolvedGoal = raw;
|
||||
Debug.LogWarning($"[EnemyLocomotion] 路点 '{_waypoints[_wpIndex].name}'(index {_wpIndex}) 无法吸附到任何可行走导航段" +
|
||||
"(超出搜索半径)。请检查该路点是否摆放在贴近地面/平台的可行走处。", this);
|
||||
}
|
||||
_enemy.MoveTo(_wpResolvedGoal);
|
||||
_wpResolvedGoal = _waypoints[_wpIndex].position;
|
||||
_enemy.Nav?.MoveTowards(_wpResolvedGoal);
|
||||
}
|
||||
|
||||
private void AdvanceWaypoint()
|
||||
@@ -280,7 +271,6 @@ namespace BaseGames.Enemies
|
||||
public Transform[] DebugWaypoints => _waypoints;
|
||||
public int DebugWaypointIndex => _wpIndex;
|
||||
public Vector2 DebugResolvedGoal => _wpResolvedGoal;
|
||||
public bool DebugResolveOk => _wpResolveOk;
|
||||
public float DebugArriveRadius => _waypointArriveRadius;
|
||||
public bool DebugWanderPausing => _wanderPausing;
|
||||
|
||||
@@ -298,11 +288,11 @@ namespace BaseGames.Enemies
|
||||
if (next != null) Gizmos.DrawLine(_waypoints[i].position, next.position);
|
||||
}
|
||||
|
||||
// 运行期:当前吸附目标 + 到达半径 + 连线(绿=吸附成功 红=失败回落原始点)
|
||||
// 运行期:当前目标点 + 到达半径 + 连线
|
||||
if (Application.isPlaying && _wpIndex >= 0)
|
||||
{
|
||||
Vector3 from = _enemy != null ? _enemy.transform.position : transform.position;
|
||||
Gizmos.color = _wpResolveOk ? Color.green : Color.red;
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawWireSphere(_wpResolvedGoal, 0.22f);
|
||||
Gizmos.DrawLine(from, _wpResolvedGoal);
|
||||
Gizmos.color = new Color(1f, 0.9f, 0.2f, 0.7f);
|
||||
|
||||
Reference in New Issue
Block a user