Files

120 lines
5.4 KiB
C#
Raw Permalink 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 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; // 重置卡住计数
}
}
}