Files
Cielonos/Assets/Scripts/MainGame/Characters/Base/Subcontrollers/Animation/AnimationSubcontrollerBase.cs
SoulliesOfficial 7bc1e1722c 爆更
2026-06-05 04:21:00 -04:00

246 lines
9.0 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 System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using SLSUtilities.General;
using SLSUtilities.FunctionalAnimation;
using UnityEngine;
using UnityEngine.Serialization;
using Random = UnityEngine.Random;
namespace Cielonos.MainGame.Characters
{
/// <summary>
/// 动画子控制器基类。
/// 负责管理角色的核心 Animator、动作覆盖、打断状态以及受击时的骨骼抖动物理效果。
/// </summary>
public partial class AnimationSubcontrollerBase : SubcontrollerBase<CharacterBase>
{
#region Fields & Properties
[TitleGroup("Animator Settings", "底层状态机配置", Alignment = TitleAlignments.Centered)]
public Animator animator;
public AnimatorStateMapper mapper;
[TitleGroup("Functional Animation Submodules", "动作播放子模块", Alignment = TitleAlignments.Centered)]
public FuncAnimSubmodule fullBodyFuncAnimSm;
public FuncAnimSubmodule upperBodyFuncAnimSm;
[TitleGroup("State Flags & Intervals", "打断状态与控制标志", Alignment = TitleAlignments.Centered)]
public Dictionary<DisruptionType, bool> disruptionStatus;
public bool isDuringRootMotion;
public bool isDisablingMoveXZ;
public bool isDisablingMoveY;
public List<FuncAnimInterval> LastFrameIntervals { get; private set; }
public List<FuncAnimInterval> CurrentIntervals { get; private set; }
[TitleGroup("Callbacks", "事件回调注册表", Alignment = TitleAlignments.Centered)]
public Dictionary<string, Action<RuntimeFuncAnim>> registeredFunctions;
#region Bone Shake Fields
[TitleGroup("Bone Shake Settings", "受击骨骼震动物理参数", Alignment = TitleAlignments.Centered)]
[Tooltip("刚度:越大回弹越快。对于重型机甲,建议 150-300轻型无人机建议 80-150。")]
public float stiffness = 200f;
[Tooltip("阻尼:越大停得越快,建议 10-20。")]
public float damping = 15f;
[Tooltip("冲击力倍率:将受击强度转化为震动初速度")]
public float impactForceMultiplier = 500f;
[Tooltip("参与受击震动的测试骨骼列表")]
public List<Transform> testShakeBones;
private List<BoneShakeState> activeShakes = new List<BoneShakeState>();
private class BoneShakeState
{
public Transform bone;
public Vector3 shakeAxis;
public float currentAngle = 0f;
public float velocity = 0f;
}
#endregion
#endregion
#region Initialization
public override void Initialize()
{
base.Initialize();
fullBodyFuncAnimSm = new FuncAnimSubmodule(this, "FullBodyAction");
upperBodyFuncAnimSm = new FuncAnimSubmodule(this, "UpperBodyAction");
registeredFunctions = new Dictionary<string, Action<RuntimeFuncAnim>>();
disruptionStatus = new Dictionary<DisruptionType, bool>()
{
{ DisruptionType.ForcedAction , false},
{ DisruptionType.ForcedExternal , false},
{ DisruptionType.NormalExternal, false },
{ DisruptionType.NormalAction, false },
{ DisruptionType.Movement, false }
};
LastFrameIntervals = new List<FuncAnimInterval>();
CurrentIntervals = new List<FuncAnimInterval>();
RegisterDefaultFunctions();
}
public virtual void RegisterDefaultFunctions()
{
// 在派生类中扩展具体的动作事件回调注册
}
#endregion
#region Unity Lifecycle (Update & LateUpdate)
protected virtual void Update()
{
if (owner.statusSm.isDead)
{
return;
}
fullBodyFuncAnimSm?.UpdateTime();
UpdateIntervalInfo();
}
protected virtual void LateUpdate()
{
fullBodyFuncAnimSm?.UpdateEvents();
BoneShakeLateUpdate();
}
#endregion
#region Interval Evaluation
protected virtual void UpdateIntervalInfo()
{
if (fullBodyFuncAnimSm?.currentRuntimeFuncAnim == null)
{
isDuringRootMotion = false;
return;
}
RuntimeFuncAnim currentFuncAnim = fullBodyFuncAnimSm.currentRuntimeFuncAnim;
LastFrameIntervals = CurrentIntervals;
CurrentIntervals = currentFuncAnim.GetEnablingIntervals();
disruptionStatus[DisruptionType.NormalExternal] = CurrentIntervals.Any(interval => interval.intervalType == IntervalType.ExternalDisruption);
disruptionStatus[DisruptionType.NormalAction] = CurrentIntervals.Any(interval => interval.intervalType == IntervalType.ActionDisruption);
disruptionStatus[DisruptionType.Movement] = CurrentIntervals.Any(interval => interval.intervalType == IntervalType.MovementDisruption);
if (currentFuncAnim.HasIntervalType(IntervalType.ForcedActionDisruption))
{
disruptionStatus[DisruptionType.ForcedAction] = CurrentIntervals.Any(interval => interval.intervalType == IntervalType.ForcedActionDisruption);
}
else
{
disruptionStatus[DisruptionType.ForcedAction] = true;
}
if (currentFuncAnim.HasIntervalType(IntervalType.ForcedExternalDisruption))
{
disruptionStatus[DisruptionType.ForcedExternal] = CurrentIntervals.Any(interval => interval.intervalType == IntervalType.ForcedExternalDisruption);
}
else
{
disruptionStatus[DisruptionType.ForcedExternal] = true;
}
isDuringRootMotion = currentFuncAnim.funcAnimData.animInfo.useRootMotion &&
CurrentIntervals.Any(interval => interval.intervalType == IntervalType.RootMotion);
}
#endregion
#region Bone Shake Core
private void BoneShakeLateUpdate()
{
float dt = owner.selfTimeSm.DeltaTime;
for (int i = activeShakes.Count - 1; i >= 0; i--)
{
var state = activeShakes[i];
if (state.bone == null) {
activeShakes.RemoveAt(i);
continue;
}
// 阻尼弹簧物理公式 (Hooke's Law + Damping): F = -k * x - d * v
float force = -stiffness * state.currentAngle - damping * state.velocity;
state.velocity += force * dt;
state.currentAngle += state.velocity * dt;
// 应用偏转角度
Quaternion shakeRot = Quaternion.AngleAxis(state.currentAngle, state.shakeAxis);
state.bone.localRotation = state.bone.localRotation * shakeRot;
// 能量极小时剔除,避免持续旋转计算开销
if (Mathf.Abs(state.currentAngle) < 0.1f && Mathf.Abs(state.velocity) < 0.1f)
{
activeShakes.RemoveAt(i);
}
}
}
public void ApplyBoneShake(Transform hitBone, Vector3 hitDirection, float intensity)
{
var state = activeShakes.Find(x => x.bone == hitBone);
if (state == null)
{
state = new BoneShakeState();
state.bone = hitBone;
activeShakes.Add(state);
}
// 给予物理弹簧初速度 (Impulse)
state.velocity += intensity * impactForceMultiplier;
// 计算垂直于受击方向的震动轴
Vector3 axis = Vector3.Cross(hitDirection, Vector3.up).normalized;
if (axis == Vector3.zero) axis = Vector3.right;
state.shakeAxis = axis;
}
#endregion
#region Hit Feedbacks
public virtual void PlayGetHitBoneShake(float intensity, Vector3 direction = default)
{
if (direction == default)
{
direction = owner.transform.right;
}
else
{
direction = Quaternion.Euler(0, 90, 0) * direction;
}
foreach (Transform bone in testShakeBones)
{
ApplyBoneShake(bone, direction, intensity);
}
}
public virtual void PlayGetHitAnimation(string funcAnimName, out float animDuration,
Vector3 direction = default)
{
animDuration = 0.2f;
if (fullBodyFuncAnimSm.Play(funcAnimName))
{
FuncAnimInterval interval = fullBodyFuncAnimSm.currentData.Interval(IntervalType.MovementDisruption);
animDuration = MathExtensions.Average(interval.StartTime, interval.EndTime);
}
}
#endregion
}
}