(如有错误,请联系我更正,以免误导他人!)
cocos2dx中给我们封装好许许多多的动作,利用这些封装好的动作我们可以很容易地实现很多有趣的动作效果。但是,在实际开发中,cocos2dx中已经封装好的动作往往并不能满足我们的需求。这时候就需要我们自己来封装我们自己设计的动作。那么,要快速封装新的动作类,我们就必须了解cocos2dx中动作的原理。那么下面,我们就来简单了解下cocos2dx中的动作具体是怎么实现的。
首先,我们看到我们常用的动作类MoveBy、JumpBy。通过查看源码,break发现,他们都继承于同一个类:ActionInterval。而ActionInterval继承于FiniteTimeAction,FiniteTimeAction继承于Actionn。其中Action是所有动作类的基类。
ActionInterval其实是动作的一个大的分支,我们今天主要分析这个类型的动作的封装。在cocos2dx源码文档中对ActionInterval的描述如下:这是一个持续一段时间的动作。它有一个开始时间和一个结束时间,结束时间是开始时间+duration。其中duration是FiniteTimeAction类的成员变量,FiniteTimeAction类的作用就是来维护这个变量。
在封装动作类之前,我们还要了解下ActionManager。这是动作管理类,用来管理动作、执行动作等。我们知道,要实现一个动作效果,我们首先要创建一个动作,然后调用runAction函数来执行动作。那么runAction到底是怎么执行动作的呢?
runAction其实只有一个操作,把动作添加到动作管理器里面(ActionManger对象),动作的执行还是要经过动作管理器的操作。
当一个动作添加入动作管理器时,会马上调用动作的startWithTarget把动作与执行动作的对象进行绑定,在动作与对象绑定的同时,我们也可以对动作进一步的初始化。
在默认情况下(没改变动作管理器时,默认使用导演的动作管理器),“导演”(cocos2dx中由导演执行游戏的 大部分行为(刷新画面、初始化、结束游戏等),导演实则为一个单例)每一桢都调用动作管理器的update函数。
动作管理器的update函数遍历添加入管理器内的动作,判断动作是否需要执行,需要执行则调用动作的step函数。
在ActionInterval动作中,step负责计算动作的执行程度。计算结果是一个浮点值,它的范围为0~1。0代表刚动作刚开始,1代表动作执行完,以此类推0.5代表动作执行一半。然后,计算结果作为参数调用动作的update函数。(step函数一般不需要我们重写。)
动作的update函数就是动作的具体执行算法。
由上面的分析可知,我们要封装一个简单的ActionInterval类型的动作,只需要重写它的update、startWithDuration方法,再实现自己的初始化函数即可。下面,我们就一边上代码,一边更具体的讲解。
我们来封装一个晃动的动作,使元素在执行时间duration内(水平\竖直)晃动_shaketime次,每次晃动的距离为
_shakewidth。
<span style="font-size:18px;">class LShakeAction:public cocos2d::ActionInterval
{
private:
int _shaketime;//晃动次数
int _shakewidth;//晃动距离
cocos2d::Vec2 _startpos;//起始点(执行对象的初始位置)
ShakeDirection _shaked;//晃动方向
};</span>
其中ShakeDirection定义如下:
<span style="font-size:18px;">enum ShakeDirection {
HORIZONTAL,//水平方向
VERTICAL//竖直方向
};</span>
下面是成员函数:
<span style="font-size:18px;">public:
static LShakeAction* create(float duration,int shaketime,int shakewidth, ShakeDirection shaked=ShakeDirection::HORIZONTAL);
virtual LShakeAction* clone() const override;
virtual void startWithTarget(cocos2d::Node* target) override;
virtual void update(float time) override;
public:
LShakeAction();
virtual ~LShakeAction();
private:
bool initWithDuration(float duration, int shaketime, int shakewidth, ShakeDirection shaked);
CC_DISALLOW_COPY_AND_ASSIGN(LShakeAction);//禁用拷贝构造与=运算符</span>
create函数定义:
<span style="font-size:18px;">LShakeAction * LShakeAction::create(float duration, int shaketime, int shakewidth, ShakeDirection shaked)
{
auto action = new(std::nothrow) LShakeAction;
if (action&&action->initWithDuration(duration, shaketime, shakewidth, shaked))
{
action->autorelease();
return action;
}
return nullptr;
}</span>
这个函数相信小伙伴们都很熟悉了,创建对象,调用对象初始化函数,把对象加入自动回收池(详情见《cocos2dx内存管理》:http://blog.csdn.net/qq_28290581/article/details/52201752)。由create函数的声明可以看到,默认的晃动方向是HORIZONTAL(水平方向)。
initWithDuration函数定义:
<span style="font-size:18px;">bool LShakeAction::initWithDuration(float duration, int shaketime, int shakewidth, ShakeDirection shaked)
{
if (shaketime <= 0)
return false;
if (!ActionInterval::initWithDuration(duration) || shaketime <= 0)
return false;
_shaketime = shaketime;
_shakewidth = shakewidth;
_shaked = shaked;
return true;
}</span>
调用父类的initWithDuration初始化FiniteTimeAction中的_duration(动作执行时间)成员变量。然后给成员变量_shaketime、_shakewidth、_shaked赋值。
clone函数:
<span style="font-size:18px;">LShakeAction * LShakeAction::clone() const
{
return LShakeAction::create(_duration, _shaketime, _shakewidth, _shaked);
}</span>
克隆动作。
startWithTarget函数:
<span style="font-size:18px;">void LShakeAction::startWithTarget(cocos2d::Node * target)
{
ActionInterval::startWithTarget(target);
_startpos = target->getPosition();
}</span>
调用父类的startWithTarget并记录动作执行对象的初始位置。
update函数定义(主角来了):
<span style="font-size:18px;">void LShakeAction::update(float time)
{
if (_target) {
float frac = fmodf((time*_shaketime), 1.0f);
float movedistance = moveSign(frac)*fmodf(frac, 0.25f)*_shakewidth;
Vec2 newpos = _startpos;
if (_shaked == ShakeDirection::HORIZONTAL)
newpos.x += movedistance;
else
newpos.y += movedistance;
_target->setPosition(newpos);
}
}</span>
由前面的分析知,参数time是由step函数计算并传递的,它代表动作执行的程度。time*shaketime的整数部分+1就是当前对象正在进行的晃动的次数,小数部分就是这次晃动执行的具体到的程度。上个简图:假设当前晃动3次。
由此可知,frac的值就是当前晃动的执行程度。
假设晃动是水平晃动,当shakewidth为正数时,物体先往右边移动(负数则相反),当移动到startpos.x(物体初始位置的x坐标)+shakewidth时改变移动方向,当物体移动到startpos.x-shakewidth时又改变方向,最后,当物体回到初始位置时,则完成一次晃动。所以,我们可以把物体的整个晃动分解成4段:
第一段:从startpos.x移动到shartpos.x+shakewidth(初始方向)
第二段:从shartpos.x+shakewidth移动到startpos.x(移动方向与初始方向相反)
第三段:从startpos.x移动到startpos.x-shakewidth(移动方向与初始方向相反)
第四段:从startpos.x-shakewidth移动到startpos.x。(移动方向与初始方向相同)
movedistance即是当前调用update后物体需要到移动的位置距初始位置的距离。其中fmodf(frac, 0.25f)的计算结果与frac的计算结果类似,把当前晃动拆分为4段,并得出当前段的执行程度。fmodf(frac, 0.25f)*_shakewidth就得出了movedistance的绝对值。再通过对移动方向的判断(乘以moveSign(frac)(移动方向与初始方向相同为正,移动方向与初始方向相反为负)得出movedistance,其中moveSign函数定义如下:
static int moveSign(float frac) {
int res = frac / 0.25f;
switch (res)
{
case 0:case 3:case 4:return 1;
case 1:case 2:return -1;
}
return 0;
}
计算好movedistance之后,我们只需要判断shaked为HORIZONTAL(水平)或者为VERTICAL来判断是对物体的x坐标还是y坐标进行操作就行了。
完整代码下载:http://download.csdn.net/detail/qq_28290581/9609876