Files
Continentis/Assets/Scripts/MainGame/Commands/Cmd_PlayAnimation.cs

122 lines
4.9 KiB
C#
Raw Normal View History

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