cocos2dx——自制动作。shake your body!

来源:互联网 发布:数据录入员招聘58同城 编辑:程序博客网 时间:2024/04/19 07:41

      如有错误,请联系我更正,以免误导他人!)

       cocos2dx中给我们封装好许许多多的动作,利用这些封装好的动作我们可以很容易地实现很多有趣的动作效果。但是,在实际开发中,cocos2dx中已经封装好的动作往往并不能满足我们的需求。这时候就需要我们自己来封装我们自己设计的动作。那么,要快速封装新的动作类,我们就必须了解cocos2dx中动作的原理。那么下面,我们就来简单了解下cocos2dx中的动作具体是怎么实现的。

首先,我们看到我们常用的动作类MoveByJumpBy。通过查看源码,break发现,他们都继承于同一个类:ActionInterval。而ActionInterval继承于FiniteTimeActionFiniteTimeAction继承于Actionn。其中Action是所有动作类的基类。

       ActionInterval其实是动作的一个大的分支,我们今天主要分析这个类型的动作的封装。在cocos2dx源码文档中对ActionInterval的描述如下:这是一个持续一段时间的动作。它有一个开始时间和一个结束时间,结束时间是开始时间+duration。其中durationFiniteTimeAction类的成员变量,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;elsenewpos.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坐标进行操作就行了。


       这样,我们就完成了一个简单的晃动动作的封装。我么你只需像使用MoveTo、MoveBy等动作一样,创建动作,再调用runAction函数即可实现一个动作效果了。
效果图如下(GIF显示得不是很流畅)


      完整代码下载:http://download.csdn.net/detail/qq_28290581/9609876

0 0
原创粉丝点击