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

146 lines
6.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 Data;
using Managers;
using UnityEngine;
namespace AI
{
public class JobNode_MoveToAttackRange : LeafNodeBase
{
// 卡住检测的常量
private const int MAX_STUCK_FRAMES = 10; // 实体在多少帧内没有显著移动,则认为卡住
private const float STUCK_POSITION_THRESHOLD_SQ = 0.00001f; // 位置变化的平方距离阈值
// 新增的偏移尝试常量
private const int _maxOffsetAttempts = 5; // 尝试偏移的最大次数
private const float _offsetDistance = 0.5f; // 每次偏移的距离
private bool _isPathSet; // 指示当前是否已设置路径
private Vector3 _lastKnownSelfPosition; // 上次已知自身位置
private int _stuckFrameCount; // 卡住帧计数
private Entity.Entity _targetHostileEntity; // 目标敌对实体
private Vector3 _targetMovePosition; // 目标移动位置
// 便利属性,获取实体的攻击范围
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;
var dir = (SelfEntity.Position - _targetHostileEntity.Position);
if (dir.sqrMagnitude<=AttackRange*AttackRange)
{
return Status.Success;
}
// 计算目标攻击范围边缘的位置
var directionToSelf = dir.normalized;
// 这次计算出的目标点是最初的精确临界点
var initialTargetMovePosition = _targetHostileEntity.Position + directionToSelf * AttackRange;
_targetMovePosition = initialTargetMovePosition; // 暂时设置,可能被偏移后的点更新
// 3. 通知 SelfEntity 设置路径目标,并检查是否成功
if (SelfEntity.SetTarget(initialTargetMovePosition))
{
// 路径成功生成,可以开始移动
_isPathSet = true;
_lastKnownSelfPosition = SelfEntity.Position; // 初始化上次已知位置,用于卡住检测
_stuckFrameCount = 0; // 重置卡住计数器
return Status.Running; // 路径已设置,开始移动
}
var pathFoundAfterOffset = false;
for (var i = 0; i < _maxOffsetAttempts; i++)
{
// 生成随机偏移向量,确保在二维平面内 (假设Z轴不变)
var randomOffsetCircle = Random.insideUnitCircle * _offsetDistance;
var potentialOffsetTarget = initialTargetMovePosition +
new Vector3(randomOffsetCircle.x, randomOffsetCircle.y, 0);
// 再次尝试设置目标
if (SelfEntity.SetTarget(potentialOffsetTarget))
{
// 偏移后路径成功生成
_targetMovePosition = potentialOffsetTarget; // 更新节点内部的目标为成功的偏移点
pathFoundAfterOffset = true;
break; // 跳出偏移尝试循环
}
}
if (pathFoundAfterOffset)
{
// 偏移后找到路径初始化状态并返回Running
_isPathSet = true;
_lastKnownSelfPosition = SelfEntity.Position;
_stuckFrameCount = 0;
return Status.Running;
}
_isPathSet = false; // 确保路径标志为false
return Status.Failure; // 最终无法设置路径,任务失败
}
// 路径已设置,继续管理移动和卡住检测
// 实体已到达目标点,或已经进入目标的攻击范围
if (SelfEntity.OnTargetPoint || (SelfEntity.Position - _targetHostileEntity.Position).sqrMagnitude <
SelfEntity.AttributesNow.attackRange * SelfEntity.AttributesNow.attackRange)
return Status.Success; // 成功移动到攻击范围
if (SelfEntity.AttributesNow.moveSpeed <= 0)
return Status.Failure;
// 检查是否卡住
// 计算当前位置与上次已知位置的平方距离,避免开方运算,提高性能
var currentPositionChangeSq = (SelfEntity.Position - _lastKnownSelfPosition).sqrMagnitude;
if (currentPositionChangeSq < STUCK_POSITION_THRESHOLD_SQ)
{
_stuckFrameCount++;
if (_stuckFrameCount >= MAX_STUCK_FRAMES)
{
_isPathSet = false;
return Status.Running;
}
}
else
{
// 实体有移动,重置卡住计数器
_stuckFrameCount = 0;
}
_lastKnownSelfPosition = SelfEntity.Position;
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;
}
}
}