243 lines
10 KiB
C#
243 lines
10 KiB
C#
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, 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, runtimeStartEvents);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 物理执行动作播放的核心实现。
|
||
/// </summary>
|
||
public virtual bool Play(FuncAnimData data, float speedMultiplier = 1f, float transitionDuration = 0.1f,
|
||
bool isNormalizedTransition = false, 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 = animInfo.overrideStartFrame / frameRate;
|
||
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();
|
||
}
|
||
}
|
||
} |