#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