查NGUI動畫方面的資料時,發現D.S.Qiu寫的一篇UITweener源碼剖析的blog,源碼面前了無秘密,原文鏈接。另外,D.S.Qiu寫過一系列NGUI源碼的blog,值得每個使用NGUI的研讀。
NGUI所見即所得之UITweener
一直沒有用過NGUI動畫的功能,之前的理解就是:設置始末兩個“位置”,然后就是從起始位置移到結束位置。至于中間是怎么變化的,就感覺很神奇了,變化率怎么設置才不會看起來很“傻”,這里不是看“郭靖”,動畫一定要有驚奇,摸不著猜不透的感覺。對NGUI主要的幾個腳本都已經有點掌握了(猛點查看),一直都沒有去”膜拜“Tweening文件夾的各個大神,可能以前會覺得不就是一個動畫組件,自己都可以實現。但是看過里面的代碼就后悔了,因為至少結合TweenFOV和TweenOrhoSize這兩個腳本就可以實現很多效果,竟然輕而易舉的集成了,看來人還是不要太看得起自己的好,這樣才會走的更快更遠。
每次都覺得前面吹水很寫,也寫不好(一直都有感覺自己的寫作水平太差了),那就來看下Tweening文件夾下到底賣的是什么藥——UITweener和它的“孩子”:
UITweener的Fields 看著很復雜,其實只要把UITweener琢磨透了,其它都只是重寫UITweener的OnUpdate方法和封裝了Begin方法。還是先看下主要的Field(作用看注釋):
bool mStarted = false; //是否開始動畫 float mStartTime = 0f; //動畫開始播放的時間, mStarted =true;mStartTime = time + delay; float mDuration = 0f; //動畫長度(時間) float mAmountPerDelta = 1000f; //單位時間動畫播放的長度,有點幀率的感覺 float mFactor = 0f; //當前動畫播放的進度 /// <summary> /// Amount advanced per delta time. /// </summary> public float amountPerDelta { get { if (mDuration != duration) { mDuration = duration; mAmountPerDelta = Mathf.Abs((duration > 0f) ? 1f / duration : 1000f); } return mAmountPerDelta; } } 通過Begin設置需要的參數:
C#代碼
static public T Begin<T> (GameObject go, float duration) where T : UITweener { T comp = go.GetComponent<T>(); if UNITY_Flash if ((object)comp == null) comp = (T)go.AddComponent<T>(); else if (comp == null) comp = go.AddComponent<T>(); endif comp.mStarted = false; comp.duration = duration; comp.mFactor = 0f; comp.mAmountPerDelta = Mathf.Abs(comp.mAmountPerDelta); comp.style = Style.Once; comp.animationCurve = new AnimationCurve(new Keyframe(0f, 0f, 0f, 1f), new Keyframe(1f, 1f, 1f, 0f)); comp.eventReceiver = null; comp.callWhenFinished = null; comp.enabled = true; return comp; } Update函數
然后再Update函數先計算出時間delta,進一步計算出當前動畫播放的mFactor,然后進行Sample采用,執行OnUpdate:
C#代碼
void Update () { float delta = ignoreTimeScale ? RealTime.deltaTime : Time.deltaTime; float time = ignoreTimeScale ? RealTime.time : Time.time; if (!mStarted) { mStarted = true; mStartTime = time + delay; } if (time < mStartTime) return; // Advance the sampling factor mFactor += amountPerDelta * delta; // Loop style simply resets the play factor after it exceeds 1. if (style == Style.Loop) { if (mFactor > 1f) { mFactor -= Mathf.Floor(mFactor); } } else if (style == Style.PingPong) { // Ping-pong style reverses the direction if (mFactor > 1f) { mFactor = 1f - (mFactor - Mathf.Floor(mFactor)); mAmountPerDelta = -mAmountPerDelta; } else if (mFactor < 0f) { mFactor = -mFactor; mFactor -= Mathf.Floor(mFactor); mAmountPerDelta = -mAmountPerDelta; } } // If the factor goes out of range and this is a one-time tweening Operation, disable the script if ((style == Style.Once) && (mFactor > 1f || mFactor < 0f)) { mFactor = Mathf.Clamp01(mFactor); Sample(mFactor, true); current = this; // Notify the listener delegates EventDelegate.Execute(onFinished); // DePRecated legacy functionality support if (eventReceiver != null && !string.IsNullOrEmpty(callWhenFinished)) eventReceiver.SendMessage(callWhenFinished, this, SendMessageOptions.DontRequireReceiver); current = null; // Disable this script unless the function calls above changed something if (mFactor == 1f && mAmountPerDelta > 0f || mFactor == 0f && mAmountPerDelta < 0f) enabled = false; } else Sample(mFactor, false); } Sample采樣函數
前面說的動畫要有摸不著猜不透的感覺,就是要考Sample的采樣函數來實現的,UITweener支持5種動畫曲線:
C#代碼
public enum Method { Linear, EaseIn, EaSEOut, EaseInOut, BounceIn, BounceOut, } 采樣的函數,原理很簡單:根據當前播放的進度mFactor,計算出實際的動畫播放刻度,然后執行OnUpdate操作:
C#代碼
public void Sample (float factor, bool isFinished) { // Calculate the sampling value float val = Mathf.Clamp01(factor); if (method == Method.EaseIn) { val = 1f - Mathf.Sin(0.5f * Mathf.PI * (1f - val)); if (steeperCurves) val *= val; } else if (method == Method.EaseOut) { val = Mathf.Sin(0.5f * Mathf.PI * val); if (steeperCurves) { val = 1f - val; val = 1f - val * val; } } else if (method == Method.EaseInOut) { const float pi2 = Mathf.PI * 2f; val = val - Mathf.Sin(val * pi2) / pi2; if (steeperCurves) { val = val * 2f - 1f; float sign = Mathf.Sign(val); val = 1f - Mathf.Abs(val); val = 1f - val * val; val = sign * val * 0.5f + 0.5f; } } else if (method == Method.BounceIn) { val = BounceLogic(val); } else if (method == Method.BounceOut) { val = 1f - BounceLogic(1f - val); } // Call the virtual update OnUpdate((animationCurve != null) ? animationCurve.Evaluate(val) : val, isFinished); } 緩動函數(easing fuction)
上面說的動畫曲線,中文叫緩動函數(曲線):
通過上面這張圖可以很感性的認識不同函數的具體的效果,也可以自己嘗試推導一邊加深理解,不過D.S.Qiu已經有點“廉頗老矣”,憑著記憶“奇變偶不變,符號看象限”,慢的只能到easeInSine,要想詳細了解可以參考②和③。
妙用mAmountPerDelta
mAmountPerDelta就是動畫播放速度,只對mAmountPerData就可以有更多控制:Toggle,PlayForward,PlayResverse:
C#代碼
/// <summary> /// Manually activate the tweening process, reversing it if necessary. /// </summary> public void Play (bool forward) { mAmountPerDelta = Mathf.Abs(amountPerDelta); if (!forward) mAmountPerDelta = -mAmountPerDelta; enabled = true; Update(); } /// <summary> /// Manually start the tweening process, reversing its direction. /// </summary> public void Toggle () { if (mFactor > 0f) { mAmountPerDelta = -amountPerDelta; } else { mAmountPerDelta = Mathf.Abs(amountPerDelta); } enabled = true; }
『Bug修復和吐槽
之前用TweenRotation這個腳本,做游戲等待轉圈等待界面,發現總是不能旋轉360度,總是一個小于180的角度,無論from和to如何設置:
C#代碼
public Vector3 from; public Vector3 to; 后來無奈之下,只好去看下TweenRotation的OnUpdate函數,發現使用的是 Quaternion.Slerp這個函數,發現確實是這樣,所以就做了下面的修改:
C#代碼
protected override void OnUpdate (float factor, bool isFinished) { //cachedTransform.localRotation = Quaternion.Slerp(Quaternion.Euler(from), Quaternion.Euler(to), factor); //NGUI的實現是上一行,有Bug,不能達到要求 cachedTransform.localEulerAngles = Vector3.Lerp(from, to, factor); }
NGUI提供了UIPlayTween,這個腳本管理一組Tween腳本的Play,提供了不同的Tirgger,然后在不同的事件函數中觸發Play(true):
C#代碼
void OnClick () { if (enabled && trigger == Trigger.OnClick) { Play(true); } } 雖然UIPlayTween提供了很多參數都是沒有滿足要其交替地進行PlayForward和PlayReverse,因為其都是執行Play(true),開始的時候我想到了給其中一個UITween添加OnFinished委托,在播放結束的時候改變playDiretion的方向:
C#代碼
public Direction playDirection = Direction.Forward; 但是這個控制因為動畫是有時間的,會有問題。所以我只能添加一個Trigger的類型:Trigger.None,然后自己去調用,就是自己管理動畫的播放,而不是在OnClick中觸發。』
增補于 2013,12,23 21:50
小結:
確實很簡單,主要是對緩動函數的理解,有了這些基礎可以做的事情(特效和動畫)就很多了——屏幕抖動和刀光劍影(下次自己動手嘗試下,哈哈),NGUI的ButtonScale等腳本也是通過UITweener來完成的。但是收獲蠻多的,又一點多了,晚安!
參考:
①NGUI: Next-Gen UI kit 3.0.0:http://www.tasharen.com/ngui/docs/class_u_i_tweener.html
② 緩動函數:http://easings.net/zh-cn
③Easing Equations by Robbert Penner: http://www.gizma.com/easing/#sin2
④Unity3dPack: http://www.unity3dpack.com/?p=300
新聞熱點
疑難解答