Files
Cielonos/Assets/Scripts/MainGame/Characters/Base/Subcontrollers/Animation/AnimationSubcontrollerBase.cs

243 lines
9.5 KiB
C#
Raw Normal View History

2025-11-25 08:19:33 -05:00
using System;
using System.Collections.Generic;
using System.Linq;
2026-01-03 18:19:39 -05:00
using Sirenix.OdinInspector;
2026-02-13 09:22:11 -05:00
using SLSUtilities.General;
2025-11-25 08:19:33 -05:00
using SLSUtilities.FunctionalAnimation;
using UnityEngine;
using UnityEngine.Serialization;
2025-12-17 04:19:38 -05:00
using Random = UnityEngine.Random;
2025-11-25 08:19:33 -05:00
namespace Cielonos.MainGame.Characters
{
public partial class AnimationSubcontrollerBase : SubcontrollerBase<CharacterBase>
{
public Animator animator;
2026-01-03 18:19:39 -05:00
public AnimatorStateMapper mapper;
2025-11-25 08:19:33 -05:00
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>()
{
2026-01-03 18:19:39 -05:00
{ DisruptionType.ForcedAction , false},
{ DisruptionType.ForcedExternal , false},
2025-11-25 08:19:33 -05:00
{ DisruptionType.NormalExternal, false },
{ DisruptionType.NormalAction, false },
{ DisruptionType.Movement, false }
};
lastFrameIntervals = new List<FuncAnimInterval>();
currentIntervals = new List<FuncAnimInterval>();
RegisterDefaultFunctions();
}
protected virtual void Update()
{
2025-12-08 05:27:53 -05:00
if (owner.statusSm.isDead)
{
return;
}
2025-11-25 08:19:33 -05:00
fullBodyFuncAnimSm?.UpdateTime();
UpdateIntervalInfo();
}
protected virtual void LateUpdate()
{
fullBodyFuncAnimSm?.UpdateEvents();
2025-12-17 04:19:38 -05:00
BoneShakeLateUpdate();
2025-11-25 08:19:33 -05:00
}
}
public partial class AnimationSubcontrollerBase
{
protected virtual void UpdateIntervalInfo()
{
if (fullBodyFuncAnimSm?.currentRuntimeFuncAnim == null)
{
isDuringRootMotion = false;
return;
}
2026-01-12 03:22:16 -05:00
RuntimeFuncAnim currentFuncAnim = fullBodyFuncAnimSm.currentRuntimeFuncAnim;
2025-11-25 08:19:33 -05:00
lastFrameIntervals = currentIntervals;
2026-01-12 03:22:16 -05:00
currentIntervals = currentFuncAnim.GetEnablingIntervals();
2025-11-25 08:19:33 -05:00
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);
2026-01-12 03:22:16 -05:00
if (currentFuncAnim.HasIntervalType(IntervalType.ForcedActionDisruption))
2026-01-03 18:19:39 -05:00
{
disruptionStatus[DisruptionType.ForcedAction] = currentIntervals.Any(interval => interval.intervalType == IntervalType.ForcedActionDisruption);
}
else
{
disruptionStatus[DisruptionType.ForcedAction] = true;
}
2026-01-12 03:22:16 -05:00
if (currentFuncAnim.HasIntervalType(IntervalType.ForcedExternalDisruption))
2026-01-03 18:19:39 -05:00
{
disruptionStatus[DisruptionType.ForcedExternal] = currentIntervals.Any(interval => interval.intervalType == IntervalType.ForcedExternalDisruption);
}
else
{
disruptionStatus[DisruptionType.ForcedExternal] = true;
}
2026-01-12 03:22:16 -05:00
isDuringRootMotion = currentFuncAnim.funcAnimData.animInfo.useRootMotion &&
currentIntervals.Any(interval => interval.intervalType == IntervalType.RootMotion);
2025-11-25 08:19:33 -05:00
}
}
2025-12-17 04:19:38 -05:00
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()
{
2025-12-22 18:36:29 -05:00
float dt = owner.selfTimeSm.DeltaTime;
2025-12-17 04:19:38 -05:00
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;
}
}
2025-11-25 08:19:33 -05:00
public partial class AnimationSubcontrollerBase
{
public virtual void RegisterDefaultFunctions()
{
2025-12-08 05:27:53 -05:00
registeredFunctions.Add("SetRootMotionMoveXMultiplier", anim =>
{
owner.movementSc.rootMotionMoveXMultiplier = anim.runtimeVariables.GetVariable<float>("RootMotionMoveXMultiplier");
});
registeredFunctions.Add("SetRootMotionMoveYMultiplier", anim =>
{
2026-03-20 12:07:44 -04:00
owner.movementSc.rootMotionMoveYMultiplier = anim.runtimeVariables.GetVariable<float>("RootMotionMoveYMultiplier");
2025-12-08 05:27:53 -05:00
});
2025-11-25 08:19:33 -05:00
2025-12-08 05:27:53 -05:00
registeredFunctions.Add("SetRootMotionMoveZMultiplier", anim =>
{
2026-03-20 12:07:44 -04:00
owner.movementSc.rootMotionMoveZMultiplier = anim.runtimeVariables.GetVariable<float>("RootMotionMoveZMultiplier");
2025-12-08 05:27:53 -05:00
});
2025-11-25 08:19:33 -05:00
}
}
public partial class AnimationSubcontrollerBase
{
2025-12-17 04:19:38 -05:00
public virtual void PlayGetHitBoneShake(float intensity, Vector3 direction = default)
2025-11-25 08:19:33 -05:00
{
2025-12-17 04:19:38 -05:00
if (direction == default)
{
direction = owner.transform.right;
}
else
{
direction = Quaternion.Euler(0, 90, 0) * direction;
}
2025-11-25 08:19:33 -05:00
2025-12-17 04:19:38 -05:00
foreach (Transform bone in testShakeBones)
2025-11-25 08:19:33 -05:00
{
2025-12-17 04:19:38 -05:00
ApplyBoneShake(bone, direction, intensity);
}
}
2025-11-25 08:19:33 -05:00
2026-04-12 02:11:15 -04:00
public virtual void PlayGetHitAnimation(string funcAnimName, out float animDuration,
Vector3 direction = default)
2025-12-17 04:19:38 -05:00
{
2026-04-12 02:11:15 -04:00
fullBodyFuncAnimSm.Play(funcAnimName);
animDuration = fullBodyFuncAnimSm.currentData.Interval(IntervalType.ActionDisruption).StartTime;
//animDuration = fullBodyFuncAnimSm.currentData.Interval(IntervalType.Active).EndTime;
2025-11-25 08:19:33 -05:00
}
}
}