Files
Cielonos/Assets/Scripts/SLSUtilities/FunctionalAnimation/FuncAnimPlayer.cs
SoulliesOfficial 6d7ebc5825 Passion & UI
2026-06-12 17:11:39 -04:00

243 lines
10 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 UnityEngine;
namespace SLSUtilities.FunctionalAnimation
{
/// <summary>
/// 纯净的 FunctionalAnimation 物理播放器基类。
/// 负责管理注册数据集、时间推进、动画混合、事件分发以及无战斗业务耦合的通用区间查询。
/// </summary>
public class FuncAnimPlayer
{
private static readonly int ActionSpeed = Animator.StringToHash("ActionSpeed");
/// <summary>执行当前动画的实体对象(如 CharacterBase 或 NpcBase</summary>
public IFuncAnimExecutor Executor { get; }
/// <summary>实体关联的动画状态机组件</summary>
public Animator Animator { get; }
/// <summary>动作所播放的 Animator Layer 目标层级名称</summary>
public string animatorLayerName;
/// <summary>当前正在播放的动作运行时状态</summary>
public RuntimeFuncAnim currentRuntimeFuncAnim;
/// <summary>动作播放速度的临时缩放倍率</summary>
public float currentPlaySpeedMultiplier = 1f;
/// <summary>实体本地注册的动作数据集查找字典</summary>
public Dictionary<string, FuncAnimData> collection;
// ── 快捷只读时间/帧属性 ──
public FuncAnimData currentData => currentRuntimeFuncAnim?.funcAnimData;
public AnimationClip currentClip => currentData?.animationClip;
public float currentPlayTime => currentRuntimeFuncAnim?.currentPlayTime ?? 0f;
public float currentTotalPlayTime => currentRuntimeFuncAnim?.currentTotalPlayTime ?? 0f;
/// <summary>当前动画播放的归一化进度 (0 到 1)</summary>
public float currentNormalizedPlayTime => (currentClip != null && currentClip.length > 0f)
? Mathf.Min(1f, currentPlayTime / currentClip.length)
: 0f;
/// <summary>当前动画播放对应的帧数</summary>
public float currentFrame => (currentRuntimeFuncAnim != null && currentClip != null)
? (currentRuntimeFuncAnim.currentPlayTime * currentClip.frameRate)
: 0f;
/// <summary>经播放速度缩放后的总动画剪辑时长</summary>
public float currentScaledClipLength => (currentClip != null)
? (currentClip.length / currentPlaySpeedMultiplier)
: 0f;
/// <summary>当前是否有动画正在播放</summary>
public bool IsPlaying => currentRuntimeFuncAnim != null;
public FuncAnimPlayer(IFuncAnimExecutor executor, string layerName = "FullBodyAction")
{
Executor = executor;
Animator = executor.Animator;
animatorLayerName = layerName;
collection = new Dictionary<string, FuncAnimData>();
}
/// <summary>
/// 注册动作资源到本地字典中
/// </summary>
public virtual void Add(FuncAnimData animation)
{
if (animation == null) return;
collection[animation.animInfo.animationName] = animation;
}
/// <summary>
/// 从动作库中移除指定动画
/// </summary>
public virtual void Remove(string animationName)
{
if (collection.ContainsKey(animationName))
{
collection.Remove(animationName);
}
}
/// <summary>
/// 重新覆盖注册某个动画数据
/// </summary>
public virtual void Reset(FuncAnimData animation)
{
if (animation == null) return;
Remove(animation.animInfo.animationName);
Add(animation);
}
/// <summary>
/// 通过动画名称查找并播放动作。
/// </summary>
public virtual bool Play(string animationName, float speedMultiplier = 1f, float transitionDuration = 0.1f,
bool isNormalizedTransition = false, float normalizedStartTime = -1f, List<FuncAnimPayloadBase> runtimeStartEvents = null)
{
if (!collection.TryGetValue(animationName, out FuncAnimData funcAnimData))
{
// 使用 GetType().Name 动态输出子类名称,避免日志混淆
Debug.LogWarning($"[{GetType().Name}] 动作库中未找到名为 '{animationName}' 的动作数据。");
return false;
}
return Play(funcAnimData, speedMultiplier, transitionDuration, isNormalizedTransition, normalizedStartTime, runtimeStartEvents);
}
/// <summary>
/// 物理执行动作播放的核心实现。
/// </summary>
public virtual bool Play(FuncAnimData data, float speedMultiplier = 1f, float transitionDuration = 0.1f,
bool isNormalizedTransition = false, float normalizedStartTime = -1f, List<FuncAnimPayloadBase> runtimeStartEvents = null)
{
if (data == null || Animator == null) return false;
// 1. 打断并退场当前正在播放的动作,触发其打断事件与结束事件
if (currentRuntimeFuncAnim != null && !currentRuntimeFuncAnim.isDisrupted)
{
currentRuntimeFuncAnim.isDisrupted = true;
currentRuntimeFuncAnim.InvokeDisruptionEvents();
currentRuntimeFuncAnim.InvokeEndEvents();
}
float oldClipLength = (currentRuntimeFuncAnim != null && currentClip != null) ? currentClip.length : 1f;
// 2. 构建全新的运行时动作实例并装载动态事件
currentRuntimeFuncAnim = new RuntimeFuncAnim(data, Executor);
currentRuntimeFuncAnim.ClearRuntimeEvents();
runtimeStartEvents?.ForEach(payload => currentRuntimeFuncAnim.AddStartEvent(payload));
FuncAnimInfo animInfo = currentData.animInfo;
currentPlaySpeedMultiplier = speedMultiplier;
// 3. 计算起始偏移并执行 Animator 融合过渡
float clipLength = currentClip != null ? currentClip.length : 1f;
float frameRate = currentClip != null ? currentClip.frameRate : 30f;
currentRuntimeFuncAnim.currentPlayTime = normalizedStartTime < 0f ? animInfo.overrideStartFrame / frameRate : normalizedStartTime;
float normalizedTimeOffset = clipLength > 0f ? currentPlayTime / clipLength : 0f;
float normalizedTransitionDuration = isNormalizedTransition ? transitionDuration : transitionDuration / oldClipLength;
Animator.CrossFade(animInfo.stateName, normalizedTransitionDuration, Animator.GetLayerIndex(animatorLayerName), normalizedTimeOffset);
// 4. 应用动作播放速度参数
try
{
float actionSpeed = animInfo.overridePlaySpeed * currentPlaySpeedMultiplier;
Animator.SetFloat(ActionSpeed, actionSpeed);
}
catch { /* 忽略状态机中未配置 ActionSpeed 变量的情况 */ }
// 5. 触发启动事件
currentRuntimeFuncAnim.InvokeStartEvents();
currentRuntimeFuncAnim.isDisrupted = false;
return true;
}
/// <summary>
/// 物理停止当前动画:触发退场事件并将状态机融合至 Empty 空白状态。
/// </summary>
public virtual bool Stop(float transitionDuration = 0.1f)
{
if (currentRuntimeFuncAnim == null) return true;
if (!currentRuntimeFuncAnim.isDisrupted)
{
currentRuntimeFuncAnim.isDisrupted = true;
currentRuntimeFuncAnim.InvokeDisruptionEvents();
currentRuntimeFuncAnim.InvokeEndEvents();
}
float oldClipLength = currentClip != null ? currentClip.length : 1f;
float normalizedTransitionDuration = transitionDuration / oldClipLength;
Animator.CrossFade("Empty", normalizedTransitionDuration, Animator.GetLayerIndex(animatorLayerName));
currentRuntimeFuncAnim = null;
return true;
}
/// <summary>
/// 动作生命周期推进(时钟更新与循环/自然退场监测)。
/// </summary>
public virtual void Update(float deltaTime)
{
if (currentRuntimeFuncAnim == null)
{
currentPlaySpeedMultiplier = 1f;
return;
}
// 推进播放时间
float step = deltaTime * currentData.animInfo.overridePlaySpeed * currentPlaySpeedMultiplier;
currentRuntimeFuncAnim.currentPlayTime += step;
currentRuntimeFuncAnim.currentTotalPlayTime += step;
float clipLength = currentClip != null ? currentClip.length : 0f;
// 监测单次播放是否到头
if (currentPlayTime >= clipLength)
{
UpdateEvents();
if (currentClip != null && currentClip.isLooping)
{
// 循环动作:取模复位并重新触发 StartEvent
currentRuntimeFuncAnim.currentPlayTime = clipLength > 0f ? currentRuntimeFuncAnim.currentPlayTime % clipLength : 0f;
currentRuntimeFuncAnim.dataAnimEventIndex = 0;
currentRuntimeFuncAnim.runtimeAnimEventIndex = 0;
currentRuntimeFuncAnim.ResetUpdateUntilEventsStatus();
currentRuntimeFuncAnim.InvokeStartEvents();
}
else
{
// 非循环动作:执行打断与结束退场事件,并注销运行时
if (!currentRuntimeFuncAnim.isDisrupted)
{
currentRuntimeFuncAnim.isDisrupted = true;
currentRuntimeFuncAnim.InvokeDisruptionEvents();
currentRuntimeFuncAnim.InvokeEndEvents();
}
currentRuntimeFuncAnim = null;
}
}
}
/// <summary>
/// 物理帧更新:驱动时间轴上已注册的时刻事件及持续事件。
/// </summary>
public virtual void UpdateEvents()
{
RuntimeFuncAnim rt = currentRuntimeFuncAnim;
if (rt == null) return;
currentRuntimeFuncAnim.UpdateAnimEvent();
currentRuntimeFuncAnim.InvokeUpdateEvents();
currentRuntimeFuncAnim.InvokeUpdateUntilEvents();
}
}
}