Files
Cielonos/Assets/Scripts/MainGame/Characters/Automata/AI/Actions/Movement/AdvanceRetreat.cs
SoulliesOfficial f26f9fd374 爆更
2026-03-20 12:07:44 -04:00

319 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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("前后移动 (Advance/Retreat)。设定一个与目标的期望距离,敌人会自动前进或后退到该距离。支持随机区间与次数/时间限制。")]
[NodeIcon("f900ccca7c66371459b52036efeb8778", "cfd0e78235c50db46bc12d1751492ecf")]
[Category("Cielonos/Movement")]
public class AdvanceRetreat : MovementBase
{
#region Parameters
[Header("Target")]
[Tooltip("对峙的目标对象。")]
[SerializeField] protected SharedVariable<GameObject> m_Target;
[Header("Distance Config")]
[Tooltip("目标距离的最小值 (米)。如果想设定固定距离,将其与最大值设为相同即可。")]
[SerializeField] protected SharedVariable<float> m_MinTargetDistance = 3f;
[Tooltip("目标距离的最大值 (米)。")]
[SerializeField] protected SharedVariable<float> m_MaxTargetDistance = 6f;
[Header("Movement")]
[Tooltip("前后移动时的速度。")]
[SerializeField] protected SharedVariable<float> m_MoveSpeed = 2f;
[Header("Facing")]
[Tooltip("是否在移动过程中面朝目标。如果不勾选,则依赖外部系统的朝向控制。")]
[SerializeField] protected SharedVariable<bool> m_FaceTarget = true;
[Tooltip("面朝目标的旋转角速度 (度/秒)。")]
[SerializeField] protected SharedVariable<float> m_RotationSpeed = 360f;
[Header("Limits & Timing")]
[Tooltip("任务退出限制模式:总时间 / 总次数 / 无限")]
[SerializeField] protected TacticalLimitMode m_LimitMode = TacticalLimitMode.TotalDuration;
[Tooltip("最大移动次数(用于 TotalMoveCount 模式)。")]
[SerializeField] protected SharedVariable<int> m_MaxMoveCount = 3;
[Tooltip("最长总持续时间(秒,用于 TotalDuration 模式)。")]
[SerializeField] protected SharedVariable<float> m_MaxDuration = 8f;
[Tooltip("在总时间结束时,是否强制走完最后一次移动后再退出。")]
[SerializeField] protected SharedVariable<bool> m_FinishLastMoveBeforeEnd = false;
[Tooltip("单次移动的最大持续时间 (秒)。到达时间或到达航点都会结束单词移动。")]
[SerializeField] protected SharedVariable<float> m_SingleMoveMaxDuration = 2f;
[Tooltip("每次到达航点后的短暂停顿 (秒)。")]
[SerializeField] protected SharedVariable<float> m_TransitionPause = 0.2f;
#endregion
#region Private State
private NavMeshAgentPathfinder _navPathfinder;
private NavMeshAgent _agent;
private AutomataLandMovementSubcontroller _movementSc;
private float _originalMaxSpeed;
private bool _originalUpdateRotation;
private bool _original8WayMovement;
// 总体进度控制
private float _taskStartTime;
private int _currentMoveCount;
private bool _isTimeUp;
// 单次移动状态
private float _singleMoveStartTime;
private bool _isMoving;
private float _transitionPauseEndTime;
private const int k_MaxSampleRetries = 5;
#endregion
public override void OnAwake()
{
base.OnAwake();
_navPathfinder = m_Pathfinder as NavMeshAgentPathfinder;
if (_navPathfinder != null) _agent = _navPathfinder.m_NavMeshAgent;
var automata = gameObject.GetComponent<Automata>();
if (automata != null) _movementSc = automata.movementSc as AutomataLandMovementSubcontroller;
}
public override void OnStart()
{
base.OnStart();
if (m_Target == null || m_Target.Value == null || _agent == null)
return;
_originalMaxSpeed = _navPathfinder.Speed;
_originalUpdateRotation = _agent.updateRotation;
_agent.updateRotation = !m_FaceTarget.Value;
_agent.autoBraking = true;
_agent.speed = m_MoveSpeed.Value;
if (_movementSc != null)
{
_original8WayMovement = _movementSc.is8WayMovement;
_movementSc.is8WayMovement = true;
}
_taskStartTime = Time.time;
_currentMoveCount = 0;
_isTimeUp = false;
_isMoving = false;
_transitionPauseEndTime = 0f;
StartNewMove();
}
public override TaskStatus OnUpdate()
{
if (m_Target == null || m_Target.Value == null || _agent == null)
return TaskStatus.Failure;
if (m_FaceTarget.Value) RotateTowardsTarget();
// 检查外部限制
if (m_LimitMode == TacticalLimitMode.TotalDuration && !_isTimeUp)
{
if (Time.time - _taskStartTime >= m_MaxDuration.Value)
{
_isTimeUp = true;
if (!m_FinishLastMoveBeforeEnd.Value)
{
return TaskStatus.Success;
}
}
}
if (_isMoving)
{
bool arrived = HasArrived();
bool singleMoveTimeUp = (Time.time - _singleMoveStartTime) >= m_SingleMoveMaxDuration.Value;
if (arrived || singleMoveTimeUp)
{
StopAgent();
_isMoving = false;
_currentMoveCount++;
_transitionPauseEndTime = Time.time + m_TransitionPause.Value;
}
}
else
{
if (Time.time >= _transitionPauseEndTime)
{
if (_isTimeUp)
return TaskStatus.Success;
if (m_LimitMode == TacticalLimitMode.TotalMoveCount && _currentMoveCount >= m_MaxMoveCount.Value)
return TaskStatus.Success;
StartNewMove();
}
}
return TaskStatus.Running;
}
private void StartNewMove()
{
Vector3 targetPos = m_Target.Value.transform.position;
Vector3 toSelf = transform.position - targetPos;
toSelf.y = 0f;
if (toSelf.sqrMagnitude < 0.01f)
{
toSelf = -transform.forward; // 极近距离兜底
}
// 获取本次移动的目标距离
float targetDist = Random.Range(m_MinTargetDistance.Value, m_MaxTargetDistance.Value);
// 如果当前距离目标太近toSelf.normalized 其实就是互相远离的方向,这里就是后退;
// 如果太远toSelf.normalized 也是远离的方向,乘以 targetDist 就是前进。
// 无论是前进还是后退目标点坐标在同一条射线上targetPos + radialDir * targetDist
// 绝佳的几何性质保证了无论向前还是向后,方向绝对正确无误
Vector3 radialDir = toSelf.normalized;
Vector3 destination = targetPos + radialDir * targetDist;
if (TrySampleAndSetDestination(targetPos, destination))
{
_isMoving = true;
_singleMoveStartTime = Time.time;
if (_agent.isStopped) _agent.isStopped = false;
}
else
{
// 如果完全找不到航点(可能是身后是墙),放弃这次移动
_isMoving = false;
_currentMoveCount++;
_transitionPauseEndTime = Time.time + m_TransitionPause.Value;
}
}
private bool TrySampleAndSetDestination(Vector3 centerTargetPos, Vector3 destination)
{
Vector3 sampledPos = destination;
if (SamplePosition(ref sampledPos))
{
SetDestination(sampledPos);
return true;
}
// 采样失败时,尝试向目标方向靠拢
for (int i = 1; i <= k_MaxSampleRetries; i++)
{
// 如果是后退撞墙,逐步缩短目标距离(即靠近目标)进行采样
Vector3 retryPos = Vector3.Lerp(destination, centerTargetPos, i * 0.15f);
if (SamplePosition(ref retryPos))
{
SetDestination(retryPos);
return true;
}
}
return false;
}
private void RotateTowardsTarget()
{
Vector3 direction = m_Target.Value.transform.position - transform.position;
direction.y = 0f;
if (direction.sqrMagnitude < 0.001f) return;
Quaternion targetRot = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRot, m_RotationSpeed.Value * Time.deltaTime);
}
private void StopAgent()
{
if (_agent != null && _agent.isOnNavMesh && !_agent.isStopped)
{
_agent.isStopped = true;
_agent.velocity = Vector3.zero;
}
}
public override void OnEnd()
{
base.OnEnd();
CleanUpAgent();
}
public override void OnBehaviorTreeStopped(bool paused)
{
base.OnBehaviorTreeStopped(paused);
CleanUpAgent();
}
private void CleanUpAgent()
{
if (_agent != null)
{
if (_agent.isOnNavMesh && _agent.isActiveAndEnabled)
{
_agent.isStopped = true;
_agent.velocity = Vector3.zero;
_agent.ResetPath();
}
_agent.updateRotation = _originalUpdateRotation;
_agent.autoBraking = true;
_agent.speed = _originalMaxSpeed;
}
if (_movementSc != null)
{
_movementSc.is8WayMovement = _original8WayMovement;
}
}
public override void Reset()
{
base.Reset();
m_Target = null;
m_MinTargetDistance = 3f;
m_MaxTargetDistance = 6f;
m_MoveSpeed = 2f;
m_FaceTarget = true;
m_RotationSpeed = 360f;
m_LimitMode = TacticalLimitMode.TotalDuration;
m_MaxMoveCount = 3;
m_MaxDuration = 8f;
m_FinishLastMoveBeforeEnd = false;
m_SingleMoveMaxDuration = 2f;
m_TransitionPause = 0.2f;
}
#if UNITY_EDITOR
protected override void OnDrawGizmosSelected()
{
if (m_Target == null || m_Target.Value == null) return;
Vector3 targetPos = m_Target.Value.transform.position;
if (Mathf.Abs(m_MinTargetDistance.Value - m_MaxTargetDistance.Value) < 0.01f)
{
UnityEditor.Handles.color = new Color(0f, 1f, 1f, 0.4f);
UnityEditor.Handles.DrawWireDisc(targetPos, Vector3.up, m_MinTargetDistance.Value);
}
else
{
UnityEditor.Handles.color = new Color(1f, 0.3f, 0.3f, 0.4f);
UnityEditor.Handles.DrawWireDisc(targetPos, Vector3.up, m_MinTargetDistance.Value);
UnityEditor.Handles.color = new Color(0.3f, 0.6f, 1f, 0.4f);
UnityEditor.Handles.DrawWireDisc(targetPos, Vector3.up, m_MaxTargetDistance.Value);
}
}
#endif
}
}