#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:追击玩家(三阶段视线丢失模型)。 /// /// /// Tracking:持有视线,以奔跑速度追击并更新 LastKnownPlayerPosition。 /// Searching:视线丢失超过 LostSightBuffer(~0.3s)后进入。速度降低,向最后可见位置行进;若恢复视线即回到 Tracking。 /// Lost:Searching 持续超过 LoseLinkTimeout → 返回 Failure,让 BD 树切入 BD_InvestigateLastKnown。 /// /// /// 短暂遮挡(如绕过柱子)在 LostSightBuffer 内不会改变速度,避免追击手感割裂。 /// [TaskName("Chase Player")] [TaskCategory("BaseGames/Enemy/Movement")] [TaskDescription("三段式追击(Tracking→Searching→Lost);丢失视线后进入缓冲期")] public sealed class BD_ChasePlayer : Action { [Header("距离限制")] [Tooltip("追击距离上限(m);0 = 使用 EnemyStatsSO.MaxChaseDistance")] [SerializeField] [Min(0f)] private float m_MaxChaseDistance = 0f; [Header("视线丢失")] [Tooltip("视线丢失判定超时(s);0 = 使用 EnemyStatsSO.LoseLinkTimeout")] [SerializeField] [Min(0f)] private float m_LoseLinkTimeout = 0f; [Tooltip("视线刚丢失后的缓冲期(s):此期间保持追击速度,短暂遮挡不触发搜索模式")] [SerializeField] [Min(0f)] private float m_LostSightBuffer = 0.3f; [Header("路径规划")] [Tooltip("路径重规划阈值(m):玩家移动超过此距离后才重新规划,避免每帧请求")] [SerializeField] [Min(0.1f)] private float m_ReplanThreshold = 1.5f; [Header("搜索阶段")] [Tooltip("Searching 阶段速度倍率(相对于行走速度;<1 = 减速搜索)")] [SerializeField] [UnityEngine.Range(0.3f, 1.5f)] private float m_SearchSpeedMultiplier = 0.7f; private enum ChaseSubState { Tracking, Searching } private EnemyBase _enemy; private float _losTimer; private Vector2 _lastReplanPos; private ChaseSubState _subState; public override void OnAwake() => _enemy = gameObject.GetComponent(); public override void OnStart() { _enemy.SetAiPhase(AiPhase.Chase); _losTimer = 0f; _lastReplanPos = Vector2.positiveInfinity; _subState = ChaseSubState.Tracking; float runSpeed = _enemy.Stats?.RunSpeed ?? 4f; _enemy.Nav?.SetSpeed(runSpeed); var ac = _enemy.AnimConfig; if (_enemy.Animancer != null && ac?.Run != null) _enemy.Animancer.Play(ac.Run); } public override TaskStatus OnUpdate() { if (_enemy == null || _enemy.PlayerTransform == null) return TaskStatus.Failure; float maxDist = m_MaxChaseDistance > 0f ? m_MaxChaseDistance : (_enemy.Stats?.MaxChaseDistance ?? 15f); float loseTime = m_LoseLinkTimeout > 0f ? m_LoseLinkTimeout : (_enemy.Stats?.LoseLinkTimeout ?? 2f); // 超出最大追击距离 → 放弃追击 if (_enemy.Stats != null && _enemy.Stats.SqrDistanceToPlayer > maxDist * maxDist) return TaskStatus.Failure; Vector2 playerPos = _enemy.PlayerTransform.position; if (_enemy.IsPlayerVisible()) { // 视线恢复:Searching → Tracking,恢复奔跑速度 if (_subState == ChaseSubState.Searching) { _subState = ChaseSubState.Tracking; _enemy.Nav?.SetSpeed(_enemy.Stats?.RunSpeed ?? 4f); } _losTimer = 0f; _enemy.LastKnownPlayerPosition = playerPos; } else { _losTimer += Time.deltaTime; if (_subState == ChaseSubState.Tracking) { // 缓冲期结束 → 切入 Searching:减速并向最后可见位置行进 if (_losTimer >= m_LostSightBuffer) { _subState = ChaseSubState.Searching; float searchSpeed = (_enemy.Stats?.WalkSpeed ?? 2f) * m_SearchSpeedMultiplier; _enemy.Nav?.SetSpeed(searchSpeed); _enemy.MoveTo(_enemy.LastKnownPlayerPosition); _lastReplanPos = _enemy.LastKnownPlayerPosition; } } else // Searching { if (_losTimer >= loseTime) return TaskStatus.Failure; // 搜索超时 → 进入 BD_InvestigateLastKnown } } // Tracking 阶段按阈值重规划路径 if (_subState == ChaseSubState.Tracking) { float sqrReplan = m_ReplanThreshold * m_ReplanThreshold; if ((playerPos - _lastReplanPos).sqrMagnitude > sqrReplan) { _enemy.MoveTo(playerPos); _lastReplanPos = playerPos; } } _enemy.FacePlayer(); return TaskStatus.Running; } public override void OnEnd() { float walkSpeed = _enemy?.Stats?.WalkSpeed ?? 2f; _enemy?.Nav?.SetSpeed(walkSpeed); _enemy?.StopMovement(); _subState = ChaseSubState.Tracking; // 重置子状态,防止下次激活时以错误状态重入 } } } #endif