在用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;