feat(scene-mgmt): 添加AnimatorKit动画管理器

This commit is contained in:
2026-04-10 13:24:04 +08:00
parent bc8bf2c183
commit 75c71b394d
8 changed files with 668 additions and 0 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7a05366bfd7167e4b9ec409cc1ac8a55
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,161 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using AibisDream.Kit;
namespace AibisDream
{
public class AnimatorCenter : SingletonBase<AnimatorCenter>
{
private readonly Dictionary<string, AnimatorHandler> _handlers = new();
private AnimatorCenter() { }
public void RegisterHandler(AnimatorHandler handler)
{
_handlers.Add(handler.animatorName, handler);
}
public void UnregisterHandler(AnimatorHandler handler)
{
_handlers.Remove(handler.animatorName);
}
private bool TryGetHandler(string animatorName, out AnimatorHandler handler)
{
if (_handlers.TryGetValue(animatorName, out handler))
return true;
Debug.LogError($"AnimatorCenter: 未找到名为 \"{animatorName}\" 的 AnimatorHandler。");
handler = null;
return false;
}
public void PlayAnimation(string animatorName, string stateName, int layer = 0, Action onComplete = null)
{
if (!TryGetHandler(animatorName, out AnimatorHandler animatorHandler))
return;
animatorHandler.SetAnimatorObjectActive(true);
animatorHandler.Play(stateName, layer, onComplete);
}
public IEnumerator PlayAnimationAsync(string animatorName, string stateName, int layer = 0)
{
if (!TryGetHandler(animatorName, out AnimatorHandler animatorHandler))
yield break;
animatorHandler.SetAnimatorObjectActive(true);
yield return animatorHandler.PlayAsync(stateName, layer);
}
public void PlayAnimationFromAddressable(string animatorName, string addressableKey, string stateName,
int layer = 0, Action onComplete = null)
{
if (!TryGetHandler(animatorName, out AnimatorHandler animatorHandler))
return;
animatorHandler.SetAnimatorObjectActive(true);
animatorHandler.PlayFromAddressable(addressableKey, stateName, layer, onComplete);
}
public IEnumerator PlayAnimationFromAddressableAsync(string animatorName, string addressableKey,
string stateName, int layer = 0)
{
if (!TryGetHandler(animatorName, out AnimatorHandler animatorHandler))
yield break;
animatorHandler.SetAnimatorObjectActive(true);
yield return animatorHandler.PlayFromAddressableAsync(addressableKey, stateName, layer);
}
public void StopAnimation(string animatorName)
{
if (!TryGetHandler(animatorName, out AnimatorHandler animatorHandler))
return;
animatorHandler.Stop();
}
public void PauseAnimation(string animatorName)
{
if (!TryGetHandler(animatorName, out AnimatorHandler animatorHandler))
return;
animatorHandler.Pause();
}
public void ResumeAnimation(string animatorName)
{
if (!TryGetHandler(animatorName, out AnimatorHandler animatorHandler))
return;
animatorHandler.Resume();
}
public void ResetAnimation(string animatorName)
{
if (!TryGetHandler(animatorName, out AnimatorHandler animatorHandler))
return;
animatorHandler.Reset();
}
public void HideAnimation(string animatorName)
{
if (!TryGetHandler(animatorName, out AnimatorHandler animatorHandler))
return;
animatorHandler.SetAnimatorObjectActive(false);
}
public float GetPlaybackTime(string animatorName, int layer = 0)
{
return TryGetHandler(animatorName, out AnimatorHandler animatorHandler)
? animatorHandler.GetPlaybackTime(layer)
: 0f;
}
public float GetDuration(string animatorName, int layer = 0)
{
return TryGetHandler(animatorName, out AnimatorHandler animatorHandler)
? animatorHandler.GetDuration(layer)
: 0f;
}
public void SetPlaybackSpeed(string animatorName, float speed)
{
if (!TryGetHandler(animatorName, out AnimatorHandler animatorHandler))
return;
animatorHandler.SetPlaybackSpeed(speed);
}
public void SeekToTime(string animatorName, double timeSeconds, int layer = 0)
{
if (!TryGetHandler(animatorName, out AnimatorHandler animatorHandler))
return;
animatorHandler.SeekToTime(timeSeconds, layer);
}
public void PauseAll()
{
foreach (AnimatorHandler registeredHandler in _handlers.Values)
registeredHandler.Pause();
}
public void StopAll()
{
foreach (AnimatorHandler registeredHandler in _handlers.Values)
registeredHandler.Stop();
}
public void ResetAll()
{
foreach (AnimatorHandler registeredHandler in _handlers.Values)
registeredHandler.Reset();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2b1fe1047b45a654c926318a50a38357
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,401 @@
using System;
using System.Collections;
using AibisDream.Framework;
using UnityEngine;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace AibisDream
{
public class AnimatorHandler : MonoBehaviour
{
private const string AnimationPrefix = "Animation/";
public string animatorName;
[SerializeField] private Animator animator;
[Tooltip("若当前状态为 Loop:勾选则播完一整轮后触发完成回调;不勾选则循环状态不会结束(异步会一直等待)")]
[SerializeField]
private bool completeAfterOneCycleWhenStateLoops = true;
private float _speedBeforePause = 1f;
private AsyncOperationHandle<RuntimeAnimatorController> _addressableHandle;
private RuntimeAnimatorController _savedRuntimeAnimatorController;
private Coroutine _playCompletionCoroutine;
private Action _onCompleteCallback;
#if UNITY_EDITOR
private void OnValidate()
{
ValidateSerializedAnimator();
}
#endif
private void Start()
{
AnimatorCenter.Instance.RegisterHandler(this);
}
private void OnDestroy()
{
AnimatorCenter.Instance.UnregisterHandler(this);
CancelCompletionCoroutine();
if (animator != null)
animator.speed = _speedBeforePause > 0f ? _speedBeforePause : 1f;
ReleaseAddressableHandle();
}
private void ValidateSerializedAnimator()
{
if (animator != null && animator.transform == transform)
animator = null;
}
private bool EnsureAnimator()
{
if (animator != null)
return true;
Debug.LogError($"AnimatorHandler {animatorName}: 未在子物体上找到 Animator。", this);
return false;
}
private void CancelCompletionCoroutine()
{
if (_playCompletionCoroutine == null)
return;
StopCoroutine(_playCompletionCoroutine);
_playCompletionCoroutine = null;
}
private void ReleaseAddressableHandle()
{
if (!_addressableHandle.IsValid())
return;
if (animator != null && animator.runtimeAnimatorController == _addressableHandle.Result)
animator.runtimeAnimatorController = _savedRuntimeAnimatorController;
ResourceKit.Release(_addressableHandle);
_addressableHandle = default;
_savedRuntimeAnimatorController = null;
}
private void EnsurePlayingSpeedForNewClip()
{
if (animator != null && animator.speed < 0.001f)
animator.speed = _speedBeforePause > 0f ? _speedBeforePause : 1f;
}
/// <summary>
/// 对挂载 <see cref="Animator"/> 的 <see cref="GameObject"/> 调用 <see cref="GameObject.SetActive"/>
/// 不是修改 <see cref="Animator.enabled"/>。Handler 根物体可保持激活以便协程与注册。
/// </summary>
public void SetAnimatorObjectActive(bool active)
{
if (animator == null)
return;
animator.gameObject.SetActive(active);
}
private void ActivateAnimatorGameObject()
{
SetAnimatorObjectActive(true);
}
/// <summary>
/// 使用当前 Animator 上已绑定的控制器播放指定状态。
/// </summary>
public void Play(string stateName, int layer = 0, Action onComplete = null)
{
if (!EnsureAnimator())
return;
ActivateAnimatorGameObject();
CancelCompletionCoroutine();
ReleaseAddressableHandle();
_onCompleteCallback = onComplete;
EnsurePlayingSpeedForNewClip();
animator.Play(stateName, layer, 0f);
animator.Update(0f);
if (onComplete != null)
_playCompletionCoroutine = StartCoroutine(WaitForStatePlaybackEnd(layer, stateName, true));
}
public IEnumerator PlayAsync(string stateName, int layer = 0)
{
if (!EnsureAnimator())
yield break;
ActivateAnimatorGameObject();
CancelCompletionCoroutine();
ReleaseAddressableHandle();
EnsurePlayingSpeedForNewClip();
animator.Play(stateName, layer, 0f);
animator.Update(0f);
yield return WaitForStatePlaybackEnd(layer, stateName, false);
}
private void OnPlaybackComplete()
{
_playCompletionCoroutine = null;
var completionCallback = _onCompleteCallback;
_onCompleteCallback = null;
ReleaseAddressableHandle();
completionCallback?.Invoke();
}
/// <param name="invokeCompletionCallback">为 true 时在结束时调用 <see cref="OnPlaybackComplete"/>(含 Addressable 释放与 Action);为 false 时仅做 Addressable 释放(用于无回调异步)</param>
private IEnumerator WaitForStatePlaybackEnd(int layer, string stateName, bool invokeCompletionCallback)
{
var timeout = 10f;
while (timeout > 0f)
{
var currentStateInfo = animator.GetCurrentAnimatorStateInfo(layer);
if (currentStateInfo.IsName(stateName) && !animator.IsInTransition(layer))
break;
timeout -= Time.deltaTime;
yield return null;
}
while (true)
{
var currentStateInfo = animator.GetCurrentAnimatorStateInfo(layer);
if (!currentStateInfo.IsName(stateName))
{
if (invokeCompletionCallback)
OnPlaybackComplete();
else
ReleaseAddressableHandle();
yield break;
}
if (animator.IsInTransition(layer))
{
yield return null;
continue;
}
if (currentStateInfo.loop)
{
if (completeAfterOneCycleWhenStateLoops && currentStateInfo.normalizedTime >= 1f)
{
if (invokeCompletionCallback)
OnPlaybackComplete();
else
ReleaseAddressableHandle();
yield break;
}
}
else if (currentStateInfo.normalizedTime >= 1f)
{
if (invokeCompletionCallback)
OnPlaybackComplete();
else
ReleaseAddressableHandle();
yield break;
}
yield return null;
}
}
/// <summary>
/// 从 Addressables 加载 RuntimeAnimatorController(键为 Animation/ + addressableKey)并播放状态。
/// </summary>
public void PlayFromAddressable(string addressableKey, string stateName, int layer = 0, Action onComplete = null)
{
StartCoroutine(PlayFromAddressableCoroutine(addressableKey, stateName, layer, onComplete));
}
public IEnumerator PlayFromAddressableAsync(string addressableKey, string stateName, int layer = 0)
{
yield return PlayFromAddressableCoroutine(addressableKey, stateName, layer, null);
}
private IEnumerator PlayFromAddressableCoroutine(string addressableKey, string stateName, int layer,
Action onComplete)
{
if (!EnsureAnimator())
yield break;
ActivateAnimatorGameObject();
if (string.IsNullOrEmpty(addressableKey))
{
Debug.LogError($"AnimatorHandler {animatorName}: addressableKey 为空。", this);
yield break;
}
ReleaseAddressableHandle();
var loadKey = AnimationPrefix + addressableKey;
RuntimeAnimatorController loaded = null;
AsyncOperationHandle<RuntimeAnimatorController> loadedHandle = default;
var loadDone = false;
ResourceKit.LoadAssetAsyncWithHandle<RuntimeAnimatorController>(loadKey, loadHandle =>
{
loadedHandle = loadHandle;
if (loadHandle.Status == AsyncOperationStatus.Succeeded)
loaded = loadHandle.Result;
loadDone = true;
});
yield return new WaitUntil(() => loadDone);
if (loaded == null)
{
Debug.LogError($"AnimatorHandler {animatorName}: 加载失败 '{loadKey}'", this);
yield break;
}
CancelCompletionCoroutine();
_savedRuntimeAnimatorController = animator.runtimeAnimatorController;
animator.runtimeAnimatorController = loaded;
animator.Rebind();
animator.Update(0f);
_addressableHandle = loadedHandle;
EnsurePlayingSpeedForNewClip();
_onCompleteCallback = onComplete;
animator.Play(stateName, layer, 0f);
animator.Update(0f);
if (onComplete != null)
{
_playCompletionCoroutine = StartCoroutine(WaitForStatePlaybackEnd(layer, stateName, true));
while (_playCompletionCoroutine != null)
yield return null;
}
else
{
yield return WaitForStatePlaybackEnd(layer, stateName, false);
}
}
public void Stop()
{
if (!EnsureAnimator())
return;
CancelCompletionCoroutine();
_onCompleteCallback = null;
animator.speed = 0f;
ReleaseAddressableHandle();
}
public void Pause()
{
if (!EnsureAnimator())
return;
if (animator.speed > 0f)
{
_speedBeforePause = animator.speed;
animator.speed = 0f;
}
}
public void Resume()
{
if (!EnsureAnimator())
return;
if (animator.speed == 0f)
animator.speed = _speedBeforePause > 0f ? _speedBeforePause : 1f;
}
public void Reset()
{
if (!EnsureAnimator())
return;
CancelCompletionCoroutine();
_onCompleteCallback = null;
animator.speed = 1f;
animator.Rebind();
animator.Update(0f);
ReleaseAddressableHandle();
}
/// <summary>
/// 当前层当前状态下,已播放时长(秒)。过渡或非目标状态时语义以当前 StateInfo 为准。
/// </summary>
public float GetPlaybackTime(int layer = 0)
{
if (animator == null)
return 0f;
var currentStateInfo = animator.GetCurrentAnimatorStateInfo(layer);
return currentStateInfo.normalizedTime * currentStateInfo.length;
}
/// <summary>
/// 当前层当前状态片段长度(秒)。
/// </summary>
public float GetDuration(int layer = 0)
{
if (animator == null)
return 0f;
return animator.GetCurrentAnimatorStateInfo(layer).length;
}
public void SetPlaybackSpeed(float speed)
{
if (!EnsureAnimator())
return;
animator.speed = speed;
if (speed > 0f)
_speedBeforePause = speed;
}
/// <summary>
/// 在当前层下,将播放位置 seek 到指定时间(秒),相对当前状态片段长度归一化后播放。
/// </summary>
public void SeekToTime(double timeSeconds, int layer = 0)
{
if (!EnsureAnimator())
return;
var currentStateInfo = animator.GetCurrentAnimatorStateInfo(layer);
var stateLengthSeconds = currentStateInfo.length;
if (stateLengthSeconds <= 0.0001f)
return;
var normalizedPlaybackTime = Mathf.Clamp01((float)(timeSeconds / stateLengthSeconds));
animator.Play(currentStateInfo.shortNameHash, layer, normalizedPlaybackTime);
animator.Update(0f);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: babec43795065b244ac4cef4af7fa225
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,34 @@
using System.Collections;
using Yarn.Unity;
namespace AibisDream
{
// Yarn 命令说明见同目录 AnimatorYarnCommand.md
public static class AnimatorYarnCommand
{
[YarnCommand("play_animation")]
public static IEnumerator PlayAnimation(string animatorName, string stateName)
{
yield return AnimatorCenter.Instance.PlayAnimationAsync(animatorName, stateName);
}
[YarnCommand("play_animation_addressable")]
public static IEnumerator PlayAnimationAddressable(string animatorName, string addressableKey, string stateName)
{
yield return AnimatorCenter.Instance.PlayAnimationFromAddressableAsync(animatorName, addressableKey,
stateName);
}
[YarnCommand("reset_animation")]
public static void ResetAnimation(string animatorName)
{
AnimatorCenter.Instance.ResetAnimation(animatorName);
}
[YarnCommand("hide_animation")]
public static void HideAnimation(string animatorName)
{
AnimatorCenter.Instance.HideAnimation(animatorName);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4c6df6618b817054092bb3443c7bcc7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,31 @@
# AnimatorYarnCommandYarn 用法)
实现类:`AnimatorYarnCommand.cs`。命令经 `AnimatorCenter` 调用场景中已注册的 `AnimatorHandler`
## 参数说明
- **animatorName**:与场景里 `AnimatorHandler.animatorName` 一致。
- **stateName**Animator Controller 里的状态名。
- **addressableKey**Addressables 资源键中 **`Animation/` 之后** 的部分(与 `AnimatorHandler` 加载逻辑一致)。
`.yarn` 里参数顺序与 C# 方法一致;含空格或中文时按项目 Yarn 版本加双引号。
## 命令一览
| 命令 | 是否等待结束 |
|------|----------------|
| `<<play_animation …>>` | 会。等到 `PlayAnimationAsync` 完成(非 Loop 播完或 Loop 一整轮等,见 Handler 上配置)。 |
| `<<play_animation_addressable …>>` | 会。加载控制器并播完本次流程。 |
| `<<reset_animation …>>` | 否。立即 `ResetAnimation`。 |
| `<<hide_animation …>>` | 否。关掉**挂 Animator 的那层 GameObject**Handler 仍可留在场景里。 |
## 示例
把名称换成你场景里的注册名与状态名:
```yarn
<<play_animation MyCutscene "手接树叶特写">>
<<play_animation_addressable MyCutscene "Test/手接树叶" "手接树叶特写">>
<<reset_animation MyCutscene>>
<<hide_animation MyCutscene>>
```