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;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
using SLSFramework.General;
|
|
|
|
|
|
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 =>
|
|
|
|
|
|
{
|
|
|
|
|
|
owner.movementSc.rootMotionMoveYMultiplier = anim.runtimeVariables.GetVariable<float>("SetRootMotionMoveYMultiplier");
|
|
|
|
|
|
});
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
2025-12-08 05:27:53 -05:00
|
|
|
|
registeredFunctions.Add("SetRootMotionMoveZMultiplier", anim =>
|
|
|
|
|
|
{
|
|
|
|
|
|
owner.movementSc.rootMotionMoveZMultiplier = anim.runtimeVariables.GetVariable<float>("SetRootMotionMoveZMultiplier");
|
|
|
|
|
|
});
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public partial class AnimationSubcontrollerBase
|
|
|
|
|
|
{
|
|
|
|
|
|
public virtual bool SetGetHitDisruption(DisruptionType disruptionType, BreakthroughType breakthroughType)
|
|
|
|
|
|
{
|
2025-12-08 05:27:53 -05:00
|
|
|
|
if (owner.reactionSc.breakthroughResistances[breakthroughType])
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (fullBodyFuncAnimSm.Stop(disruptionType))
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
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
|
|
|
|
|
2025-12-17 04:19:38 -05:00
|
|
|
|
protected virtual void PlayGetHitAnimation(string getHitAnimPrefix, out float animDuration, Vector3 direction = default)
|
|
|
|
|
|
{
|
|
|
|
|
|
int fullBodyActionIndex = animator.GetLayerIndex("FullBodyAction");
|
|
|
|
|
|
string getHitFrontAnim = getHitAnimPrefix + "Front";
|
|
|
|
|
|
string getHitAnim = getHitAnimPrefix + "Front";
|
|
|
|
|
|
float normalizedTransitionDuration = 0.1f / animator.GetCurrentAnimatorStateInfo(fullBodyActionIndex).length;
|
|
|
|
|
|
animDuration = 0f;
|
|
|
|
|
|
if (animator.HasState(fullBodyActionIndex, Animator.StringToHash(getHitAnim)))
|
|
|
|
|
|
{
|
2025-11-25 08:19:33 -05:00
|
|
|
|
direction.y = 0;
|
|
|
|
|
|
direction = direction.normalized;
|
|
|
|
|
|
float angle = Vector3.SignedAngle(transform.forward, direction, Vector3.up);
|
2025-12-17 04:19:38 -05:00
|
|
|
|
|
|
|
|
|
|
string directionStr = angle switch
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2025-12-17 04:19:38 -05:00
|
|
|
|
> -45f and <= 45f => "Back",
|
|
|
|
|
|
> 45f and <= 135f => "Left",
|
|
|
|
|
|
> -135f and <= -45f => "Right",
|
|
|
|
|
|
_ => "Front"
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
getHitAnim = getHitAnimPrefix + directionStr;
|
|
|
|
|
|
if (direction == default || !animator.HasState(fullBodyActionIndex, Animator.StringToHash(getHitAnim)))
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2025-12-17 04:19:38 -05:00
|
|
|
|
animator.CrossFade(getHitFrontAnim, normalizedTransitionDuration, fullBodyActionIndex, 0);
|
2026-01-03 18:19:39 -05:00
|
|
|
|
animDuration = mapper.GetClip(getHitFrontAnim).length;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-12-17 04:19:38 -05:00
|
|
|
|
animator.CrossFade(getHitAnim, normalizedTransitionDuration, fullBodyActionIndex, 0);
|
2026-01-03 18:19:39 -05:00
|
|
|
|
animDuration = mapper.GetClip(getHitAnim).length;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
2025-12-17 04:19:38 -05:00
|
|
|
|
}
|
|
|
|
|
|
else if (animator.HasState(fullBodyActionIndex, Animator.StringToHash(getHitAnimPrefix)))
|
|
|
|
|
|
{
|
|
|
|
|
|
getHitAnim = getHitAnimPrefix;
|
|
|
|
|
|
animator.CrossFade(getHitAnim, normalizedTransitionDuration, fullBodyActionIndex, 0);
|
2026-01-03 18:19:39 -05:00
|
|
|
|
animDuration = mapper.GetClip(getHitAnim).length;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (animator.HasState(fullBodyActionIndex, Animator.StringToHash("GetHit")))
|
|
|
|
|
|
{
|
2025-12-17 04:19:38 -05:00
|
|
|
|
animator.CrossFade("GetHit", normalizedTransitionDuration, fullBodyActionIndex, 0);
|
2026-01-03 18:19:39 -05:00
|
|
|
|
animDuration = mapper.GetClip("GetHit").length;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-12-22 18:36:29 -05:00
|
|
|
|
animator.CrossFade("Empty", 0.1f, fullBodyActionIndex, 0);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-17 04:19:38 -05:00
|
|
|
|
|
|
|
|
|
|
public virtual void PlayGetHitMediumAnimation(out float animDuration, Vector3 direction = default)
|
|
|
|
|
|
{
|
|
|
|
|
|
PlayGetHitAnimation("GetHitMedium", out animDuration, direction);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public virtual void PlayGetHitHeavyAnimation(out float animDuration, Vector3 direction = default)
|
|
|
|
|
|
{
|
|
|
|
|
|
PlayGetHitAnimation("GetHitHeavy", out animDuration, direction);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public virtual void PlayGetHitDisruptionAnimation(out float animDuration, Vector3 direction = default)
|
|
|
|
|
|
{
|
|
|
|
|
|
PlayGetHitAnimation("GetHitDisruption", out animDuration, direction);
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|