174 lines
6.3 KiB
C#
174 lines
6.3 KiB
C#
using Opsive.BehaviorDesigner.AddOns.MovementPack.Runtime.Tasks;
|
||
using Opsive.BehaviorDesigner.AddOns.Shared.Runtime.Pathfinding;
|
||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||
using Opsive.GraphDesigner.Runtime;
|
||
using Opsive.GraphDesigner.Runtime.Variables;
|
||
using Opsive.Shared.Utility;
|
||
using UnityEngine;
|
||
using UnityEngine.AI;
|
||
|
||
namespace Cielonos.MainGame.Characters.AI
|
||
{
|
||
[Description("以精准停止方式追踪目标。在减速缓冲区内平滑减速,恰好停在目标前方指定距离处。")]
|
||
[NodeIcon("079b135d5d495e14abd9ad2cb0dfaf9e", "c0515b66e6314734ba5a8f2ec7d6f451")]
|
||
[Category("Cielonos/Movement")]
|
||
public class PrecisePursue : MovementBase
|
||
{
|
||
#region Pursue Parameters
|
||
[Tooltip("追踪开始时的初始移动速度,超出减速距离外会保持此速度。")]
|
||
[SerializeField] protected SharedVariable<float> m_StartSpeed = 10f;
|
||
[Tooltip("Agent 正在追踪的目标对象。")]
|
||
[SerializeField] protected SharedVariable<GameObject> m_Target;
|
||
[Tooltip("指定提前预测目标的距离的程度。")]
|
||
[SerializeField] protected SharedVariable<float> m_DistancePrediction = 20;
|
||
[Tooltip("预测提前距离的乘数。")]
|
||
[SerializeField] protected SharedVariable<float> m_DistancePredictionMultiplier = 1;
|
||
#endregion
|
||
|
||
#region Precise Stopping Parameters
|
||
[Header("Precise Stopping")]
|
||
[Tooltip("开始减速的距离阈值 (单位: 米)")]
|
||
[SerializeField] protected SharedVariable<float> m_SlowDownDistance = 6f;
|
||
[Tooltip("减速后的最低速度 (防止完全停滞)")]
|
||
[SerializeField] protected SharedVariable<float> m_MinSpeed = 0.5f;
|
||
[Tooltip("减速缓动曲线 (X: 0=停止距离 1=减速距离, Y: 0=最低速度 1=最大速度)")]
|
||
[SerializeField] protected AnimationCurve m_SpeedCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||
#endregion
|
||
|
||
#region Private State
|
||
private Vector3 _targetPosition;
|
||
private float _originalMaxSpeed;
|
||
private NavMeshAgentPathfinder _navPathfinder;
|
||
private NavMeshAgent _agent;
|
||
#endregion
|
||
|
||
public override void OnAwake()
|
||
{
|
||
base.OnAwake();
|
||
|
||
_navPathfinder = m_Pathfinder as NavMeshAgentPathfinder;
|
||
if (_navPathfinder == null)
|
||
{
|
||
Debug.LogError("[PrecisePursue] Requires NavMeshAgentPathfinder.");
|
||
return;
|
||
}
|
||
_agent = _navPathfinder.m_NavMeshAgent;
|
||
}
|
||
|
||
public override void OnStart()
|
||
{
|
||
base.OnStart();
|
||
|
||
if (m_Target == null || m_Target.Value == null)
|
||
{
|
||
Debug.LogError("[PrecisePursue] A target must be set.");
|
||
return;
|
||
}
|
||
|
||
if (_agent == null) return;
|
||
|
||
_originalMaxSpeed = _navPathfinder.Speed;
|
||
_agent.autoBraking = false;
|
||
|
||
_targetPosition = m_Target.Value.transform.position;
|
||
SetDestination(GetTargetDestination());
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
if (m_Target == null || m_Target.Value == null)
|
||
return TaskStatus.Failure;
|
||
|
||
if (_agent == null)
|
||
return TaskStatus.Failure;
|
||
|
||
// 1. 用直线距离(非 NavMesh remainingDistance)做判定
|
||
float distanceToTarget = Vector3.Distance(
|
||
transform.position, m_Target.Value.transform.position
|
||
);
|
||
|
||
// 2. 到达判定
|
||
if (distanceToTarget <= _navPathfinder.StoppingDistance)
|
||
{
|
||
_agent.velocity = Vector3.zero;
|
||
_agent.isStopped = true;
|
||
_agent.speed = 0f;
|
||
return TaskStatus.Success;
|
||
}
|
||
|
||
// 3. 减速缓冲区控制
|
||
float denominator = m_SlowDownDistance.Value - _navPathfinder.StoppingDistance;
|
||
if (denominator > 0f && distanceToTarget < m_SlowDownDistance.Value)
|
||
{
|
||
float t = Mathf.Clamp01(
|
||
(distanceToTarget - _navPathfinder.StoppingDistance) / denominator
|
||
);
|
||
float easedFactor = m_SpeedCurve.Evaluate(t);
|
||
_agent.speed = Mathf.Lerp(m_MinSpeed.Value, m_StartSpeed.Value, easedFactor);
|
||
}
|
||
else
|
||
{
|
||
_agent.speed = m_StartSpeed.Value;
|
||
}
|
||
|
||
_agent.isStopped = false;
|
||
|
||
// 4. 持续更新预测目标点
|
||
SetDestination(GetTargetDestination());
|
||
|
||
return TaskStatus.Running;
|
||
}
|
||
|
||
public override void OnEnd()
|
||
{
|
||
base.OnEnd();
|
||
|
||
if (_agent != null && _agent.isOnNavMesh && _agent.isActiveAndEnabled)
|
||
{
|
||
_agent.isStopped = true;
|
||
_agent.velocity = Vector3.zero;
|
||
_agent.ResetPath();
|
||
}
|
||
|
||
if (_navPathfinder != null && _agent != null)
|
||
{
|
||
_agent.autoBraking = true;
|
||
_agent.speed = _originalMaxSpeed;
|
||
}
|
||
}
|
||
|
||
private Vector3 GetTargetDestination()
|
||
{
|
||
var distance = (m_Target.Value.transform.position - transform.position).magnitude;
|
||
var velocityMagnitude = m_Pathfinder.Velocity.magnitude;
|
||
|
||
float futurePrediction;
|
||
if (velocityMagnitude <= distance / m_DistancePrediction.Value)
|
||
{
|
||
futurePrediction = m_DistancePrediction.Value;
|
||
}
|
||
else
|
||
{
|
||
futurePrediction = (distance / velocityMagnitude)
|
||
* m_DistancePredictionMultiplier.Value;
|
||
}
|
||
|
||
var lastTargetPosition = _targetPosition;
|
||
_targetPosition = m_Target.Value.transform.position;
|
||
return _targetPosition + (_targetPosition - lastTargetPosition) * futurePrediction;
|
||
}
|
||
|
||
public override void Reset()
|
||
{
|
||
base.Reset();
|
||
m_StartSpeed = 10f;
|
||
m_Target = null;
|
||
m_DistancePrediction = 20;
|
||
m_DistancePredictionMultiplier = 2; // 与声明一致
|
||
m_SlowDownDistance = 6f;
|
||
m_MinSpeed = 0.5f;
|
||
m_SpeedCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||
}
|
||
}
|
||
}
|