270 lines
10 KiB
C#
270 lines
10 KiB
C#
using System;
|
||
using Cielonos.MainGame.Characters;
|
||
using Sirenix.OdinInspector;
|
||
using SLSUtilities.FunctionalAnimation;
|
||
using UnityEngine;
|
||
|
||
namespace Cielonos.MainGame.FunctionalAnimation
|
||
{
|
||
[Serializable]
|
||
[EventColor(0.2f, 0.7f, 1.0f)] // 蓝色的时间轴事件
|
||
public class ManualMove : FuncAnimPayloadBase
|
||
{
|
||
public override bool IsCombatOnly => true;
|
||
|
||
[Header("Target Settings")]
|
||
[Tooltip("目标获取方式:从行为树的变量中获取 Vector3 坐标,或者从行为树的变量中获取 GameObject 进行追踪。")]
|
||
public TargetSourceType targetSource = TargetSourceType.Vector3Variable;
|
||
|
||
[ShowIf("targetSource", TargetSourceType.Vector3Variable)]
|
||
[Tooltip("行为树中存储目标 Vector3 坐标的变量名称。")]
|
||
public string targetPositionVariableName = "TargetPosition";
|
||
|
||
[ShowIf("targetSource", TargetSourceType.GameObjectVariable)]
|
||
[Tooltip("行为树中存储目标 GameObject 的变量名称。")]
|
||
public string targetGameObjectVariableName = "Target";
|
||
|
||
[ShowIf("targetSource", TargetSourceType.GameObjectVariable)]
|
||
[Tooltip("是否启用前置位置预测(Lead Prediction)?仅当目标是玩家时生效。")]
|
||
public bool usePlayerPrediction = false;
|
||
|
||
[Header("Movement Frame Config")]
|
||
[Tooltip("移动持续的动画帧数。")]
|
||
public int moveFrameCount = 30;
|
||
|
||
[Tooltip("动画自身的帧率(默认30帧/秒)。用于换算持续秒数。")]
|
||
public float animFrameRate = 30f;
|
||
|
||
[Header("Curve & Height")]
|
||
[Tooltip("水平移动插值的曲线(X轴是0-1进度,Y轴是插值权重0-1)。")]
|
||
public AnimationCurve horizontalCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||
|
||
[Tooltip("是否启用垂直高度弧线(手动跳跃抛物线)?若为 false,将保留动画自身的 Y 轴根运动与重力。")]
|
||
public bool enableVerticalArc = false;
|
||
|
||
[ShowIf("enableVerticalArc")]
|
||
[Tooltip("跳跃的最大拱起高度(米)。")]
|
||
public float peakHeight = 3.0f;
|
||
|
||
[Header("Collision & Physics")]
|
||
[Tooltip("是否在移动期间临时关闭碰撞体?(建议设为 false,以使 Boss 能够正常与墙体碰撞,防止穿墙异常)")]
|
||
public bool disableCollisionDuringMove = false;
|
||
|
||
// 运行时状态标志
|
||
[HideInInspector]
|
||
[NonSerialized]
|
||
private bool isUpdateInstance = false;
|
||
|
||
[HideInInspector]
|
||
[NonSerialized]
|
||
private Vector3 startPosition;
|
||
|
||
[HideInInspector]
|
||
[NonSerialized]
|
||
private Vector3 targetPosition;
|
||
|
||
[HideInInspector]
|
||
[NonSerialized]
|
||
private float duration;
|
||
|
||
[HideInInspector]
|
||
[NonSerialized]
|
||
private float elapsedTime;
|
||
|
||
[HideInInspector]
|
||
[NonSerialized]
|
||
private bool isFinished = false;
|
||
|
||
[HideInInspector]
|
||
[NonSerialized]
|
||
private bool hasCleanedUp = false;
|
||
|
||
[HideInInspector]
|
||
[NonSerialized]
|
||
private AutomataLandMovementSubcontroller movementController;
|
||
|
||
[HideInInspector]
|
||
[NonSerialized]
|
||
private bool originalGravityState = true;
|
||
|
||
public override void Invoke()
|
||
{
|
||
if (isUpdateInstance)
|
||
{
|
||
RunUpdate();
|
||
}
|
||
else
|
||
{
|
||
StartMovement();
|
||
}
|
||
}
|
||
|
||
private void StartMovement()
|
||
{
|
||
if (Character == null) return;
|
||
|
||
// 1. 获取目标点
|
||
Vector3 finalTargetPos = Character.transform.position; // 缺省为原位
|
||
|
||
BehaviorSubcontroller behaviorSc = (Character as Automata)!.behaviorSc;
|
||
if (behaviorSc != null && behaviorSc.mainBehaviorTree != null)
|
||
{
|
||
if (targetSource == TargetSourceType.Vector3Variable)
|
||
{
|
||
var varObj = behaviorSc.mainBehaviorTree.GetVariable(targetPositionVariableName);
|
||
if (varObj != null)
|
||
{
|
||
finalTargetPos = (Vector3)varObj.GetValue();
|
||
}
|
||
}
|
||
else if (targetSource == TargetSourceType.GameObjectVariable)
|
||
{
|
||
var varObj = behaviorSc.mainBehaviorTree.GetVariable(targetGameObjectVariableName);
|
||
if (varObj != null && varObj.GetValue() is GameObject targetGo)
|
||
{
|
||
finalTargetPos = targetGo.transform.position;
|
||
|
||
// 玩家位置预测
|
||
if (usePlayerPrediction && Character is Automata automata && targetGo.GetComponent<Player>() != null)
|
||
{
|
||
float calcDuration = moveFrameRateDuration();
|
||
finalTargetPos = automata.PredictPlayerPosition(calcDuration);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. 准备克隆作为每帧更新实例
|
||
ManualMove updateClone = (ManualMove)this.DeepClone();
|
||
updateClone.isUpdateInstance = true;
|
||
updateClone.startPosition = Character.transform.position;
|
||
updateClone.targetPosition = finalTargetPos;
|
||
updateClone.duration = moveFrameRateDuration();
|
||
updateClone.elapsedTime = 0f;
|
||
updateClone.isFinished = false;
|
||
updateClone.hasCleanedUp = false;
|
||
updateClone.movementController = Character.movementSc as AutomataLandMovementSubcontroller;
|
||
|
||
if (updateClone.movementController != null)
|
||
{
|
||
updateClone.originalGravityState = updateClone.movementController.isApplyingGravity;
|
||
|
||
// 3. 挂起导航
|
||
if (updateClone.movementController.navMeshAgent.enabled)
|
||
{
|
||
updateClone.movementController.navMeshAgent.enabled = false;
|
||
}
|
||
|
||
// 4. 若手动接管 Y 轴抛物线,则临时挂起重力计算
|
||
if (enableVerticalArc)
|
||
{
|
||
updateClone.movementController.isApplyingGravity = false;
|
||
}
|
||
}
|
||
|
||
// 5. 临时关闭碰撞体 (如果勾选)
|
||
if (disableCollisionDuringMove && Character.collisionSc != null)
|
||
{
|
||
Character.collisionSc.DisableAllColliders();
|
||
}
|
||
|
||
// 6. 注册到 FuncAnim 的运行时 Update 队列中,以驱动每帧更新
|
||
runtimeFuncAnim.AddUpdateEvent(updateClone);
|
||
}
|
||
|
||
private void RunUpdate()
|
||
{
|
||
if (isFinished || Character == null) return;
|
||
|
||
// 自我保护与打断清理:若动作已被打断、或当前正在播放的动作已发生更换,立刻中断
|
||
if (runtimeFuncAnim.isDisrupted || Character.animationSc.fullBodyFuncAnimSm.currentRuntimeFuncAnim != runtimeFuncAnim)
|
||
{
|
||
isFinished = true;
|
||
EndMovement();
|
||
return;
|
||
}
|
||
|
||
float dt = Character.selfTimeSm.DeltaTime;
|
||
elapsedTime += dt;
|
||
float progress = duration > 0f ? Mathf.Clamp01(elapsedTime / duration) : 1f;
|
||
|
||
// 1. 计算当前的水平坐标 XZ
|
||
float curveT = horizontalCurve != null ? horizontalCurve.Evaluate(progress) : progress;
|
||
Vector3 currentHorizontalPos = Vector3.Lerp(startPosition, targetPosition, curveT);
|
||
|
||
// 2. 计算上一步的水平坐标 XZ
|
||
float prevProgress = duration > 0f ? Mathf.Clamp01((elapsedTime - dt) / duration) : 0f;
|
||
float prevCurveT = horizontalCurve != null ? horizontalCurve.Evaluate(prevProgress) : prevProgress;
|
||
Vector3 prevHorizontalPos = Vector3.Lerp(startPosition, targetPosition, prevCurveT);
|
||
|
||
// XZ 位移差
|
||
Vector3 deltaPosition = currentHorizontalPos - prevHorizontalPos;
|
||
deltaPosition.y = 0f;
|
||
|
||
// 3. 如果启用垂直高度弧线 (重写 Y 轴)
|
||
if (enableVerticalArc && peakHeight > 0f)
|
||
{
|
||
float arcY = 4f * peakHeight * progress * (1f - progress);
|
||
float prevArcY = 4f * peakHeight * prevProgress * (1f - prevProgress);
|
||
|
||
float currentY = Mathf.Lerp(startPosition.y, targetPosition.y, progress) + arcY;
|
||
float prevY = Mathf.Lerp(startPosition.y, targetPosition.y, prevProgress) + prevArcY;
|
||
|
||
deltaPosition.y = currentY - prevY;
|
||
}
|
||
|
||
// 4. 将位移量传递给 MovementController 进行物理应用,而非直接赋值 transform.position
|
||
if (movementController != null)
|
||
{
|
||
movementController.customHorizontalMovementOverride += deltaPosition;
|
||
}
|
||
|
||
// 5. 检查移动结束
|
||
if (progress >= 1f)
|
||
{
|
||
isFinished = true;
|
||
EndMovement();
|
||
}
|
||
}
|
||
|
||
private void EndMovement()
|
||
{
|
||
if (hasCleanedUp) return;
|
||
hasCleanedUp = true;
|
||
|
||
if (Character == null) return;
|
||
|
||
// 1. 恢复碰撞体
|
||
if (disableCollisionDuringMove && Character.collisionSc != null)
|
||
{
|
||
Character.collisionSc.EnableAllColliders();
|
||
}
|
||
|
||
// 2. 重新激活并对齐导航系统,恢复重力
|
||
if (movementController != null)
|
||
{
|
||
movementController.isApplyingGravity = originalGravityState;
|
||
|
||
if (!movementController.navMeshAgent.enabled)
|
||
{
|
||
movementController.navMeshAgent.enabled = true;
|
||
movementController.navMeshAgent.baseOffset = 0f; // 落地瞬间重置导航高度偏移,消除浮空
|
||
movementController.navMeshAgent.Warp(Character.transform.position);
|
||
}
|
||
}
|
||
}
|
||
|
||
private float moveFrameRateDuration()
|
||
{
|
||
float rate = animFrameRate > 0f ? animFrameRate : 30f;
|
||
return (float)moveFrameCount / rate;
|
||
}
|
||
|
||
public enum TargetSourceType
|
||
{
|
||
Vector3Variable,
|
||
GameObjectVariable
|
||
}
|
||
}
|
||
}
|