(client) feat:健康给予,路径优化,结算界面,商店界面 (#60)

Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com>
Reviewed-on: http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite/pulls/60
This commit is contained in:
2025-10-10 14:08:23 +08:00
parent 9a797479ff
commit 16b49f3d3a
1900 changed files with 114053 additions and 34157 deletions

View File

@@ -6,22 +6,25 @@ namespace AI
{
public class JobNode_MoveToAttackRange : LeafNodeBase
{
private Entity.Entity _targetHostileEntity; // 目标敌对实体
private Vector3 _targetMovePosition; // 目标移动位置
// 卡住检测的常量
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 const int MAX_STUCK_FRAMES = 10; // 实体在多少帧内没有显著移动,则认为卡住
private const float STUCK_POSITION_THRESHOLD_SQ = 0.001f; // 位置变化的平方距离阈值
private Entity.Entity _targetHostileEntity; // 目标敌对实体
private Vector3 _targetMovePosition; // 目标移动位置
// 便利属性,获取实体的攻击范围
private float AttackRange => SelfEntity.AttributesNow.attackRange;
/// <summary>
/// 执行移动到攻击范围的逻辑。
/// 执行移动到攻击范围的逻辑。
/// </summary>
/// <returns>行为树节点状态。</returns>
protected override Status ExecuteLeafLogic()
@@ -36,47 +39,83 @@ namespace AI
Relation.Hostile);
// 如果没有找到敌对目标,任务失败
if (!hostileEntityRecord || !hostileEntityRecord.entity)
{
return Status.Failure;
}
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;
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 设置路径目标
SelfEntity.SetTarget(_targetMovePosition);
_isPathSet = true;
_lastKnownSelfPosition = SelfEntity.Position; // 初始化上次已知位置,用于卡住检测
_stuckFrameCount = 0; // 重置卡住计数器
return Status.Running; // 路径已设置,开始移动
// 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; // 最终无法设置路径,任务失败
}
// 路径已设置,继续管理移动和卡住检测
// 4. 检查是否已到达目标点
if (SelfEntity.OnTargetPoint)
{
// 实体已到达目标点,或已经进入目标的攻击范围
if (SelfEntity.OnTargetPoint || (SelfEntity.Position - _targetHostileEntity.Position).sqrMagnitude <
SelfEntity.AttributesNow.attackRange * SelfEntity.AttributesNow.attackRange)
return Status.Success; // 成功移动到攻击范围
}
// 5. 卡住检测
if (SelfEntity.AttributesNow.moveSpeed <= 0)
return Status.Failure;
// 检查是否卡住
// 计算当前位置与上次已知位置的平方距离,避免开方运算,提高性能
float currentPositionChangeSq = (SelfEntity.Position - _lastKnownSelfPosition).sqrMagnitude;
var 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; // 尽管卡住,但节点仍然在尝试完成任务
_isPathSet = false;
return Status.Running;
}
}
else
@@ -85,16 +124,14 @@ namespace AI
_stuckFrameCount = 0;
}
// 更新上次已知位置
_lastKnownSelfPosition = SelfEntity.Position;
// 6. 持续移动
SelfEntity.TryMove();
return Status.Running; // 正在向目标移动
return Status.Running;
}
/// <summary>
/// 重置此移动节点的所有内部状态。
/// 重置此移动节点的所有内部状态。
/// </summary>
public override void Reset()
{
@@ -106,4 +143,4 @@ namespace AI
_stuckFrameCount = 0;
}
}
}
}