CCActionEase想说爱你也不难

2024-01-30 21:18

本文主要是介绍CCActionEase想说爱你也不难,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

尊重作者劳动,转载时请标明文章出处。
作者: Bugs Bunny
地址: http://www.cnblogs.com/cocos2d-x/archive/2012/03/19/2407032.html


本文函数图像使用GeoGebra绘制,感谢它才华横溢的作者。

为了方便用户灵活地控制精灵运动,cocos2d-x提供了CCActionEase类系的动作。它们拥有相似的名字——CCEaseXxxxIn、CCEaseXxxxOut、CCEaseXxxxInOut,同时也拥有相似的行为——速度由慢至快、速度由快至慢、速度先由慢至快再由快至慢。但是除了这些,我们对CCActionEase一无所知。就算查阅参考手册,我们能得到的信息也不过是类似Ease Sine In的简短说明。它们究竟是什么模样,我们该如何选择?

今天我们就来解决这个问题。鉴于CCActionEase类系的庞大,文章可能会分成两到三篇。

1)CCEaseSineIn

在《cocos2d-x动作系统浅析》一文中提到:
update函数接受一个百分比参数,它表示动作的完成进度。update根据这个百分比将目标对象做出相应的调整。
可以说这个update函数就是CCActionEase的灵魂。

1 void CCEaseSineIn::update(ccTime time)
2 {
3 m_pOther->update(-1 * cosf(time * (float)M_PI_2) + 1);
4 }

之前我们已经知道CCActionEase类系的动作就是调整其他动作的速度,变换出新的效果。这里的m_pOther就是那个被影响的动作,而一切魔力的源头就在它接受的参数上。CCEaseSineIn将传入的百分比参数进行了一系列变换,然后传给了m_pOther。

我们将这个变换公式提取出来,记作:
f(x)=1-cos(π/2*x) x∈[0,1]
这个就是已用时间百分比与实际完成进度的关系。在匀速运动中,它们应该是相等的,但是在变速运动中,它们的关系就会变幻莫测。

上图中的黑色曲线就是f(x)的函数图像。它的定义域从0开始,到1结束,值域也是这样。根据这条线的走势,可以粗略看出速度是越变越快的,但还是不够形象。
在运动学中,物体的位移对于时间的导数就是物体的瞬时速度。如果我们能得到这条瞬时速度的曲线,那就直观多了。上面的函数f(x)是已用时间百分比与实际完成进度的关系,这里可以近似地理解为时间与路程的关系。
所以我们对f(x)求导,得出:
f'(x)=π/2*sin(π/2*x) x∈[0,1]
它对应图中那条红色曲线。可以很明显地看出,速度越变越快,在C点达到了最高。
正如它名字说的那样,它的速度由慢至快,呈正弦变化。

2)CCEaseSineOut

我们再来看下CCEaseSineOut类。

1 void CCEaseSineOut::update(ccTime time)
2 {
3 m_pOther->update(sinf(time * (float)M_PI_2));
4 }

同理得出:
f(x)=sin(π/2*x) x∈[0,1]
f'(x)=π/2*cos(π/2*x)

同样我们更关注那条红色曲线,它从最高点C出发,一路下降到达A点。这表明在CCEaseSineOut动作中,速度是越来越慢的,它的图像也呈正弦变化。

3)CCEaseSineInOut

我们知道CCEaseXxxxInOut的速度变化是先由慢至快,再由快至慢。如果我们将上面两个图像拼在一起,然后在将横轴比例缩小一倍,那结果就是这条曲线的模样了。
一般情况下,我们需要将函数分成两段,第一段在0到0.5之间,第二段在0.5到1之间。我们来看看CCEaseSineInOut是如何实现的。

1 void CCEaseSineInOut::update(ccTime time)
2 {
3 m_pOther->update(-0.5f * (cosf((float)M_PI * time) - 1));
4 }

f(x)=-0.5*(cos(π*x)-1) x∈[0,1]
f'(x)=π/2*sin(π*x)

在CCEaseSineInOut中,这两段曲线正好是同一个函数(非分段函数)的图像。很巧妙是不是?
图中红色曲线从原点O出发,一路上升到达最高点C,然后又一路下滑降至D点。它同样也是一条正弦变化的曲线。动作的速度看起来就是由慢至快,再由快至慢的。

小结

CCEaseSineIn、CCEaseSineOut、CCEaseSineInOut这三个动作同属速度正弦变化,变化的范围是[0,π/2]。

4)CCEaseExponentialIn

