Files
Cielonos/Assets/Opsive/BehaviorDesigner/Add-Ons/CielonosPack/Actions/Movement/CalculateJumpTarget.cs
SoulliesOfficial 9a9e48f8a5
2026-06-27 12:52:03 -04:00

188 lines
7.6 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 Opsive.BehaviorDesigner.Runtime;
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.GraphDesigner.Runtime.Variables;
using Opsive.Shared.Utility;
using UnityEngine;
using UnityEngine.AI;
namespace Cielonos.MainGame.Characters.AI
{
[Description("计算跳跃的目标落点坐标,并将计算结果写入行为树的 Vector3 变量中。支持多种跳跃策略(接近、后撤、侧闪、出死角等)。")]
[Category("Cielonos/Movement")]
public class CalculateJumpTarget : AutomataActionBase
{
public enum JumpStrategy
{
[Tooltip("快速接近玩家:直接指向玩家位置(或预测位置)。")]
CloseIn,
[Tooltip("战术后撤:向自身后方跳跃拉开距离,用于准备释放远程攻击。")]
Retreat,
[Tooltip("侧向侧袭Flanking跳到玩家侧方90度用于绕过正面防守/激光。")]
Flank,
[Tooltip("挣脱死角Escape Corner当靠近场景边缘/墙壁时,计算指向场地中央的跳跃点。")]
EscapeCorner,
[Tooltip("避险跳跃Escape Hazard远离最近的玩家危险区域/地裂伤害。")]
EscapeHazard,
[Tooltip("原地原跳Self Slam以自身当前位置为中心通常用于大招落地原地震击。")]
SelfSlam
}
[Header("Strategy Config")]
[Tooltip("跳跃落点策略模式")]
public JumpStrategy strategy = JumpStrategy.CloseIn;
[Tooltip("输出的目标位置变量名称(写入行为树,供 ManualMove 读取)。")]
public SharedVariable<Vector3> jumpLandPosition = new SharedVariable<Vector3> { Value = Vector3.zero };
[Header("Distance Parameters")]
[Tooltip("后撤跳跃的距离(米)。")]
public float retreatDistance = 8f;
[Tooltip("接近跳跃时,期望落在距离玩家多少米的位置(用于巨斧攻击范围适配)。")]
public float closeInArrivalRadius = 2.0f;
[Tooltip("战斗场地/房间的中心点。当从死角需要跃回场地中央时使用。")]
public SharedVariable<Vector3> arenaCenter = new SharedVariable<Vector3> { Value = Vector3.zero };
[Tooltip("侧向跳跃相对于玩家的偏置距离(米)。")]
public float flankDistance = 5f;
[Tooltip("侧向跳跃的方向选择1 = 玩家右侧,-1 = 玩家左侧0 = 随机。")]
public int flankDirection = 0;
[Header("Prediction")]
[Tooltip("是否在 CloseIn 模式下启用玩家位置预测?")]
public bool usePlayerPrediction = true;
[Tooltip("预测玩家未来的提前量时间(秒)。")]
public float predictionLeadTime = 1.0f;
[Header("NavMesh Validation")]
[Tooltip("是否对计算出的落点在 NavMesh 上进行合法性采样?(防止跳出地图边缘/掉落悬崖)")]
public bool validateOnNavMesh = true;
[Tooltip("NavMesh 采样半径。")]
public float navMeshSampleRadius = 2f;
public override TaskStatus OnUpdate()
{
if (self == null || self.player == null)
return TaskStatus.Failure;
Vector3 computedTarget = self.transform.position;
Vector3 playerPos = self.player.transform.position;
Vector3 selfPos = self.transform.position;
switch (strategy)
{
case JumpStrategy.CloseIn:
Vector3 basePlayerPos = usePlayerPrediction
? self.PredictPlayerPosition(predictionLeadTime)
: playerPos;
Vector3 toPlayer = basePlayerPos - selfPos;
toPlayer.y = 0f;
if (toPlayer.magnitude > closeInArrivalRadius)
{
// 敌人跳到连线与设定半径的交点上
computedTarget = basePlayerPos - toPlayer.normalized * closeInArrivalRadius;
}
else
{
computedTarget = basePlayerPos;
}
break;
case JumpStrategy.Retreat:
// 计算 Boss 朝向的后方
Vector3 backDir = -self.transform.forward;
backDir.y = 0f;
backDir.Normalize();
computedTarget = selfPos + backDir * retreatDistance;
break;
case JumpStrategy.Flank:
// 计算从玩家指向 Boss 的方向
Vector3 toBoss = selfPos - playerPos;
toBoss.y = 0f;
toBoss.Normalize();
// 计算垂直的切线方向 (左右)
Vector3 tangent = new Vector3(-toBoss.z, 0f, toBoss.x); // 玩家右侧
int dir = flankDirection;
if (dir == 0)
{
dir = Random.value > 0.5f ? 1 : -1;
}
computedTarget = playerPos + tangent * (flankDistance * dir);
break;
case JumpStrategy.EscapeCorner:
Vector3 toCenter = arenaCenter.Value - selfPos;
toCenter.y = 0f;
toCenter.Normalize();
computedTarget = selfPos + toCenter * retreatDistance;
break;
case JumpStrategy.EscapeHazard:
// 默认向远离玩家方向跳跃,作为基础避险
Vector3 awayDir = selfPos - playerPos;
awayDir.y = 0f;
awayDir.Normalize();
computedTarget = selfPos + awayDir * retreatDistance;
break;
case JumpStrategy.SelfSlam:
computedTarget = selfPos;
break;
}
// 对计算出的落点在 NavMesh 上进行合法性采样,防止跳出地图
if (validateOnNavMesh)
{
if (NavMesh.SamplePosition(computedTarget, out NavMeshHit hit, navMeshSampleRadius, NavMesh.AllAreas))
{
computedTarget = hit.position;
}
else
{
// 如果采样失败,尝试向玩家位置收缩
Vector3 dirToPlayer = playerPos - computedTarget;
if (NavMesh.SamplePosition(computedTarget + dirToPlayer * 0.5f, out hit, navMeshSampleRadius, NavMesh.AllAreas))
{
computedTarget = hit.position;
}
}
}
// 写入行为树变量与调试缓存
debugComputedTarget = computedTarget;
jumpLandPosition.SetValue(computedTarget);
return TaskStatus.Success;
}
private Vector3 debugComputedTarget;
protected override void OnDrawGizmos()
{
#if UNITY_EDITOR
if (debugComputedTarget != Vector3.zero)
{
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(debugComputedTarget, 0.4f);
Gizmos.color = new Color(0.2f, 0.8f, 1.0f, 0.25f);
Gizmos.DrawSphere(debugComputedTarget, 0.4f);
if (self != null)
{
Gizmos.color = Color.cyan;
Gizmos.DrawLine(self.transform.position, debugComputedTarget);
}
}
#endif
}
}
}