Files
Cielonos/Assets/Opsive/BehaviorDesigner/Add-Ons/CielonosPack/Actions/Movement/CalculateJumpTarget.cs

188 lines
7.6 KiB
C#
Raw Normal View History

2026-06-27 12:52:03 -04:00
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
}
}
}