feat(scene-mgmt): 添加AnimatorKit动画管理器
This commit is contained in:
@@ -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 @@
|
||||
# AnimatorYarnCommand(Yarn 用法)
|
||||
|
||||
实现类:`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>>
|
||||
```
|
||||
Reference in New Issue
Block a user