Files
Cielonos/Assets/Scripts/MainGame/Characters/Base/Subcontrollers/Animation/AnimationSubcontrollerBase.cs
2026-04-18 13:57:19 -04:00

247 lines
9.5 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
{
public partial class AnimationSubcontrollerBase : SubcontrollerBase<CharacterBase>
{
public Animator animator;
public AnimatorStateMapper mapper;
public Dictionary<DisruptionType, bool> disruptionStatus;
public bool isDuringRootMotion;
public bool isDisablingMoveXZ;
public bool isDisablingMoveY;
public BaseAnimationGroup defaultAnimationGroup;
public FunctionalAnimationSubmodule fullBodyFuncAnimSm;
public Dictionary<string, Action<RuntimeFuncAnim>> registeredFunctions;
public List<FuncAnimInterval> lastFrameIntervals { get; private set; }
public List<FuncAnimInterval> currentIntervals { get; private set; }
public override void Initialize()
{
base.Initialize();
fullBodyFuncAnimSm = new FunctionalAnimationSubmodule(this, "FullBodyAction");
registeredFunctions = new Dictionary<string, Action<RuntimeFuncAnim>>();
defaultAnimationGroup?.SetUp(this);
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();
}
protected virtual void Update()
{
if (owner.statusSm.isDead)
{
return;
}
fullBodyFuncAnimSm?.UpdateTime();
UpdateIntervalInfo();
}
protected virtual void LateUpdate()
{
fullBodyFuncAnimSm?.UpdateEvents();
BoneShakeLateUpdate();
}
}
public partial class AnimationSubcontrollerBase
{
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);
}
}
public partial class AnimationSubcontrollerBase
{
private class BoneShakeState
{
public Transform bone;
public Vector3 shakeAxis; // 震动轴
// 物理变量
public float currentAngle = 0f; // 当前偏离角度 (位移 x)
public float velocity = 0f; // 当前震动速度 (速度 v)
}
[Header("Bone Shake Settings")]
// 刚度:越大越硬,回弹越快。对于重型机甲,建议 150-300轻型无人机建议 80-150。
public float stiffness = 200f;
// 阻尼:越大停得越快。建议 10-20。如果太小机器人会像果冻一样晃。
public float damping = 15f;
// 冲击力倍率:将受击力度转化为弹簧的初始速度
public float impactForceMultiplier = 500f;
public List<Transform> testShakeBones; // 用于测试的骨骼列表
private List<BoneShakeState> activeShakes = new List<BoneShakeState>();
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
// force = -stiffness * displacement - damping * velocity
float force = -stiffness * state.currentAngle - damping * state.velocity;
// a = F / m (假设质量为1简化计算)
// v += a * dt
state.velocity += force * dt;
// x += v * dt
state.currentAngle += state.velocity * dt;
// --- 应用旋转 ---
// 将计算出的角度应用到轴向上
Quaternion shakeRot = Quaternion.AngleAxis(state.currentAngle, state.shakeAxis);
state.bone.localRotation = state.bone.localRotation * shakeRot;
// --- 移除条件 ---
// 当能量非常小速度和位移都接近0时移除节省性能
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;
}
}
public partial class AnimationSubcontrollerBase
{
public virtual void RegisterDefaultFunctions()
{
registeredFunctions.Add("SetRootMotionMoveXMultiplier", anim =>
{
owner.movementSc.rootMotionMoveXMultiplier = anim.runtimeVariables.GetVariable<float>("RootMotionMoveXMultiplier");
});
registeredFunctions.Add("SetRootMotionMoveYMultiplier", anim =>
{
owner.movementSc.rootMotionMoveYMultiplier = anim.runtimeVariables.GetVariable<float>("RootMotionMoveYMultiplier");
});
registeredFunctions.Add("SetRootMotionMoveZMultiplier", anim =>
{
owner.movementSc.rootMotionMoveZMultiplier = anim.runtimeVariables.GetVariable<float>("RootMotionMoveZMultiplier");
});
}
}
public partial class AnimationSubcontrollerBase
{
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)
{
if (fullBodyFuncAnimSm.Play(funcAnimName))
{
animDuration = fullBodyFuncAnimSm.currentData.Interval(IntervalType.ActionDisruption).StartTime;
}
animDuration = 0.2f;
//animDuration = fullBodyFuncAnimSm.currentData.Interval(IntervalType.Active).EndTime;
}
}
}