有了前面的经验,后面就容易多了,先来看一下CCEaseExponentialIn的update函数。

1 void CCEaseExponentialIn::update(ccTime time)
2 {
3 m_pOther->update(time == 0 ? 0 : powf(2, 10 * (time/1 - 1)) - 1 * 0.001f);
4 }

大家可能已经注意到,这里使用了一个条件运算符,于是表达式变作了分段函数。
当x=0时,f(x)=0
当x∈(0,1]时,f(x)=2^(10*(x-1))-0.001

注意这条不是速度的曲线。
上面副绘图区中的图像就是这个函数的整体走势,我们在主绘图区给原点附近的曲线一个特写。可以看到,除了x=0的情况,曲线与x轴还有一个交点。
对2^(10*(x-1))-0.001=0求解,得出:
x=1-ln(1000)/(10*ln(2))=0.00342

现在我们开始在脑中想象一下精灵按照CCEaseExponentialIn动作移动的详细步骤。
首先,时间从零开始,精灵被设置到起始位置。这一步是正常的没有问题。
接下来,精灵猛地朝着反方向跳动了很小的一段距离。这个距离是非常非常小的,也就是图上的B点附近,大约只占整个移动距离的0.00234%
然后,精灵开始以变化的速度朝着目标点移动。经过点A时精灵回到初始位置。这时,我们设计的运动才刚刚开始。
如果我们将x=1代入公式,可以推算出:
f(x)=1-0.001=0.999
也就是说,图像最终没有到达终点,而是差了一小段距离。

简单来说,总时间的前0.342%部分以及最终的那一瞬间的运动是不太正常的。
如果你设计了一个超过1000秒的运动,那么前3秒内,精灵的准确位置不会在你设计的轨迹上。
当然如果想观察到这个问题,运动的距离也是一个关键。
假设你疯狂地设计了一个运动10万像素的精灵,并且运动时间超过1000秒,那你就能观察到这一现象了。3秒钟,反向2个像素。

但是为什么会这样呢?是引擎的bug吗?
确切来说,这应该算不上是bug,这只是精度引起的问题。

下面这段都是我自己的推测,也就是猜到,大家看看就好了。
我猜测这个公式的最初原型应该是:
f(x)=2^(10*(x-1)) x∈[0,1]
但是它有一个问题,那就是当x=0的时候,f(0)=1/1024
时间为零的时候,精灵大约就已经有了千分之一的位移,而且是在一个物体运动刚开始的时候,猛然地跳动是非常明显的。所以设计者将千分之一的误差移动到了末尾,也就是运动要结束的时候。
那公式现在的样子就是:
f(x)=2^(10*(x-1))-1/1024 x∈[0,1]

大家都知道cocos2d-x多使用单精度浮点型数字,以及写0.0009765625f比较麻烦等诸多因素,最后这个公式就简化成了现在的模样。

我的猜想说完了,我们接着来求导:
f'(x)=10*2^(10*(x-1))*ln(2) x∈[0,1]

按照最理想的那个公式绘制出图像,这里我们只看那条红色的曲线。这条曲线从D点开始一路上升,迅速到达C点。如果你对它再次求导,就能得出其加速度的变化规律。从DC曲线上应该可以看出其加速度也是越来越大的。
额,说得有点儿远了。我们把注意力先集中起来,计算出速度的最小值和最大值。
f'(0)=10*2^(-10)*ln(2)=0.006769
f'(1)=10*2^0*ln(2)=6.931472

CCEaseExponentialIn的速度由慢至快,从0.006769上升至6.931472,呈指数级变化。

5)CCEaseExponentialOut

1 void CCEaseExponentialOut::update(ccTime time)
2 {
3 m_pOther->update(time == 1 ? 1 : (-powf(2, -10 * time / 1) + 1));
4 }

CCEaseExponentialOut与CCEaseExponentialIn的实现是相似的,唯一的不同是CCEaseExponentialOut在最后一瞬间会有短距离的跳跃(千分之一的误差),而CCEaseExponentialIn是舍弃部分。个人认为CCEaseExponentialOut的处理方式更合理些。
好了直接上图

这里没有难点,我直接让工具生成的导函数图像。
我们关心的是A点(0,6.93147)和D点(1,0.00677),与CCEaseExponentialIn的速度范围是一样的。从6.93147下降至0.00677,速度为由快至慢的指数变化。

6)CCEaseExponentialInOut

在《知易游戏开发教程cocos2d-x移植版003》中有一段CCEaseExponentialInOut的演示代码,测试运行时会发现精灵最后以极快的速度飞出了屏幕,是笔者使用不当,还是别的什么原因?当时由于时间、精力的问题没有深入研究,今天借此机会将问题分析一下。

