Files
Continentis/Assets/Scripts/MainGame/Commands/Cmd_PlayAnimation.cs
SoulliesOfficial ac98ec3aef 更新
2026-04-17 12:01:50 -04:00

122 lines
4.9 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 Continentis.MainGame.Character;
using Cysharp.Threading.Tasks;
using SLSUtilities.General;
using UnityEngine;
namespace Continentis.MainGame.Commands
{
/// <summary>
/// 播放角色动画的命令。
/// 优先使用 ICharacterAnimator 接口驱动,同时保留帧精确事件轮询能力。
/// 当角色使用 FrameAnimatorAnimatorPlus2D时支持按帧/归一化时间触发 Action。
/// </summary>
public class Cmd_PlayAnimation : CommandBase
{
private readonly CombatCharacterViewBase characterView;
private readonly ICharacterAnimator characterAnimator;
private readonly bool waitForFinish;
private readonly float overrideDuration;
private readonly string animationName;
private AnimationClip clip;
private float ClipLength => clip != null ? clip.length : DefaultAnimationDuration;
private readonly Dictionary<float, Action> animationActions = new Dictionary<float, Action>();
private const float DefaultAnimationDuration = 0.5f;
public Cmd_PlayAnimation(CombatCharacterViewBase characterView, string animationName,
bool waitForFinish = true, float overrideDuration = -1f, int layer = 0)
{
this.characterView = characterView;
this.characterAnimator = characterView.CharacterAnimator;
this.animationName = animationName;
this.waitForFinish = waitForFinish;
this.overrideDuration = overrideDuration;
// 从 CharacterData 获取 AnimationClip 引用(用于计算时长和帧事件)
characterView.character.data.animations.TryGetValue(animationName, out clip);
}
/// <summary>在动画的指定归一化时间点0~1执行 Action。</summary>
public Cmd_PlayAnimation AddAction(float normalizedTime, Action action)
{
animationActions[normalizedTime] = action;
return this;
}
/// <summary>在动画的指定帧执行 Action。</summary>
public Cmd_PlayAnimation AddAction(int frame, Action action)
{
if (clip == null) return this;
float normalizedTime = frame / (clip.frameRate * clip.length);
return AddAction(normalizedTime, action);
}
/// <summary>在动画的指定归一化时间点执行带强类型参数的 Action参数从 selfContext 读取。</summary>
public Cmd_PlayAnimation AddAction<T>(float normalizedTime, string selfContextKey, Action<T> action)
{
T param = selfContext.Get<T>(selfContextKey);
animationActions[normalizedTime] = () => action(param);
return this;
}
/// <summary>在动画的指定帧执行带强类型参数的 Action参数从 selfContext 读取。</summary>
public Cmd_PlayAnimation AddAction<T>(int frame, string selfContextKey, Action<T> action)
{
if (clip == null) return this;
float normalizedTime = frame / (clip.frameRate * clip.length);
return AddAction(normalizedTime, selfContextKey, action);
}
protected override async UniTask ExecuteAsync(CommandContext outerContext)
{
if (characterAnimator == null || string.IsNullOrEmpty(animationName))
{
Debug.LogWarning("[Cmd_PlayAnimation] CharacterAnimator 或动画名称为空。");
return;
}
// 通过 ICharacterAnimator 播放
characterAnimator.PlayAction(animationName);
// 帧轮询动画事件fire-and-forget不阻塞命令流
if (animationActions.Count > 0)
PollAnimationActionsAsync().Forget();
if (waitForFinish)
{
float duration = overrideDuration >= 0f ? overrideDuration : ClipLength;
await UniTask.Delay(TimeSpan.FromSeconds(duration));
}
}
/// <summary>
/// 基于经过时间轮询帧事件,兼容所有动画驱动器。
/// </summary>
private async UniTaskVoid PollAnimationActionsAsync()
{
float elapsed = 0f;
float totalDuration = ClipLength;
var pending = new Dictionary<float, Action>(animationActions);
while (elapsed < totalDuration && pending.Count > 0)
{
await UniTask.Yield(PlayerLoopTiming.Update);
elapsed += Time.deltaTime;
float normalizedTime = Mathf.Clamp01(elapsed / totalDuration);
foreach (float key in pending.Keys.ToList())
{
if (normalizedTime >= key)
{
pending[key]?.Invoke();
pending.Remove(key);
}
}
}
}
}
}