前言
前置知识:设置参数后,不会立即切换对应状态,偶尔等一帧也不会进入
AnimatorStateInfo是结构体!值类型,要不断重复获取才是最新的
AnimatorStateInfo重要属性
normalizedTime
:动画播放进度,播放完则是1,每次循环+1,需要注意不循环的动画可能只到9.x甚至更低就切换了shortNameHash
:状态名字 转换成的hash值length
:动画时长,单位秒IsName
: 将字符串转为hash进行判断- speed:状态上配置的时间缩放,混合树的话就是本身的时间缩放和动作域无关,不受animator影响
代码
三种等待方式
- 根据namehash判断:准确但是如果没有进入目标状态会死循环不够安全
- 基于动画播放进度和过渡:normalizedTime的值跳跃性大,不够准确
- 基于动画时长:灵活,但是无法判断状态的开始,也无法处理钝帧等中途缩放时间的情况
public static class AnimationTool
{
#region 基于指定hash
// 准确来说,不是基于动画而是基于状态
public static async UniTask AwaitStateStartByName(Animator animator, Action callback = null, int nameHash = 0, int layer = 0, CancellationToken token = default)
{
while (animator.GetCurrentAnimatorStateInfo(layer).shortNameHash != nameHash)
{
// 在动画未结束或者在过渡中时继续等待
await UniTask.Yield(token);
}
callback?.Invoke();
}
public static async UniTask AwaitStateStartByName(Animator animator, Action callback = null, string name = "", int layer = 0, CancellationToken token = default)
{
var nameHash = Animator.StringToHash(name);
await AwaitStateStartByName(animator, callback, nameHash, layer);
}
public static async UniTask AwaitStateEndByName(Animator animator, Action callback = null, int nameHash = 0, int layer = 0, CancellationToken token = default)
{
var animInfo = animator.GetCurrentAnimatorStateInfo(layer);
while (animator.GetCurrentAnimatorStateInfo(layer).shortNameHash == nameHash)
{
Debug.Log(animInfo.normalizedTime + " " + animInfo.shortNameHash);
await UniTask.Yield(token);
}
Debug.Log(animInfo.normalizedTime + " " + animInfo.shortNameHash);
callback?.Invoke();
}
public static async UniTask AwaitStateEndByName(Animator animator, Action callback = null, string name = "", int layer = 0, CancellationToken token = default)
{
var nameHash = Animator.StringToHash(name);
await AwaitStateStartByName(animator, callback, nameHash, layer);
}
/// <summary>
/// 组合用法 等待当前真实动画播放完毕
/// </summary>
/// <param name="animator"></param>
/// <param name="callback"></param>
/// <param name="name"></param>
/// <param name="layer"></param>
/// <returns></returns>
public static async UniTask AwaitStateStartAndEndByName(Animator animator, Action callback = null, string name = "", int layer = 0, CancellationToken token = default)
{
var animInfo = animator.GetCurrentAnimatorStateInfo(layer);
var nameHash = Animator.StringToHash(name);
if (animInfo.shortNameHash != nameHash)
{
await AwaitStateStartByName(animator, null, name, layer, token);
}
while (animator.GetCurrentAnimatorStateInfo(layer).shortNameHash == nameHash)
{
// 在动画未结束或者在过渡中时继续等待
await UniTask.Yield(token);
}
callback?.Invoke();
}
#endregion
#region 基于当前hash
/// <summary>
/// 组合用法 等待当前真实动画播放完毕
/// </summary>
/// <param name="animator"></param>
/// <param name="callback"></param>
public static void AwaitCurrentStateEnd(Animator animator, Action callback, int layer = 0)
{
AwaitCurrentState(animator, () =>
{
AwaitAnimEnd(animator, callback, layer);
}, layer);
}
/// <summary>
/// 等待动画 严格等待 注意无法处理自循环的动画!只有一个动画用delay版本
/// </summary>
/// <param name="animator"></param>
/// <returns></returns>
public static void AwaitCurrentState(Animator animator, Action callback, int layer = 0)
{
AwaitCurrentStateAsync(animator, callback, layer).Forget();
}
public static async UniTask AwaitCurrentStateAsync(Animator animator, Action callback = null, int layer = 0)
{
var animInfo = animator.GetCurrentAnimatorStateInfo(layer);
var nameHash = animInfo.fullPathHash;
// 等待直到当前动画发生改变
await UniTask.WaitUntil(() =>
{
var info = animator.GetCurrentAnimatorStateInfo(layer);
return nameHash != info.fullPathHash;
});
callback?.Invoke();
}
public static void AwaitNextState(Animator animator, Action callback, int layer = 0)
{
AwaitNextStateAsync(animator, callback, layer).Forget();
}
public static async UniTask AwaitNextStateAsync(Animator animator, Action callback, int layer = 0)
{
await AwaitCurrentStateAsync(animator, null, layer);
await AwaitCurrentStateAsync(animator, callback, layer);
}
#endregion
#region 基于播放进度
/// <summary>
/// 等待动画播放进度结束 不包含过渡!
/// </summary>
/// <param name="animator"></param>
/// <param name="callback"></param>
/// <param name="layer"></param>
public static void AwaitAnimEnd(Animator animator, Action callback, int layer = 0)
{
AwaitAnimEndAsync(animator, callback, layer).Forget();
}
/// <summary>
/// 提前等待动画播放
/// </summary>
/// <param name="animator"></param>
/// <param name="callback"></param>
/// <param name="layer"></param>
/// <param name="time">提前的进度百分比</param>
public static void PreAwaitAnimEnd(Animator animator, Action callback, float time, int layer = 0)
{
AwaitAnimEndAsync(animator, callback, time, layer).Forget();
}
public static async UniTask AwaitAnimEndAsync(Animator animator, Action callback = null, float time = 0, int layer = 0, CancellationToken token = default)
{
while (animator.GetCurrentAnimatorStateInfo(layer).normalizedTime < (0.95f - time) || !animator.IsInTransition(layer))
{
// 在动画未结束或者在过渡中时继续等待
await UniTask.Yield(token);
}
callback?.Invoke();
}
#endregion
#region 基于动画时长
/// <summary>
/// 基于动画时长 更加安全 无法处理钝帧等中途缩放时间的情况
/// </summary>
/// <param name="animator"></param>
/// <param name="callback"></param>
/// <param name="layer"></param>
/// <param name="token"></param>
/// <param name="time">提前时间,单位秒</param>
/// <returns></returns>
public static async UniTask AwaitCurrentAnimEndByLength(Animator animator, Action callback = null, float time = 0, int layer = 0, CancellationToken token = default)
{
var animInfo = animator.GetCurrentAnimatorStateInfo(layer);
// 获取真实动画时长 animator.speed 不影响 animInfo.speed
var length = (animInfo.speed != 0 && animator.speed != 0) ?
(animInfo.length / (animInfo.speed * animator.speed)) - time : 0;
await UniTask.Delay((int)(length * 1000), cancellationToken: token);
callback?.Invoke();
}
#endregion
}