Files

120 lines
5.4 KiB
C#
Raw Permalink Normal View History

using UnityEngine;
namespace AI
{
public class JobNode_Wander : LeafNodeBase
{
private const int _maxStuckFrames = 5; // 超过此帧数认为卡住
private readonly int _maxTargetSearchAttempts = 20; // 寻找可达目标点的最大尝试次数
private readonly float _minWanderDistance = 2f; // 随机目标点距离当前位置的最小距离,避免原地踏步
private readonly float _positionCheckThreshold = 0.001f; // 检测是否卡住的距离阈值
private readonly float _wanderRange = 10f; // 徘徊的最大范围
private Vector3 _lastPosition; // 上一帧的实体位置
private int _stuckFrameCount; // 累计在同一位置的帧数
private Vector3 _wanderTargetPosition; // 当前徘徊的目标点
/// <summary>
/// 核心业务逻辑,用于执行随机徘徊。
/// LeafNodeBase 会在计时器管理后调用此方法。
/// </summary>
/// <returns>节点的执行状态Running, Success, Failure。</returns>
protected override Status ExecuteLeafLogic()
{
// 如果没有目标点,尝试寻找一个
if (_wanderTargetPosition == Vector3.zero) // Vector3.zero 作为未设置目标的标志
{
if (!FindRandomWanderTarget()) return Status.Failure;
// 找到目标并成功生成路径后,设置初始上一帧位置,并重置卡住计数
_lastPosition = SelfEntity.Position;
_stuckFrameCount = 0;
}
if (SelfEntity.AttributesNow.moveSpeed <= 0)
return Status.Success;
// 尝试沿着路径移动
SelfEntity.TryMove();
// 检查是否到达目标点
if (SelfEntity.OnTargetPoint) return Status.Success;
// 检查是否卡住
var currentPosition = SelfEntity.Position;
// 使用 SqrMagnitude 比 Distance 略快,因为它避免了开方根运算
if (Vector3.SqrMagnitude(currentPosition - _lastPosition) <
_positionCheckThreshold * _positionCheckThreshold)
{
_stuckFrameCount++;
if (_stuckFrameCount > _maxStuckFrames)
{
_wanderTargetPosition = Vector3.zero; // 清除可能导致卡住的目标点
return Status.Failure;
}
}
else
{
_stuckFrameCount = 0; // 位置发生变化,重置卡住计数
_lastPosition = currentPosition;
}
// 既未成功也未失败,继续运行
return Status.Running;
}
/// <summary>
/// 寻找一个随机且可达的徘徊目标点,并尝试生成路径。
/// </summary>
/// <returns>如果成功找到并设置目标点且路径生成成功则返回true否则返回false。</returns>
private bool FindRandomWanderTarget()
{
var currentEntityPosition = SelfEntity.Position;
var currentDimensionId = SelfEntity.currentDimensionId;
for (var i = 0; i < _maxTargetSearchAttempts; i++)
{
// 生成相对当前位置的随机偏移
var randomOffset = Random.insideUnitCircle * _wanderRange;
var potentialTarget =
currentEntityPosition + new Vector3(randomOffset.x, randomOffset.y, 0); // 假定2D平面Z轴不变
// 确保新目标点与当前位置有足够的距离
if (Vector3.Distance(potentialTarget, currentEntityPosition) < _minWanderDistance) continue; // 距离太近,重试
// 检查目标点的地貌可达性
var travelCost = Program.Instance.GetDimension(currentDimensionId).landform
.GetTravelCost(potentialTarget);
if (travelCost < 1.0f) // 小于1表示地貌允许通过
{
_wanderTargetPosition = potentialTarget; // 暂定此为目标
// 尝试设置目标并生成路径,路径规划也可能失败
if (SelfEntity.SetTarget(_wanderTargetPosition))
// 目标点地貌可达且路径成功生成
return true;
// 目标点地貌可达,但路径生成失败(例如,路径被其他临时障碍物阻挡,或路径计算失败)
// 重置_wanderTargetPosition以便在下一次循环中尝试新的目标点
_wanderTargetPosition = Vector3.zero;
}
}
_wanderTargetPosition = Vector3.zero; // 最终未能找到有效目标,确保目标点被清空
return false; // 达到最大尝试次数仍未找到有效目标或路径
}
/// <summary>
/// 重置叶节点的状态,包括基类状态和内部计时器。
/// </summary>
public override void Reset()
{
base.Reset(); // 调用基类的Reset方法将CurrentStatus设为Ready并重置_elapsedFrames
_wanderTargetPosition = Vector3.zero; // 重置目标点
_lastPosition = Vector3.zero; // 重置上一帧位置
_stuckFrameCount = 0; // 重置卡住计数
}
}
}