Files
Cielonos/Assets/Opsive/BehaviorDesigner/Add-Ons/CielonosPack/Actions/Movement/WanderInRange.cs

206 lines
7.4 KiB
C#
Raw Normal View History

2026-05-10 11:47:55 -04:00
using Opsive.BehaviorDesigner.AddOns.MovementPack.Runtime.Tasks;
2026-05-11 15:22:30 -04:00
using Opsive.BehaviorDesigner.AddOns.Shared.Runtime.Pathfinding;
2026-05-10 11:47:55 -04:00
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;
2026-05-11 15:22:30 -04:00
using UnityEngine.AI;
2026-05-10 11:47:55 -04:00
namespace Cielonos.MainGame.Characters.AI
{
[Description("在初始出生点附近的指定半径内随机选取一个 NavMesh 上的点并移动过去。到达后等待一段时间(可选),然后返回 Success。")]
[NodeIcon("Assets/Sprites/Icon/Play.png")]
[Category("Cielonos/Movement")]
public class WanderInRange : MovementBase
{
2026-05-11 15:22:30 -04:00
[Tooltip("初始移动速度。")]
[SerializeField] protected SharedVariable<float> m_StartSpeed = 10f;
2026-05-10 11:47:55 -04:00
[Tooltip("以初始出生点为圆心的游走半径(单位:米)。")]
2026-05-11 15:22:30 -04:00
[SerializeField] protected SharedVariable<float> m_WanderRadius = 10f;
2026-05-10 11:47:55 -04:00
[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;
2026-05-11 15:22:30 -04:00
private NavMeshAgentPathfinder _navPathfinder;
private NavMeshAgent _agent;
2026-05-10 11:47:55 -04:00
// 到达后等待状态
private float m_WaitDuration = -1f;
private float m_DestinationReachedTime = -1f;
// 本次 OnStart 是否已成功派发了目标点
private bool m_HasDestination;
/// <summary>
/// 记录出生点,仅在行为树初始化时调用一次。
/// </summary>
public override void OnAwake()
{
base.OnAwake();
2026-05-11 15:22:30 -04:00
_navPathfinder = m_Pathfinder as NavMeshAgentPathfinder;
if (_navPathfinder == null)
{
Debug.LogError("[PrecisePursue] Requires NavMeshAgentPathfinder.");
return;
}
_agent = _navPathfinder.m_NavMeshAgent;
2026-05-10 11:47:55 -04:00
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;
}
2026-05-11 15:22:30 -04:00
_agent.speed = m_StartSpeed.Value;
2026-05-10 11:47:55 -04:00
// 到达判定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)
2026-05-11 15:22:30 -04:00
{
2026-05-10 11:47:55 -04:00
return TaskStatus.Success;
2026-05-11 15:22:30 -04:00
}
2026-05-10 11:47:55 -04:00
}
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
}
}
}