关于Cococs中的CCActionEase
尊重作者劳动,转载时请标明文章出处。
作者:Bugs Bunny
地址:http://www.cnblogs.com/cocos2d-x/archive/2012/03/13/2393898.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]。
关于Cococs中的CCActionEase的更多相关文章
- 关于Cococs中的CCActionEase(中)
相比之前的速度正弦变化动作(这个东西叫什么更好一些?渐变动画?)与速度指数级变化动作,CCEaseIn/CCEaseOut/CCEaseInOut更具灵活性.你可以设置运动的速率,甚至是在运动的过程中 ...
- 关于Cococs中的CCActionEase(下)
我们前面介绍的动作主要是用来改变内部动作的执行速度,接下来要介绍的这几个动作主要是用来增加表现效果的,可以看作是简单的特效. 10)CCEaseBackIn 1 void CCEaseBackIn:: ...
- Python开源框架
info:更多Django信息url:https://www.oschina.net/p/djangodetail: Django 是 Python 编程语言驱动的一个开源模型-视图-控制器(MVC) ...
- Lua中调用C++方法
目前项目,使用了Lua脚本,至于使用Lua的好处不再赘述了.于是对Tolua做了一些小小的学习,总结一下吧. 主要说一下如何在Lua中调用C++方法. Lua调用C++的桥梁,是tolua.tolua ...
- mapreduce中一个map多个输入路径
package duogemap; import java.io.IOException; import java.util.ArrayList; import java.util.List; imp ...
- Hadoop 中利用 mapreduce 读写 mysql 数据
Hadoop 中利用 mapreduce 读写 mysql 数据 有时候我们在项目中会遇到输入结果集很大,但是输出结果很小,比如一些 pv.uv 数据,然后为了实时查询的需求,或者一些 OLAP ...
- Python中的多进程与多线程(一)
一.背景 最近在Azkaban的测试工作中,需要在测试环境下模拟线上的调度场景进行稳定性测试.故而重操python旧业,通过python编写脚本来构造类似线上的调度场景.在脚本编写过程中,碰到这样一个 ...
- .NET Core中的认证管理解析
.NET Core中的认证管理解析 0x00 问题来源 在新建.NET Core的Web项目时选择“使用个人用户账户”就可以创建一个带有用户和权限管理的项目,已经准备好了用户注册.登录等很多页面,也可 ...
- Angular杂谈系列1-如何在Angular2中使用jQuery及其插件
jQuery,让我们对dom的操作更加便捷.由于其易用性和可扩展性,jQuer也迅速风靡全球,各种插件也是目不暇接. 我相信很多人并不能直接远离jQuery去做前端,因为它太好用了,我们以前做的东西大 ...
随机推荐
- HBase MemStoreFlusher
HBase MemStore Flush由类org.apache.hadoop.hbase.regionserver.MemStoreFlusher实现,具体表现为HRegionServer中的一个实 ...
- Delphi实现AnsiString与WideString的转换函数 转
Delphi实现AnsiString与WideString的转换函数 分类: Delphi2013-01-26 16:23 460人阅读 评论(0) 收藏 举报 [delphi] view plain ...
- UVAlive3523 Knights of the Round Table(bcc)
题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=18122 [思路] 点-双连通分量 求出bcc,判断每个bcc是否为 ...
- python队列join
如果要让一个任务队列按照顺序进行,则必须使用join,代码如下: ''' Created on Dec 23, 2013 @author: long ''' import threading from ...
- 《算法问题实战策略》-chaper17-部分和
数组上的一个基本优化——部分和: 对于一定长度的数组,我们想不断访问这个数组上的某个区间的和,我们能够怎么做呢?这里先不去谈一些数据结构在这个问题上的优化处理.首先我们最简单的一个方法就是穷举出所有区 ...
- cURL.io - Share your files right from your terminal
cURL.io - Share your files right from your terminal cURL.io
- Java图像灰度化的实现过程解析
概要 本文主要介绍了灰度化的几种方法,以及如何使用Java实现灰度化.同时分析了网上一种常见却并不妥当的Java灰度化实现,以及证明了opencv的灰度化是使用“加权灰度化”法 24位彩色图与8位灰度 ...
- Java与.net的区别delegate和event
There is no delegate concept in Java The right-side C# program may be mimiced with reflection techno ...
- AFNetworking (3.1.0) 源码解析 <三>
今天要介绍的是Reachability文件夹下的AFNetworkReachabilityManager类.通过字面意思我们就可以知道AFNetworkReachabilityManager是用来监测 ...
- java图片处理工具类
直接上代码: package com.zxd.tool; /** * Created by zhang on 14-3-1. * 图片的常用操作类 */ import java.awt.AlphaCo ...