在用Unity自带的Animation组件的过程中,发现很多常见的基本功能并找不到,很大程度上影响了开发者正常使用,下面写一些扩展方法来进行补充:
1.得到当前Animation正在播放的动画
吐槽:为啥只有Animator可以直接取得,Animation却不行不愉快
1 public static string GetCurrentPlayingAnimationName(this Animation animation) 2 { 3 foreach (AnimationState state in animation) 4 { 5 if (animation.IsPlaying(state.name)) 6 return state.name; 7 } 8 return null; 9 }
2.得到一段AnimationClip的帧数
吐槽:这么基础的功能还要自己计算哎
1 public static float GetAnimationClipTotalFrame(this AnimationClip clip) 2 { 3 return clip.length / (1 / clip.frameRate); 4 }
这里也可以转为整型来使用
3.按一定的速率来执行播放指定动画
1 public static void PlayAnimationWithSpeed(this Animation animation, string animationName, float speed) 2 { 3 animation[animationName].speed = speed; 4 animation.CrossFade(animationName); 5 }
4.得到动画播放片段当前帧
吐槽:这个主要用于做动画事件,官方那个AnimationEvent的功能着实感觉鸡肋
1 public static int GetAnimationCurrentFrame(this Animation animation) 2 { 3 var animationName = GetCurrentPlayingAnimationName(animation); 4 if (animationName != null) 5 { 6 var currentTime = animation[animationName].normalizedTime; 7 float totalFrame = animation[animationName].clip.GetAnimationClipTotalFrame(); 8 return (int)(Mathf.Floor(totalFrame * currentTime) % totalFrame); 9 } 10 return -1; 11 }
5.当前动画暂停和恢复播放
吐槽:为啥连这个也没有啊,而且很坑的是如果记录自带的AnimationState后只要动画停止了AnimationState也跟着复位了,所幸还是自己新建一个类吧
1 public class AnimationStateInfo 2 { 3 public string name; 4 public float time; 5 public float speed; 6 7 public AnimationStateInfo(string name,float time,float speed) 8 { 9 this.name = name; 10 this.time = time; 11 this.speed = speed; 12 } 13 }
1 public static AnimationStateInfo Pause(this Animation animation) 2 { 3 var animationName = GetCurrentPlayingAnimationName(animation); 4 if (animationName != null) 5 { 6 var time = animation[animationName].time; 7 var speed = animation[animationName].speed; 8 var state = new AnimationStateInfo(animationName, time, speed); 9 animation.Stop(animationName); 10 return state; 11 } 12 return null; 13 }
1 public static void ResumePlay(this Animation animation, string name, float speed = 1f, AnimationStateInfo state = null) 2 { 3 if (state != null && name == state.name) 4 { 5 var animationName = state.name; 6 animation[animationName].time = state.time; 7 animation[animationName].speed = state.speed; 8 animation.Play(animationName); 9 } 10 else 11 { 12 animation.PlayAnimationWithSpeed(name, speed); 13 } 14 }
有种Animation快要被遗弃的感觉Orz
2020年6月23日更新:
记录一个动画过渡上的坑——关于CrossFade和CrossFadeQueued
用过Animator的话应该知道,在Animator中是可以直接编辑两个动画之间的过渡,这样可以有效防止衔接过程中出现跳帧的情况,但Animation中切换动画则没那么人性化,如果直接生硬的调用Play()方法的话只要前后两个动作幅度相差较大,就会看到很明显的跳帧现象,特别是动画播放速度本身越慢的话则越明显。
虽然我们发现Animation以前在播放时有一个AnimationPlayMode的选项:
1 namespace UnityEngine 2 { 3 public enum AnimationPlayMode 4 { 5 Stop = 0, 6 Queue = 1, 7 Mix = 2 8 } 9 }
但不幸的是,它在未来将会被舍弃:
1 [Obsolete("use PlayMode instead of AnimationPlayMode.")] 2 public bool Play(string animation, AnimationPlayMode mode);
官方说可以用PlayMode代替,然而PlayMode中并没有混合的选项:
1 namespace UnityEngine 2 { 3 // 4 // 摘要: 5 // Used by Animation.Play function. 6 public enum PlayMode 7 { 8 // 9 // 摘要: 10 // Will stop all animations that were started in the same layer. This is the default 11 // when playing animations. 12 StopSameLayer = 0, 13 // 14 // 摘要: 15 // Will stop all animations that were started with this component before playing. 16 StopAll = 4 17 } 18 }
不知道为什么唯独要把Mix去掉,这样的话我们过渡动画时只能用CrossFade或CrossFadeQueued了:
1 // 2 // 摘要: 3 // Fades the animation with name animation in over a period of time seconds and 4 // fades other animations out. 5 // 6 // 参数: 7 // animation: 8 // 9 // fadeLength: 10 // 11 // mode: 12 [GeneratedByOldBindingsGenerator] 13 public void CrossFade(string animation, [DefaultValue("0.3F")] float fadeLength, [DefaultValue("PlayMode.StopSameLayer")] PlayMode mode);
1 // 2 // 摘要: 3 // Cross fades an animation after previous animations has finished playing. 4 // 5 // 参数: 6 // animation: 7 // 8 // fadeLength: 9 // 10 // queue: 11 // 12 // mode: 13 [GeneratedByOldBindingsGenerator] 14 public AnimationState CrossFadeQueued(string animation, [DefaultValue("0.3F")] float fadeLength, [DefaultValue("QueueMode.CompleteOthers")] QueueMode queue, [DefaultValue("PlayMode.StopSameLayer")] PlayMode mode);
一开始我是倾向于用CrossFade的,但后来发现CrossFade也不那么让人感觉完美,根据我写的测试代码,CrossFade方法只有在前一段动画还有播放剩余的情况下才能过渡,要不然依然会跳帧,它并不智能到前一段动画播放完了还能取得该动画最后几帧的位置来进行动画过渡融合,但有时候我们并不能知道前面一段动画何时才能播放完成,例如有些角色可能是先旋转朝向目标完成后再朝前移动,何时开始由旋转动画开始过渡到行走并不是一个定值,会根据和目标实时的角度偏移出现较大变化。一开始我写的很单纯,旋转状态时播放旋转动画,到了行走状态就切换行走不就好了,逻辑上来说似乎顺理成章,但实际出来的效果总是不能让人满意,因为切换到行走状态后基本的旋转状态已经执行结束了,动画也相应的已经播放完成。这样无论再怎么执行下一个阶段的动画总是避免不了跳帧。一开始我以为是过渡的时间调的不对,也许设置更长的过渡时间就能得到有效解决,于是把CrossFade的时间加长到甚至1s之久,但实验后发现对后一段动画产生不了任何影响,因为前一段动画已经完成了播放,没有任何可以执行过渡的部分了。但一开始我并没有想那么多,就单纯的认为是过渡产生了问题而并没有在意一些前提性的失误,于是在这里花费了不少时间调试。之后终于在认真写过测试程序之后才发现了问题。
这时CrossFadeQueued这个方法的作用就一下子凸显了出来,我并不需要管前一个动画何时才能播放完成,只要在旋转完了之后接一个移动动画就好了,当然了,这样进入行走状态时需要进行一次额外的判断,因为不一定是只有旋转过后才会行走,如果一直就是面向目标的话,可能不用执行旋转也能行走,这是如果当前行走状态开始时角色未处于行走动画的播放中,可以重新执行一次过渡播放。
值得注意的是,利用CrossFadeQueued来播放动画的话,没办法直接修改当前队列中动画的参数,这也算是一个坑了,无论你怎么修改当前动画的播放速度与角色匹配,发现都是徒劳的,这到底是什么原因呢?如果细心的话你可能马上就发现了,CrossFade和CrossFadeQueued为什么一个有返回值一个没有呢,CrossFadeQueued返回了一个动画状态,这是你可能马上就豁然开朗了,这家伙可能是为了保持整个动画队列中动画状态的一致性,当你需要修改队列的动画状态时,你需要通过它的返回值来有效处理。
Animation.CrossFadeQueued(Ani_Run).speed = RunAnimationSpeed;