复制代码
 1 void CCEaseExponentialInOut::update(ccTime time)
2 {
3 time /= 0.5f;
4 if (time < 1)
5 {
6 time = 0.5f * powf(2, 10 * (time - 1));
7 }
8 else
9 {
10 time = 0.5f * (-powf(2, 10 * (time - 1)) + 2);
11 }
12
13 m_pOther->update(time);
14 }
复制代码

呵呵,典型的分段函数。绘制函数图像如下:

图中这条蓝色的曲线就是CCEaseExponentialInOut使用的分段函数。很明显可以看到在A点处,曲线走向发生了90°的变化,向着点(1,-511)延伸。它没有像前面说过的函数那样逼近点C(1,1),这就解释了为什么精灵莫名其妙地飞出了屏幕。

这是一个bug,我们希望曲线的后半段能像那条绿色的曲线AC那样。(我只在Win32平台上测试的,不知其他平台上是否也存在这个问题,有兴趣的朋友可以测试下。)

我的修改如下:

复制代码
 1 void CCEaseExponentialInOut::update(ccTime time)
2 {
3 time /= 0.5f;
4 if (time < 1)
5 {
6 time = 0.5f * powf(2, 10 * (time - 1));
7 }
8 else
9 {
10 // 将(time - 1)变作(1 - time)
11 time = 0.5f * (-powf(2, 10 * (1 - time)) + 2);
12 }
13
14 m_pOther->update(time);
15 }
复制代码

修正后,动作的行为正常了。
对新的函数求导,得出图中的红色曲线。其中点D、点E、点F的坐标分别为(0.5,6.93147)、(0,0.00677)、(1,0.00677)。
细心的朋友可能已经发现了点C没有到达(1,1)。是的,这里存在0.000488的误差,曲线的起始点也一样。即原来1/1024的误差被平分到了开头和末尾。

小结

CCEaseExponentialIn、CCEaseExponentialOut、CCEaseExponentialInOut这三个动作同属速度指数级变化,变化的范围是[0.00677,6.93147]。


本文函数图像使用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的整数作为速率的参数,以提高兼容性。


本文函数图像使用GeoGebra绘制,感谢它才华横溢的作者。

在上一篇中我们介绍了两种反弹效果动作——Ease Back和Ease Bounce,今天我们将要介绍第三种反弹效果动作——Ease Elastic。

那么这个Elastic是什么呢?

维基百科上说,Elastic是对弹性体或者可伸缩纤维的口语说法,或者是指小孩子玩的跳皮筋游戏。

下面我们就研究一下这个Elastic和皮筋有什么关系。

16)CCEaseElasticIn

要研究动作的行为,就离不开它的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,绘出完整函数图像,看看是什么样子的。

这是一条振荡曲线,或者叫做波动曲线,它描述的是精灵运动的幅度。

用什么来形容这个动作呢?我也说不好。地震监测仪上的那支笔?也许吧。

17)CCEaseElasticOut

复制代码
 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使用的计算公式。不过不用特别担心,如果再考虑上正弦函数的性质,你会发现他们是相等的。

18)CCEaseElasticInOut

这是最后一个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类型精度的限制。

谢谢各位的观看,如果您有什么好的建议,请给我留言。




这篇关于CCActionEase想说爱你也不难的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/661583

相关文章

赶紧码住!8款AI智能论文生成系统,毕业论文编写不难!

在当今学术研究和写作领域,AI智能论文生成系统已经成为一种重要的辅助工具。这些系统利用先进的自然语言处理技术和深度学习算法,能够帮助用户快速生成高质量的论文内容,从而大大提高了写作效率和质量。以下是8款推荐的AI智能论文生成系统,其中特别推荐千笔-aipasspaper。 1. 千笔-AIPassPaper 千笔-AIPassPaper是一款基于深度学习和自然语言处理技术的AI写作助手,旨在帮

Python爬虫入门并不难,甚至进阶也很简单(去广告版)

参考视频教程:    **Python爬虫工程师从入门到进阶  ** image 互联网的数据爆炸式的增长,而利用 Python 爬虫我们可以获取大量有价值的数据: 1.爬取数据,进行市场调研和商业分析 爬取知乎优质答案,筛选各话题下最优质的内容; 抓取房产网站买卖信息,分析房价变化趋势、做不同区域的房价分析;爬取招聘网站职位信息,分析各行业人才需求情况及薪资水平。 2.作为机

