天天看點

NGUI所見即所得之UITweener

 NGUI所見即所得之UITweener

        一直沒有用過NGUI動畫的功能,之前的了解就是:設定始末兩個“位置”,然後就是從起始位置移到結束位置。至于中間是怎麼變化的,就感覺很神奇了,變化率怎麼設定才不會看起來很“傻”,這裡不是看“郭靖”,動畫一定要有驚奇,摸不着猜不透的感覺。對NGUI主要的幾個腳本都已經有點掌握了(猛點檢視),一直都沒有去”膜拜“Tweening檔案夾的各個大神,可能以前會覺得不就是一個動畫元件,自己都可以實作。但是看過裡面的代碼就後悔了,因為至少結合TweenFOV和TweenOrhoSize這兩個腳本就可以實作很多效果,竟然輕而易舉的內建了,看來人還是不要太看得起自己的好,這樣才會走的更快更遠。

        每次都覺得前面吹水很寫,也寫不好(一直都有感覺自己的寫作水準太差了),那就來看下Tweening檔案夾下到底賣的是什麼藥——UITweener和它的“孩子”: 

NGUI所見即所得之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設定需要的參數:

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:

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種動畫曲線:

public enum Method
	{
		Linear,
		EaseIn,
		EaseOut,
		EaseInOut,
		BounceIn,
		BounceOut,
	}      

 采樣的函數,原理很簡單:根據目前播放的進度mFactor,計算出實際的動畫播放刻度,然後執行OnUpdate操作:

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)

        上面說的動畫曲線,中文叫緩動函數(曲線):

NGUI所見即所得之UITweener

        通過上面這張圖可以很感性的認識不同函數的具體的效果,也可以自己嘗試推導一邊加深了解,不過D.S.Qiu已經有點“廉頗老矣”,憑着記憶“奇變偶不變,符号看象限”,慢的隻能到easeInSine,要想詳細了解可以參考②和③。

妙用mAmountPerDelta

         mAmountPerDelta就是動畫播放速度,隻對mAmountPerData就可以有更多控制:Toggle,PlayForward,PlayResverse:

/// <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如何設定:

public Vector3 from;
	public Vector3 to;      

 後來無奈之下,隻好去看下TweenRotation的OnUpdate函數,發現使用的是 Quaternion.Slerp這個函數,發現确實是這樣,是以就做了下面的修改:

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):

void OnClick ()
{
	if (enabled && trigger == Trigger.OnClick)
	{
		Play(true);
	}
}
      

       雖然UIPlayTween提供了很多參數都是沒有滿足要其交替地進行PlayForward和PlayReverse,因為其都是執行Play(true),開始的時候我想到了給其中一個UITween添加OnFinished委托,在播放結束的時候改變playDiretion的方向:

public Direction playDirection = Direction.Forward;      

       但是這個控制因為動畫是有時間的,會有問題。是以我隻能添加一個Trigger的類型:Trigger.None,然後自己去調用,就是自己管理動畫的播放,而不是在OnClick中觸發。』

                                                                                           增補于 2013,12,23  21:50

小結:

       确實很簡單,主要是對緩動函數的了解,有了這些基礎可以做的事情(特效和動畫)就很多了——螢幕抖動和刀光劍影(下次自己動手嘗試下,哈哈),NGUI的ButtonScale等腳本也是通過UITweener來完成的。但是收獲蠻多的,又一點多了,晚安!

       如果您對D.S.Qiu有任何建議或意見可以在文章後面評論,或者發郵件([email protected])交流,您的鼓勵和支援是我前進的動力,希望能有更多更好的分享。

        轉載請在文首注明出處:http://dsqiu.iteye.com/blog/1974528

更多精彩請關注D.S.Qiu的部落格和微網誌(ID:靜水逐風)

參考:

①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