#if GRAPH_DESIGNER using UnityEngine; using Opsive.BehaviorDesigner.Runtime.Tasks; using Opsive.BehaviorDesigner.Runtime.Tasks.Actions; using BaseGames.Enemies; namespace BaseGames.Enemies.AI { /// /// BD Action:多步搜查(Navigate → LookAround → RandomWalk × N)。 /// /// /// Navigate:导航至最后可见位置。 /// LookAround:到达后原地环顾(播放 Look 动画,持续 LookAroundDuration)。 /// WalkRandom:向搜查半径内的随机点移动,重复 RandomStepCount 次。 /// 任意阶段重新发现玩家 → 返回 Failure,BD 树重入追击。 /// 所有步骤完成仍未发现 → 返回 Success,BD 树归位/恢复巡逻。 /// /// [TaskName("Investigate Last Known Pos")] [TaskCategory("BaseGames/Enemy/Movement")] [TaskDescription("多步搜查:导航到最后已知位置 → 环顾四周 → 随机游走 N 次")] public sealed class BD_InvestigateLastKnown : Action { [Tooltip("到达搜查点后环顾的时长(s)")] [SerializeField] private float m_LookAroundDuration = 0.8f; [Tooltip("随机行走步数(完成环顾后随机游荡的次数)")] [SerializeField] [Min(0)] private int m_RandomStepCount = 2; [Tooltip("随机行走半径(m)")] [SerializeField] private float m_SearchRadius = 2.5f; [Tooltip("到达目标点的判定半径(m)")] [SerializeField] private float m_ArriveRadius = 0.6f; private enum InvestigateSubStep { Navigate, LookAround, WalkRandom } private EnemyBase _enemy; private InvestigateSubStep _step; private float _stepTimer; private int _stepsRemaining; private Vector2 _randomTarget; private bool _pathFailed; private bool _subscribed; public override void OnAwake() => _enemy = gameObject.GetComponent(); public override void OnStart() { _enemy.SetAiPhase(AiPhase.Investigate); _step = InvestigateSubStep.Navigate; _stepTimer = 0f; _stepsRemaining = m_RandomStepCount; _pathFailed = false; var ac = _enemy.AnimConfig; if (_enemy.Animancer != null) { var clip = ac?.Investigate ?? ac?.Walk; if (clip != null) _enemy.Animancer.Play(clip); } if (!_subscribed && _enemy.Nav != null) { _enemy.Nav.OnNavPathFailed += HandlePathFailed; _subscribed = true; } _enemy.MoveTo(_enemy.LastKnownPlayerPosition); } public override TaskStatus OnUpdate() { if (_enemy == null) return TaskStatus.Failure; if (_enemy.IsPlayerVisible()) return TaskStatus.Failure; switch (_step) { case InvestigateSubStep.Navigate: return UpdateNavigate(); case InvestigateSubStep.LookAround: return UpdateLookAround(); case InvestigateSubStep.WalkRandom: return UpdateWalkRandom(); default: return TaskStatus.Success; } } public override void OnEnd() { if (_subscribed && _enemy?.Nav != null) { _enemy.Nav.OnNavPathFailed -= HandlePathFailed; _subscribed = false; } _enemy?.StopMovement(); } // ── 阶段逻辑 ──────────────────────────────────────────────────── private TaskStatus UpdateNavigate() { Vector2 self = _enemy.transform.position; bool arrived = _pathFailed || (self - _enemy.LastKnownPlayerPosition).sqrMagnitude <= m_ArriveRadius * m_ArriveRadius; if (!arrived) return TaskStatus.Running; _enemy.StopMovement(); EnterLookAround(); return TaskStatus.Running; } private TaskStatus UpdateLookAround() { _stepTimer += Time.deltaTime; if (_stepTimer < m_LookAroundDuration) return TaskStatus.Running; if (_stepsRemaining > 0) EnterRandomWalk(); else return TaskStatus.Success; return TaskStatus.Running; } private TaskStatus UpdateWalkRandom() { Vector2 self = _enemy.transform.position; bool arrived = _pathFailed || (self - _randomTarget).sqrMagnitude <= m_ArriveRadius * m_ArriveRadius; if (!arrived) return TaskStatus.Running; _enemy.StopMovement(); _stepsRemaining--; _pathFailed = false; if (_stepsRemaining > 0) EnterRandomWalk(); else return TaskStatus.Success; return TaskStatus.Running; } // ── 辅助方法 ──────────────────────────────────────────────────── private void EnterLookAround() { _step = InvestigateSubStep.LookAround; _stepTimer = 0f; var ac = _enemy.AnimConfig; if (_enemy.Animancer != null) { var clip = ac?.Investigate ?? ac?.Idle; if (clip != null) _enemy.Animancer.Play(clip); } } private void EnterRandomWalk() { _step = InvestigateSubStep.WalkRandom; _pathFailed = false; Vector2 origin = _enemy.LastKnownPlayerPosition; // 横板地形中只在水平方向随机偏移,保留 origin.y; // PathBerserker2d 寻路负责处理跨平台的纵向路径规划。 float dir = Random.value > 0.5f ? 1f : -1f; float dist = Random.Range(0.5f, m_SearchRadius); _randomTarget = new Vector2(origin.x + dir * dist, origin.y); _enemy.MoveTo(_randomTarget); var ac = _enemy.AnimConfig; if (_enemy.Animancer != null) { var clip = ac?.Walk; if (clip != null) _enemy.Animancer.Play(clip); } } private void HandlePathFailed() => _pathFailed = true; } } #endif