Files
Cielonos/Assets/Scripts/MainGame/Base/FunctionalAnimation/Payloads/ManualMove.cs

270 lines
10 KiB
C#
Raw Normal View History

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