Files
Cielonos/Assets/Scripts/MainGame/Characters/Player/Animation/PlayerAnimationSubcontroller.cs
2026-05-10 11:47:55 -04:00

182 lines
6.8 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 RootMotion.FinalIK;
using SLSUtilities.General;
using SLSUtilities.FunctionalAnimation;
using UnityEngine;
namespace Cielonos.MainGame.Characters
{
public partial class PlayerAnimationSubcontroller : AnimationSubcontrollerBase
{
public Player player => owner as Player;
public FunctionalAnimationSubmodule upperBodyFuncAnimSm;
public AnimatorOverrideController animatorOverride;
public FullBodyBipedIK fullBodyBipedIK;
public GrounderFBBIK grounderFBBIK;
private readonly Dictionary<string, AnimationClip> _pendingOverrides = new();
private int _overrideBatchDepth;
public override void Initialize()
{
base.Initialize();
upperBodyFuncAnimSm = new FunctionalAnimationSubmodule(this, "UpperBodyAction");
player.operationSc.OnDash += (inputDirection, length) =>
{
if (player.statusSm.HasStatus(StatusType.Stun))
{
return;
}
SetupDash(inputDirection, true, length);
};
player.operationSc.OnDodge += (length)=>
{
if (player.statusSm.HasStatus(StatusType.Stun))
{
return;
}
SetupDodge(length);
};
}
protected override void Update()
{
base.Update();
upperBodyFuncAnimSm?.UpdateTime();
player.inputSc.preinputSubmodule.Update(isDuringPreinput, isAtActionDisruption);
}
protected override void LateUpdate()
{
base.LateUpdate();
upperBodyFuncAnimSm?.UpdateEvents();
}
public override void RegisterDefaultFunctions()
{
base.RegisterDefaultFunctions();
registeredFunctions.Add("DashStart", anim => DashStart());
registeredFunctions.Add("DashEnd", anim => DashEnd());
registeredFunctions.Add("DodgeStart", anim => DodgeStart());
registeredFunctions.Add("DodgeEnd", anim => DodgeEnd());
}
/// <summary>
/// 当前是否处于 Override 批处理模式。
/// </summary>
public bool IsBatchingOverrides => _overrideBatchDepth > 0;
/// <summary>
/// 开启 AnimatorOverrideController 批处理模式。支持嵌套调用(引用计数)。
/// 批处理期间所有 clip 替换积压到缓冲区,直到最外层 FlushOverrideBatch() 一次性提交。
/// </summary>
public void BeginOverrideBatch()
{
_overrideBatchDepth++;
}
/// <summary>
/// 向批处理缓冲区追加一条 clip 替换。
/// 若未处于批处理模式,则立即写入 animatorOverride触发 Rebind
/// </summary>
public void SetOverride(string stateName, AnimationClip clip)
{
if (clip == null) return;
if (IsBatchingOverrides)
{
_pendingOverrides[stateName] = clip;
}
else
{
animatorOverride[stateName] = clip;
}
}
/// <summary>
/// 结束一层批处理。当所有嵌套层都结束后,通过 ApplyOverrides 一次性提交所有积压的 clip 替换(仅触发 1 次 Rebind
/// 提交后立即调用 animator.Update(0f) 强制求值,避免 Rebind 导致的单帧 T-Pose。
/// </summary>
public void FlushOverrideBatch()
{
if (_overrideBatchDepth > 0) _overrideBatchDepth--;
if (_overrideBatchDepth > 0) return;
if (_pendingOverrides.Count == 0) return;
var overrides = new List<KeyValuePair<AnimationClip, AnimationClip>>();
animatorOverride.GetOverrides(overrides);
for (int i = 0; i < overrides.Count; i++)
{
AnimationClip originalClip = overrides[i].Key;
if (originalClip != null && _pendingOverrides.TryGetValue(originalClip.name, out AnimationClip newClip))
{
overrides[i] = new KeyValuePair<AnimationClip, AnimationClip>(originalClip, newClip);
}
}
animatorOverride.ApplyOverrides(overrides);
_pendingOverrides.Clear();
// 强制 Animator 立即求值一次,防止 Rebind 后的单帧 T-Pose / 位置错位
animator.Update(0f);
}
}
public partial class PlayerAnimationSubcontroller
{
public bool isDuringPreinput;
public bool isAtActionDisruption;
protected override void UpdateIntervalInfo()
{
base.UpdateIntervalInfo();
isDuringPreinput = currentIntervals.Any(interval => interval.intervalType == IntervalType.Preinput);
isAtActionDisruption = lastFrameIntervals.SwitchOut(currentIntervals, (interval) => interval.intervalType == IntervalType.Preinput);
}
}
public partial class PlayerAnimationSubcontroller
{
/// <summary>
/// 计算冲刺时的摄像机倾斜角度
/// </summary>
/// <param name="dashDir">冲刺输入的平整化方向 (y=0, normalized)</param>
/// <param name="camFwd">摄像机的平整化前方 (y=0, normalized)</param>
/// <returns>Vector3(Pitch角度, 0, Roll角度)</returns>
public Vector3 CalculateDashAngles(Vector3 dashDir, Vector3 camFwd)
{
// 1. 确保输入向量是归一化的(以防万一)
Vector3 d = dashDir.normalized;
Vector3 f = camFwd.normalized;
// 2. 通过叉乘获取摄像机的水平右方向 (camRight)
// 在左手坐标系UnityUp x Forward = Right
Vector3 r = Vector3.Cross(Vector3.up, f);
// 3. 计算投影权重 (范围在 -1 到 1 之间)
// forwardWeight: 1表示完全同向-1表示完全反向
float forwardWeight = Vector3.Dot(d, f);
// sideWeight: 1表示向右冲-1表示向左冲
float sideWeight = Vector3.Dot(d, r);
// 4. 定义倾斜强度系数 (控制在 1.5度 左右)
const float tiltIntensity = 1.5f;
// 5. 计算最终角度
// x 轴旋转 (Pitch):正值向下倾斜(向前冲),负值向上倾斜(向后退)
float pitch = forwardWeight * tiltIntensity;
// z 轴旋转 (Dutch/Roll)
// 注意:向右冲时(sideWeight=1),通常相机向左倾斜(z为负值)更有动感
float roll = -sideWeight * tiltIntensity;
return new Vector3(pitch, 0, roll);
}
}
}