Files
Cielonos/Assets/Opsive/BehaviorDesigner/Add-Ons/CielonosPack/Actions/Movement/WanderInRange.cs
SoulliesOfficial 2e00676794 重做杂兵
2026-05-11 15:22:30 -04:00

206 lines
7.4 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.BehaviorDesigner.Runtime.Utility;
using Opsive.GraphDesigner.Runtime;
using Opsive.GraphDesigner.Runtime.Variables;
using Opsive.Shared.Utility;
using Unity.Entities;
using UnityEngine;
using UnityEngine.AI;
namespace Cielonos.MainGame.Characters.AI
{
[Description("在初始出生点附近的指定半径内随机选取一个 NavMesh 上的点并移动过去。到达后等待一段时间(可选),然后返回 Success。")]
[NodeIcon("Assets/Sprites/Icon/Play.png")]
[Category("Cielonos/Movement")]
public class WanderInRange : MovementBase
{
[Tooltip("初始移动速度。")]
[SerializeField] protected SharedVariable<float> m_StartSpeed = 10f;
[Tooltip("以初始出生点为圆心的游走半径(单位:米)。")]
[SerializeField] protected SharedVariable<float> m_WanderRadius = 10f;
[Tooltip("到达目标点后等待的时间范围。Min 与 Max 均为 0 时不等待,直接返回 Success。")]
[SerializeField] protected SharedVariable<RangeFloat> m_WaitAtDestinationDuration = new RangeFloat(0f, 0f);
[Tooltip("判定到达目的地所需的剩余距离阈值(单位:米)。")]
[SerializeField] protected SharedVariable<float> m_ArrivalDistance = 0.5f;
[Tooltip("每次寻路 tick 中,选取随机目标点的最大重试次数。")]
[SerializeField] protected SharedVariable<int> m_DestinationRetries = 5;
#if UNITY_EDITOR
[Tooltip("是否在无效目标点处绘制调试射线?")]
[SerializeField] protected bool m_DrawInvalidDestinationRay;
#endif
// 记录行为树初始化时的出生点位置
private Vector3 m_SpawnPosition;
private NavMeshAgentPathfinder _navPathfinder;
private NavMeshAgent _agent;
// 到达后等待状态
private float m_WaitDuration = -1f;
private float m_DestinationReachedTime = -1f;
// 本次 OnStart 是否已成功派发了目标点
private bool m_HasDestination;
/// <summary>
/// 记录出生点,仅在行为树初始化时调用一次。
/// </summary>
public override void OnAwake()
{
base.OnAwake();
_navPathfinder = m_Pathfinder as NavMeshAgentPathfinder;
if (_navPathfinder == null)
{
Debug.LogError("[PrecisePursue] Requires NavMeshAgentPathfinder.");
return;
}
_agent = _navPathfinder.m_NavMeshAgent;
m_SpawnPosition = transform.position;
}
/// <summary>
/// 每次节点开始执行时,重置状态并选取新目标。
/// </summary>
public override void OnStart()
{
base.OnStart();
m_WaitDuration = -1f;
m_DestinationReachedTime = -1f;
m_HasDestination = TrySetDestination();
}
/// <summary>
/// 每帧检查是否到达目标点,并处理到达后的等待逻辑。
/// </summary>
public override TaskStatus OnUpdate()
{
// 若初始选点失败,再尝试一次;仍然失败则返回 Failure
if (!m_HasDestination)
{
m_HasDestination = TrySetDestination();
if (!m_HasDestination)
return TaskStatus.Failure;
}
_agent.speed = m_StartSpeed.Value;
// 到达判定HasArrived() 或剩余距离小于阈值
if (HasArrived() || RemainingDistance < m_ArrivalDistance.Value)
{
// 首次到达:决定是否等待
if (m_WaitDuration < 0f)
{
m_WaitDuration = m_WaitAtDestinationDuration.Value.RandomValue;
if (m_WaitDuration > 0f)
{
m_DestinationReachedTime = Time.time;
return TaskStatus.Running;
}
return TaskStatus.Success;
}
// 等待中:检查是否已等待足够时间
if (Time.time >= m_DestinationReachedTime + m_WaitDuration)
{
return TaskStatus.Success;
}
}
return TaskStatus.Running;
}
/// <summary>
/// 在出生点半径内随机采样 NavMesh 上的有效点,并设定为寻路目标。
/// </summary>
/// <returns>成功设定目标点返回 true所有重试均失败返回 false。</returns>
private bool TrySetDestination()
{
for (int i = 0; i < m_DestinationRetries.Value; i++)
{
// 在 XZ 平面的圆形范围内随机选点
var randomOffset = Random.insideUnitCircle * m_WanderRadius.Value;
var candidate = m_SpawnPosition + new Vector3(randomOffset.x, 0f, randomOffset.y);
if (SamplePosition(ref candidate))
{
// 防止 NavMesh 采样将点吸附到半径外
if (Vector3.Distance(candidate, m_SpawnPosition) <= m_WanderRadius.Value)
{
SetDestination(candidate);
return true;
}
}
#if UNITY_EDITOR
if (m_DrawInvalidDestinationRay)
Debug.DrawRay(candidate, Vector3.up * 2f, Color.red, 1f);
#endif
}
return false;
}
/// <summary>
/// 节点结束时重置等待状态。
/// </summary>
public override void OnEnd()
{
base.OnEnd();
m_WaitDuration = -1f;
m_DestinationReachedTime = -1f;
m_HasDestination = false;
}
/// <summary>
/// 保存当前节点运行状态(等待剩余时间)。
/// </summary>
public override object Save(World world, Entity entity)
{
var saveData = new object[3];
saveData[0] = base.Save(world, entity);
saveData[1] = m_WaitDuration;
// 存储已等待时长而非绝对时间戳,以兼容时间暂停/恢复
saveData[2] = m_DestinationReachedTime >= 0f ? Time.time - m_DestinationReachedTime : -1f;
return saveData;
}
/// <summary>
/// 恢复节点运行状态。
/// </summary>
public override void Load(object saveData, World world, Entity entity)
{
var arr = (object[])saveData;
base.Load(arr[0], world, entity);
m_WaitDuration = (float)arr[1];
var elapsed = (float)arr[2];
m_DestinationReachedTime = elapsed >= 0f ? Time.time - elapsed : -1f;
}
/// <summary>
/// 编辑器重置,恢复字段默认值。
/// </summary>
public override void Reset()
{
base.Reset();
m_WanderRadius = 8f;
m_WaitAtDestinationDuration = new RangeFloat(0f, 0f);
m_ArrivalDistance = 0.5f;
m_DestinationRetries = 5;
#if UNITY_EDITOR
m_DrawInvalidDestinationRay = false;
#endif
}
}
}