本文函数图像使用GeoGebra绘制,感谢它才华横溢的作者。
在上一篇中我们介绍了两种反弹效果动作——Ease Back和Ease Bounce,今天我们将要介绍第三种反弹效果动作——Ease Elastic。
那么这个Elastic是什么呢?
维基百科上说,Elastic是对弹性体或者可伸缩纤维的口语说法,或者是指小孩子玩的跳皮筋游戏。
下面我们就研究一下这个Elastic和皮筋有什么关系。
要研究动作的行为,就离不开它的update函数。
1 void CCEaseElasticIn::update(ccTime time)
2 {
3 ccTime newT = 0;
4 if (time == 0 || time == 1)
5 {
6 newT = time;
7 }
8 else
9 {
10 float s = m_fPeriod / 4;
11 time = time - 1;
12 newT = -powf(2, 10 * time) * sinf((time - s) * M_PI_X_2 / m_fPeriod);
13 }
14
15 m_pOther->update(newT);
16 }
这是一个分段函数,两端的时间点被独立出来了。先抛开这两点不管,我们看主要的变换代码。
首先看到:
time = time - 1;
这其实就是做了一次以x=0.5为轴的轴对称。
为了方便我们将time - 1记作u,m_fPeriod记作p,那么变换公式如下:
u=t-1 t∈(0,1)
s(u)=-1*2^(10u)*sin((u-p/4)*2π/p) u∈(-1,0)
s(u)=-1*2^(10u)*sin(2πu/p-π/2) u∈(-1,0)
s(u)=2^(10u)*sin(π/2-2πu/p) u∈(-1,0)
s(u)=2^(10u)*cos(2πu/p) u∈(-1,0)
大家都知道余弦函数是以2π为周期的,这里公式中有一个除以p的操作,这说明cos(2πu/p)的周期为p。
又因为u∈(-1,0),1/1024<2^(10u)<1,这说明2^(10u)与cos(2πu/p)相乘最多影响函数曲线的幅度,绝不会改变原来的周期。
所以s(u)的函数周期就是p。
因为这是一个分段函数,所以可能存在的最大问题就是,在实际运动过程中,精灵的动作可能不是连续的,即(0,0)和(1,1)两点不在函数曲线上。所以如果你希望让精灵完美地运动,你就需要小心地选取这个周期参数。
如何选取周期参数呢?
假设我们有这样一个函数:
f(u)=cos(2πu/p)
我们希望它能满足以下条件:
1.f(0)=1
2.f(1)=0
也就是说,我们希望点(0,f(0))落在图中点A的位置,而点(1,f(1))落在图中点B、C、D、E、F这样的位置上。
条件一是时刻满足的,我们只要针对条件二来确定p就可以了。于是我们得出等式:
2π/p=π/2+N*π N≥0
p=4/(2*N+1) N≥0
于是我们可以得出,比较合适的p的取值有:4、4/3、4/5、4/7。。。
我们选取p=4/7,绘出完整函数图像,看看是什么样子的。
这是一条振荡曲线,或者叫做波动曲线,它描述的是精灵运动的幅度。
用什么来形容这个动作呢?我也说不好。地震监测仪上的那支笔?也许吧。
1 void CCEaseElasticOut::update(ccTime time)
2 {
3 ccTime newT = 0;
4 if (time == 0 || time == 1)
5 {
6 newT = time;
7 }
8 else
9 {
10 float s = m_fPeriod / 4;
11 newT = powf(2, -10 * time) * sinf((time - s) * M_PI_X_2 / m_fPeriod) + 1;
12 }
13
14 m_pOther->update(newT);
15 }
将CCEaseElasticIn的图像按照点(0.5,0.5)中心对称镜像,得到的就是CCEaseElasticOut的图像。
但是,如果你直接将中心对称公式套入CCEaseElasticIn的函数中,推导出的并不是上面CCEaseElasticOut使用的计算公式。不过不用特别担心,如果再考虑上正弦函数的性质,你会发现他们是相等的。
这是最后一个CCActionEase类动作,胜利在望,加把劲。
1 void CCEaseElasticInOut::update(ccTime time)
2 {
3 ccTime newT = 0;
4 if (time == 0 || time == 1)
5 {
6 newT = time;
7 }
8 else
9 {
10 time = time * 2;
11 if (! m_fPeriod)
12 {
13 m_fPeriod = 0.3f * 1.5f;
14 }
15
16 ccTime s = m_fPeriod / 4;
17
18 time = time - 1;
19 if (time < 0)
20 {
21 newT = -0.5f * powf(2, 10 * time) * sinf((time -s) * M_PI_X_2 / m_fPeriod);
22 }
23 else
24 {
25 newT = powf(2, -10 * time) * sinf((time - s) * M_PI_X_2 / m_fPeriod) * 0.5f + 1;
26 }
27 }
28
29 m_pOther->update(newT);
30 }
在这段代码中,首先将time扩大一倍,然后在减去一,也就是将时间分成了前后两段。
接着对前后两段时间分别套用相应的公式,进行变换。
基本都挺正常的,除了下面这一段:
11 if (! m_fPeriod)
12 {
13 m_fPeriod = 0.3f * 1.5f;
14 }
这个周期参数百分之百会被赋值,而且正常人也不会传入一个零作周期。我实在是看不出来这段代码有什么用途,所以还得请高人赐教。
至此CCActionEase类系的动作就都介绍完了。其中最头痛的就是这个Ease Elastic类的动作,要把这类动作所做的运动描述清楚,感觉是挺费劲的。
到底该怎么更好的描述Ease Elastic类的动作呢?还得让大家一起动动脑子。
这类的动作用在游戏里什么地方最合适?谁想到了就积极的留言吧。
最后,总结一下本节的重点:
1.如果fPeriod的值越小,那么精灵弹的次数就越多。
2.如果想要精灵完美运动,即起始点(0,0)、结束点(1,1)以及连接点(0.5,0.5)都在函数曲线上,那么fPeriod的值应该符合下面的公式:
fPeriod=4/(2*N+1) N≥0
3.在逻辑上fPeriod的值要大于零,在实际应用中还要受float类型精度的限制。