Files
Cielonos/Assets/Scripts/MainGame/Base/FunctionalAnimation/Payloads/ManualMove.cs
SoulliesOfficial 9a9e48f8a5
2026-06-27 12:52:03 -04:00

270 lines
10 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 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
}
}
}