Shader,想说爱你不容易。

这两天开始接触shader,给我的第一感觉是容易,第二感觉是难,第三赶脚是数学不好就先学数学吧! 首先我们通过unity自己创建一个shader脚本的时候,其实shader已经给我们搭建好了编写shader基本的框架,我们要做的就是往里面写自己的shader,但是写之前总的先认识这里面有啥子东西吧。先创建一个shader瞅瞅,我使用的unity的版本为5.x,代码如下: Shader

数字工厂管理系统的使用操作难不难

在当今智能制造的浪潮中,引入数字工厂管理系统无疑为企业带来了前所未有的效率提升与决策优化能力。然而,谈及数字工厂管理系统的操作难易程度,这并非一个可以一概而论的问题,它深深植根于系统的复杂性、用户技能水平、培训深度以及企业内部对技术变革的接纳程度等多个维度之中。以下,我们将从几个关键方面深入探讨,并提出一系列旨在提升数字工厂管理系统操作体验与运维效率的策略。 一、强化培训,奠定坚实基础 在

零基础小白看过来!人工智能到底是学习什么?算法是什么?难不难学?

# 人工智能到底是学什么?# 以豆包、ChatGPt、文心一言、通义千问为代表的大模型;以百度、华为、特斯拉、蔚小理为代表的自动驾驶;以讯飞、百度为代表的语音识别技术,以及手机上的人脸识别等等,都依托于人工智能技术。 可见人工智能是个广义的学科,涉及基础层、技术层、应用层的技术,它们共同构成了人工智能的全貌。人工智能的学习,核心是技术层和应用层的学习,技术层核心学习的是各类AI算法,应用层更

UIScrollView的无限左滑轮播一点也不难

UIScrollView的轮播在如今的app中用得十分广泛,最初实现的时候方式比较拙劣,滚动到最后一个视图时再返回到第一个看起来非常的不连贯。 今天查询UIScrollView轮播资料,总结两种比较喜欢也易于理解的方法: 1、UIScrollView显示的子视图左右各放一个多出来的子视图,如本来有A/B/C三张图片,在A前面放一个ImageView显示C,在C后天放一个ImageView显示A

苹果手机上音乐转换成mp3格式难不难?电脑支持转换吗?支持!

在苹果手机上,有时我们可能会遇到需要将音乐文件转换为MP3格式的情况。这可能是因为某些音乐播放器或设备只支持MP3格式,或者我们想要将音乐文件与其他设备或平台共享。本文将为您详细介绍在苹果手机上如何将音乐转换为MP3格式。 首先,我们需要明确一点:苹果手机自带的音乐播放器主要支持AAC、MP3、ALAC、WAV等格式的音乐文件。如果您想要播放其他格式的音乐文件,确实需要将其转换为支持的格式。

再谈毕业论文设计投机取巧之IVR自动语音服务系统设计(信息与通信工程A+其实不难)

目录 举个IVR例子格局打开,万物皆能IVR IVR系统其实可盐可甜。还能可圈可点。 戎马一生,归来依然IVR。 举个IVR例子 以下是IVR系统的一个例子。 当您拨打电话进入IVR系统。 首先检验是否为工作时间。 如是,您将被送入ivr-lang阶段,随后您将得到hello信息,您可以见如一到三选择您的语言。根据您键入的数字,您将被引入正确的分机。 exten =>

县供电公司员工向媒体投稿发文章用亲身经历告诉你并不难

在县供电公司的日子里,我肩负着一项至关重要的使命——信息宣传工作。这不仅仅是一份职责,更是连接公司与外界的桥梁,通过新闻稿件传递我们的声音,展示我们的成果。然而,回忆起刚刚踏入这个领域的时光,那段经历至今让我感慨万千。 初涉投稿,步履维艰 刚接手这项工作时,我的投稿方式还停留在传统的邮箱投稿阶段。每天,我会精心准备每一篇稿件,字斟句酌,力求做好。但即便如此,投递出去的稿件却常常

误删短信怎么办?手机短信恢复难不难?

随着科技的发展,手机已经成为我们生活中不可或缺的一部分。我们通过手机进行通讯、娱乐、购物等各种活动,手机中存储了大量的个人信息和重要数据。然而,有时候我们可能会不小心删除了一些重要的短信,这时候我们该怎么办呢?手机短信恢复难不难?本文将为您解答这个问题。 手机短信恢复的重要性 在现代社会中,手机已经成为我们生活中不可或缺的一部分。它不仅仅是一个通讯工具,更是我们获取信息、处理工作、