188 lines
7.6 KiB
C#
188 lines
7.6 KiB
C#
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
|
||
}
|
||
}
|
||
}
|