Files
Cielonos/Assets/Scripts/MainGame/Characters/Player/Animation/PlayerAnimationSubcontroller.cs

183 lines
6.8 KiB
C#
Raw Normal View History

2026-02-13 09:22:11 -05:00
using System;
2026-05-10 11:47:55 -04:00
using System.Collections.Generic;
2026-02-13 09:22:11 -05:00
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;
2026-05-23 08:27:50 -04:00
2026-02-13 09:22:11 -05:00
public AnimatorOverrideController animatorOverride;
public FullBodyBipedIK fullBodyBipedIK;
public GrounderFBBIK grounderFBBIK;
2026-05-10 11:47:55 -04:00
private readonly Dictionary<string, AnimationClip> _pendingOverrides = new();
private int _overrideBatchDepth;
2026-02-13 09:22:11 -05:00
public override void Initialize()
{
base.Initialize();
2026-04-12 02:11:15 -04:00
player.operationSc.OnDash += (inputDirection, length) =>
{
if (player.statusSm.HasStatus(StatusType.Stun))
{
return;
}
SetupDash(inputDirection, true, length);
2026-05-26 00:21:27 -04:00
//player.vfxData.SpawnVFX("PerfectDodgeLine", player, player.bodyPartsSc.head);
2026-04-12 02:11:15 -04:00
};
player.operationSc.OnDodge += (length)=>
{
if (player.statusSm.HasStatus(StatusType.Stun))
{
return;
}
SetupDodge(length);
2026-05-26 00:21:27 -04:00
//player.vfxData.SpawnVFX("PerfectDodgeLine", player, player.bodyPartsSc.head);
2026-04-12 02:11:15 -04:00
};
2026-02-13 09:22:11 -05:00
}
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());
}
2026-05-10 11:47:55 -04:00
/// <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);
}
2026-02-13 09:22:11 -05:00
}
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);
}
}
}