diff --git a/Assets/_Game/Scripts/Enemies/Navigation/BaseGames.Enemies.Navigation.asmdef b/Assets/_Game/Scripts/Enemies/Navigation/BaseGames.Enemies.Navigation.asmdef
index 97bce052..be5a1583 100644
--- a/Assets/_Game/Scripts/Enemies/Navigation/BaseGames.Enemies.Navigation.asmdef
+++ b/Assets/_Game/Scripts/Enemies/Navigation/BaseGames.Enemies.Navigation.asmdef
@@ -9,6 +9,7 @@
"rootNamespace": "BaseGames.Enemies.Navigation",
"references": [
"BaseGames.Enemies",
+ "BaseGames.AI",
"PathBerserker2d"
],
"autoReferenced": true,
diff --git a/Assets/_Game/Scripts/Enemies/Navigation/EnemyLocomotion.cs b/Assets/_Game/Scripts/Enemies/Navigation/EnemyLocomotion.cs
new file mode 100644
index 00000000..23495d6e
--- /dev/null
+++ b/Assets/_Game/Scripts/Enemies/Navigation/EnemyLocomotion.cs
@@ -0,0 +1,105 @@
+using UnityEngine;
+using BaseGames.AI;
+
+namespace BaseGames.Enemies
+{
+ ///
+ /// 敌人移动执行器:唯一的移动/朝向入口。AI 状态与能力经 IEnemyLocomotion
+ /// 声明意图,本组件每帧把当前模式翻译为对 EnemyBase/IPathAgent 的调用。
+ /// 取代散落的 MoveTo/StopMovement/FacePlayer/FaceTarget 直调与三个协程能力。
+ ///
+ [DisallowMultipleComponent]
+ public sealed class EnemyLocomotion : MonoBehaviour, IEnemyLocomotion
+ {
+ [Header("巡逻策略(P6 实现 Pace/Waypoints;当前仅 Wander)")]
+ [SerializeField] private PatrolStrategy _patrolStrategy = PatrolStrategy.Wander;
+
+ private EnemyBase _enemy;
+ private LocomotionMode _mode = LocomotionMode.Idle;
+ private Transform _approachTarget;
+ private Vector2 _facePoint;
+
+ public LocomotionMode CurrentMode => _mode;
+ public bool IsMoving => _enemy != null && _enemy.Nav != null && _enemy.Nav.IsMoving;
+
+ private void Awake()
+ {
+ _enemy = GetComponentInParent();
+ if (_enemy == null)
+ Debug.LogError("[EnemyLocomotion] 找不到 EnemyBase。", this);
+ }
+
+ // ── IEnemyLocomotion(声明意图,不立即 actuate 之外的副作用)──
+ public void SetMode(LocomotionMode mode)
+ {
+ if (_mode == mode) return;
+ _mode = mode;
+ if (mode == LocomotionMode.Idle) _enemy?.StopMovement();
+ if (mode == LocomotionMode.Patrol && _enemy?.Stats != null)
+ _enemy.Nav?.SetSpeed(_enemy.Stats.WalkSpeed);
+ }
+
+ public void Approach(Transform target)
+ {
+ _mode = LocomotionMode.Approach;
+ _approachTarget = target;
+ if (_enemy?.Stats != null) _enemy.Nav?.SetSpeed(_enemy.Stats.RunSpeed);
+ }
+
+ public void MoveTo(Vector2 point)
+ {
+ _mode = LocomotionMode.Approach;
+ _approachTarget = null;
+ _enemy?.MoveTo(point);
+ }
+
+ public void Face(Vector2 lookAt)
+ {
+ _mode = LocomotionMode.Face;
+ _facePoint = lookAt;
+ }
+
+ public void Stop()
+ {
+ _mode = LocomotionMode.Idle;
+ _enemy?.StopMovement();
+ }
+
+ // ── 每帧把模式翻译成执行 ──
+ private void Update()
+ {
+ if (_enemy == null) return;
+ switch (_mode)
+ {
+ case LocomotionMode.Idle:
+ break; // SetMode(Idle) 已停;保持
+ case LocomotionMode.Patrol:
+ TickPatrol();
+ break;
+ case LocomotionMode.Face:
+ _enemy.StopMovement();
+ _enemy.FaceTarget(_facePoint);
+ break;
+ case LocomotionMode.Approach:
+ if (_approachTarget != null) _enemy.MoveTo(_approachTarget.position);
+ break;
+ }
+ }
+
+ private void TickPatrol()
+ {
+ var nav = _enemy.Nav;
+ if (nav == null) return;
+ switch (_patrolStrategy)
+ {
+ case PatrolStrategy.Wander:
+ if (!nav.IsMoving) nav.WalkToRandom();
+ break;
+ // Pace / Waypoints:P6 实现
+ default:
+ if (!nav.IsMoving) nav.WalkToRandom();
+ break;
+ }
+ }
+ }
+}
diff --git a/Assets/_Game/Scripts/Enemies/Navigation/EnemyLocomotion.cs.meta b/Assets/_Game/Scripts/Enemies/Navigation/EnemyLocomotion.cs.meta
new file mode 100644
index 00000000..5a6dd929
--- /dev/null
+++ b/Assets/_Game/Scripts/Enemies/Navigation/EnemyLocomotion.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9201463979893884288d3645a607eb90
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: