作者: Bugs Bunny
地址: http://www.cnblogs.com/cocos2d-x/archive/2012/03/15/2398516.html
本文函數圖像使用GeoGebra繪制,感謝它才華橫溢的作者。
相比之前的速度正弦變化動作(這個東西叫什么更好一些?漸變動畫?)與速度指數級變化動作,CCEaseIn/CCEaseOut/CCEaseInOut更具靈活性。你可以設置運動的速率,甚至是在運動的過程中改變速率。它們擁有共同的基類——CCEaseRateAction。不要直接使用CCEaseRateAction,因為它沒有實現任何變化效果。
7)CCEaseIn
按照慣例貼出update函數的源代碼,以免版本更新導致文不對題。
1 void CCEaseIn::update(ccTime time)
2 {
3 m_pOther->update(powf(time, m_fRate));
4 }
根據此函數的實現推導出以下三個公式:
s(t)=t^r t∈[0,1]
v(t)=s'(t)=r*t^(r-1) t∈[0,1]
a(t)=v'(t)=r*(r-1)*t^(r-2) t∈[0,1]
s:路程 v:速度 a:加速度 t:時間 r:速率參數m_fRate
都是很基本的導函數推導,如果有不清楚的地方,建議先復習下導數。
下面我們着重分析下速率參數,也就是r的取值范圍。因為是討論二元函數,圖像是三維的,畫出后重疊在一起反而不利於理解,這里就沒有作圖。
1.r<0
當r<0時,v(t)將始終保持為負數,也就是說速度的方向與設定的方向正好相反。
又因為s(1)=1,這說明精靈最后移動到了目標點坐標,但它是從預設軌跡的延長線上反向移動到這一坐標點的。
這是一個很奇怪的動作行為,跟我們預想的設計不符,所以r的取值不應小於零。
2.r=0
當r=0時,s(t)恆等於1,也就說動作在開始之前就已經達到了完成的狀態。我們這里說的是動作的表現,動作原本的執行時間是不受此影響的。
很明顯,這與我們的設計不符,所以零也不是r的一個取值。
3.0<r<1
當r的取值范圍在(0,1)時,a(t)恆為負數,加速度為負說明速度是越來越慢的,這與CCEaseXxxxIn動作應該由慢至快的設定不符,所以這也不是r應有的取值范圍。
4.r=1
當r=1時,a(t)恆等於零,v(t)恆等於一,這說明此時的動作是速度為1的勻速運動。這貌似與設定的有慢至快也不相符。
5.1<r<2
當r的取值范圍在(1,2)時,加速度a(t)恆大於零,但呈下降趨勢。
6.r=2
當r=2是,a(t)恆等於2,也就是此時為勻加速運動。
7.r>2
當r>2時,加速度a(t)恆大於零,且呈上升趨勢。
綜上所述,當你使用CCEaseIn時,傳入的速率參數應大於1.0f,並且根據取值范圍的不同,會呈現出3種不太一樣的加速運動。
8)CCEaseOut
我們再來看一下CCEaseOut的update函數。
1 void CCEaseOut::update(ccTime time)
2 {
3 m_pOther->update(powf(time, 1 / m_fRate));
4 }
第一步還是需要推導出路程、速度、加速度的公式:
s(t)=t^(1/r) t∈[0,1]
v(t)=s'(t)=1/r*(t^(1/r-1)) t∈[0,1]
a(t)=v'(t)=1/r*(1/r-1)*(t^(1/r-2)) t∈[0,1]
第二步分析r的取值范圍:
1.r<0
當r<0時,CCEaseOut的情況與CCEaseIn一樣,運動不在預設軌跡上,排除。
2.r=0
除數不能為零,排除。
3.0<r<1
當r的取值范圍在(0,1)時,a(t)恆大於零,這說明速度是越來越快的,這與CCEaseXxxxOut由快至慢的設定不符,排除。
4.r=1
當r=1時,a(t)恆等於零,這說明是勻速運動,不符合設定,排除。
5.r>1
當r>1時,加速度a(t)恆小於零,但呈上升趨勢。
綜上所述,當你使用CCEaseOut時,傳入的速率參數應大於1.0f,與CCEaseIn不同的是,CCEaseOut只有一類加速度變化趨勢。
在繼續后面的研究之前,我們來做一些額外的思考。CCEaseOut中r的取值為什么與CCEaseIn的有些不一樣呢?是不是它的設計存在什么不合理的地方?
假設我們將r設定為3,同時畫出CCEaseIn和CCEaseOut的圖像:

顏色有點兒亂,不過沒辦法,里面包含3套對比數據,我直接說顏色,希望大家別迷糊。
那條紅色曲線是CCEaseIn的v(t)函數,那條分數的是CCEaseOut的v(t)函數。
大家都知道CCEaseXxxxIn與CCEaseXxxxOut動作的區別是,前者由慢到快,后者由快到慢。如果說得更精確些,它們的表現應該是對稱的——速度函數v(t)按照x=0.5直線軸對稱。
但是顯而易見的,紅色曲線和粉色曲線根本不對稱。
如果大家再仔細想想之前正弦變化和指數級變化的圖像,這里還存在一處對稱。那就是,路程函數s(t)的圖像應該是按照點A(0.5,0.5)中心對稱的。
那條藍色曲線是CCEaseIn的s(t)函數,那條蘭色的是CCEaseOut的s(t)函數。
可以很明顯地看出,它們是按照y=x直線軸對稱的,而不是按照點A中心對稱。
我們將紅色曲線按照x=0.5直線做軸對稱鏡像,得到那條黑色的拋物線。再對此拋物線求其反導函數圖像,得到那條黑色曲線。瞧,它與那條藍色曲線是不是按照點A中心對稱的。
所以,我認為CCEaseOut的update函數應該修改一下。如果你有不同的見解,歡迎在評論區留言。
附上我修改后的代碼:
1 void CCEaseOut::update(ccTime time)
2 {
3 m_pOther->update(1.0f - powf((1.0f - time), m_fRate));
4 }
如果按我這樣修改,那么速率參數的取值范圍與CCEaseIn中是一樣的,大於一。
9)CCEaseInOut
好了,繼續我們的研究:
1 void CCEaseInOut::update(ccTime time)
2 {
3 int sign = 1;
4 int r = (int) m_fRate;
5
6 if (r % 2 == 0)
7 {
8 sign = -1;
9 }
10
11 time *= 2;
12 if (time < 1)
13 {
14 m_pOther->update(0.5f * powf(time, m_fRate));
15 }
16 else
17 {
18 m_pOther->update(sign * 0.5f * (powf(time - 2, m_fRate) + sign * 2));
19 }
20 }
必須指出,這個函數的實現是有問題的。
第一,難道引擎的設計者只希望我們傳入整數型的速率嗎?
問題出在powf函數調用上。
我們知道傳入的time參數范圍在[0,1],即便中間做了一次乘2的操作,到了后面time-2依然是小於等於零的。所以在某些情況下,powf會出現問題。
比如,當m_fRate設置為3.5f之類的小數時,這里的powf會返回"-1.#IND000"。
於是,當動作執行到后半段時,精靈會消失,直到動作全部完成,精靈才會出現在終點上。
第二,那個sign正負標志是用來解決問題的嗎?怎么感覺引入后反而將問題復雜化了。
這里其實只需將前半段的函數按照點(0.5,0.5)做一次中點對稱就可以了,用不着這么麻煩。
我修改的代碼如下:
1 void CCEaseInOut::update(ccTime time)
2 {
3 time *= 2;
4 if (time < 1)
5 {
6 m_pOther->update(0.5f * powf(time, m_fRate));
7 }
8 else
9 {
10 m_pOther->update(0.5f * (2.0f - powf((2.0f - time), m_fRate)));
11 }
12 }
因為CCEaseInOut與CCEaseIn使用相同的算法,所以在這里速率參數的取值范圍與CCEaseIn的一樣,也是大於一。
小結
到目前為止,我們對CCActionEase的學習已經完成了一半。
我們一共學習了3類,9個動作。它們分別是CCEaseSineIn、CCEaseSineOut、CCEaseSineInOut、CCEaseExponentialIn、CCEaseExponentialOut、CCEaseExponentialInOut、CCEaseIn、CCEaseOut、CCEaseInOut。
它們與之后將要學習的動作的最大區別是,在這些動作的執行過程中,精靈會嚴格地按照內部動作指定的路徑移動,絕對不會超出起始點與終點的范圍。
在CCEaseIn/CCEaseOut/CCEaseInOut中,速度的變化范圍是[0,m_fRate],m_fRate>1。
但是,如果cocos2d-x需要與cocos2d-iphone從原則上保持高度一致,即便是存在缺陷也不能破壞原則的話,那么我推測官方在短時間內是不會修改這個問題的。因為,在大約6個月之前,有朋友提出過此問題,但似乎被無視了。
http://www.cocos2d-iphone.org/forum/topic/20979
http://code.google.com/p/cocos2d-iphone/issues/detail?id=1248
所以,在官方正式修正此問題之前,我建議大家只使用大於1的整數作為速率的參數,以提高兼容性。
