420 lines
13 KiB
C#
420 lines
13 KiB
C#
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>
|
||
/// 将 Animator 状态机恢复到当前 <see cref="RuntimeAnimatorController"/> 的默认入口状态(参数与层会按控制器重置),
|
||
/// 不释放 Addressable、不还原已替换的运行时控制器。用于在保留当前加载的控制器前提下清掉过渡与参数。
|
||
/// </summary>
|
||
public void ReturnToDefaultState()
|
||
{
|
||
if (!EnsureAnimator())
|
||
return;
|
||
|
||
CancelCompletionCoroutine();
|
||
_onCompleteCallback = null;
|
||
|
||
EnsurePlayingSpeedForNewClip();
|
||
|
||
animator.Rebind();
|
||
animator.Update(0f);
|
||
}
|
||
|
||
/// <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);
|
||
}
|
||
}
|
||
}
|