Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/AI/JobNode_MoveToAttackRange.cs

110 lines
4.8 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 Data;
using Managers;
using UnityEngine;
namespace AI
{
public class JobNode_MoveToAttackRange : LeafNodeBase
{
private Entity.Entity _targetHostileEntity; // 目标敌对实体
private Vector3 _targetMovePosition; // 目标移动位置
private bool _isPathSet; // 指示当前是否已设置路径
private Vector3 _lastKnownSelfPosition; // 上次已知自身位置
private int _stuckFrameCount; // 卡住帧计数
// 卡住检测的常量
private const int MAX_STUCK_FRAMES = 10; // 实体在多少帧内没有显著移动,则认为卡住
private const float STUCK_POSITION_THRESHOLD_SQ = 0.001f; // 位置变化的平方距离阈值
// 便利属性,获取实体的攻击范围
private float AttackRange => SelfEntity.AttributesNow.attackRange;
/// <summary>
/// 执行移动到攻击范围的逻辑。
/// </summary>
/// <returns>行为树节点状态。</returns>
protected override Status ExecuteLeafLogic()
{
// 如果路径尚未设置或目标实体无效null或死亡则需要重新寻找目标并设置路径
if (!_isPathSet || !_targetHostileEntity || _targetHostileEntity.IsDead)
{
// 1. 寻找最近的敌对实体
var hostileEntityRecord = EntityManager.Instance.FindNearestEntityByRelation(
SelfEntity.currentDimensionId,
SelfEntity.entityPrefab,
Relation.Hostile);
// 如果没有找到敌对目标,任务失败
if (!hostileEntityRecord || !hostileEntityRecord.entity)
{
return Status.Failure;
}
_targetHostileEntity = hostileEntityRecord.entity;
// 2. 计算目标移动点(在敌对目标的攻击距离边缘)
// 目标点是:从敌对实体指向 SelfEntity 的向量方向上,距离敌对实体 AttackRange 远的点。
// 这样做是为了让 SelfEntity 停在敌对实体攻击范围内,而不是直接重叠。
Vector3 directionToSelf = (SelfEntity.Position - _targetHostileEntity.Position).normalized;
_targetMovePosition = _targetHostileEntity.Position + directionToSelf * AttackRange;
// 3. 通知 SelfEntity 设置路径目标
SelfEntity.SetTarget(_targetMovePosition);
_isPathSet = true;
_lastKnownSelfPosition = SelfEntity.Position; // 初始化上次已知位置,用于卡住检测
_stuckFrameCount = 0; // 重置卡住计数器
return Status.Running; // 路径已设置,开始移动
}
// 路径已设置,继续管理移动和卡住检测
// 4. 检查是否已到达目标点
if (SelfEntity.OnTargetPoint)
{
return Status.Success; // 成功移动到攻击范围
}
// 5. 卡住检测
// 计算当前位置与上次已知位置的平方距离,避免开方运算,提高性能
float currentPositionChangeSq = (SelfEntity.Position - _lastKnownSelfPosition).sqrMagnitude;
if (currentPositionChangeSq < STUCK_POSITION_THRESHOLD_SQ)
{
_stuckFrameCount++;
if (_stuckFrameCount >= MAX_STUCK_FRAMES)
{
// 实体长时间未移动,被判定为卡住,需要重新规划路径
Debug.LogWarning(
$"[{SelfEntity.entityDef.defName}] 行为节点<移动到攻击范围>: 实体卡住了! 重新计算到目标 [{_targetHostileEntity.entityDef.defName}] 的路径。");
_isPathSet = false; // 重置此标志,在下一帧会触发重新寻找目标和设置路径
return Status.Running; // 尽管卡住,但节点仍然在尝试完成任务
}
}
else
{
// 实体有移动,重置卡住计数器
_stuckFrameCount = 0;
}
// 更新上次已知位置
_lastKnownSelfPosition = SelfEntity.Position;
// 6. 持续移动
SelfEntity.TryMove();
return Status.Running; // 正在向目标移动
}
/// <summary>
/// 重置此移动节点的所有内部状态。
/// </summary>
public override void Reset()
{
base.Reset(); // 调用基类的 Reset重置 CurrentStatus 和 _elapsedFrames
_targetHostileEntity = null;
_targetMovePosition = Vector3.zero;
_isPathSet = false;
_lastKnownSelfPosition = Vector3.zero;
_stuckFrameCount = 0;
}
}
}