您的位置:首页 > 移动开发 > Cocos引擎

菜鸟学习Cocos2d-x 3.x——浅谈动作Action

2015-05-27 10:56 411 查看


动作类概述

一款游戏,设计的再NB的游戏,如果都是一堆静态的图片,没有任何动作,那也只能“呵呵”了。动作体系对于一款游戏的成功与否,有着非常重要的影响。所以,这篇文章就对Cocos2d-x中的动作进行总结。先来看看Cocos2d-x中的与动作相关的类。

与动作相关的类图如下图所示:

现在就对这些类进行简单的介绍,在后续的小节中还会进行详细的分析的。

Ref和Clonable:这里不说,在总结Cocos2d-x内存管理的时候再进行详细总结;
Action:所有动作的父类,定义了公共的操作;
FiniteTimeAction:瞬时动作和延时动作的父类,可以定义动作的时间变化;
Follow:跟随节点的动作;
Speed:改变一个动作的时间,比如实现慢动作回放或者快进;
ActionInstant:瞬间完成动作,中间没有任何动画效果;
ActionInterval:动作会在指定的时间内完成,中间会有动画效果;
FlipX:X轴方向翻转;
MoveTo:移动动作;
……

下面就对上面说的这些类进行通过实际的代码进行总结。


Action类的主要成员函数

以下是
Action
类的主要成员函数:
/**
* 返回一个新的Action对象,表示原动作的相反的动作
*/
virtual Action* reverse() const = 0;

// 如果动作已经完成了,就返回true
virtual bool isDone() const;

// 在动作开始之前被调用,设置动作作用的对象
virtual void startWithTarget(Node *target);

/**
* 在动作完成以后会被调用,它会设置"target"对象为空
* 注:请永远不要手动调用该函数,而是调用对应的"target->stopAction(action);"
*/
virtual void stop();

/**
* 每帧都会调用的方法,如果你需要在每帧控制动作,则需要重写,时间间隔为动作间隔时间
* 最好不要重写该函数,除非你真的知道怎么做
*/
virtual void step(float dt);

/**
* 每一帧都会调用一次该函数,参数time取值为0和1之间的任意值,例如:
* 0表示动作刚刚开始的时候调用;
* 0.5表示动作执行到一半的时候调用;
* 1表示动作完成以后调用;
*/
virtual void update(float time);

// 获得执行动作的对象
inline Node* getTarget() const { return _target; }

// 设置执行动作的对象
inline void setTarget(Node *target) { _target = target; }

// 动作标签
inline int getTag() const { return _tag; }
inline void setTag(int tag) { _tag = tag; }


瞬时动作

ActionInstant
是瞬时动作类。瞬时动作表示瞬间完成动作,中间没有任何动画效果;由于
ActionInstant
的子类那么多,这里就以
FlipX
为例子,做一个简单的Demo。效果如下图所示:

可以看到小狗走到最左边时,会有一个转向的过程,这就是
FlipX
的动作效果。示例代码如下:
Size visibleSize = Director::getInstance()->getVisibleSize();

// 创建移动动作
ActionInterval *moveto = MoveTo::create(5, Vec2(0, 200));

// 创建X轴方向的翻转动作
ActionInstant *flipx = FlipX::create(true);
ActionInterval *moveback = MoveTo::create(5, Vec2(visibleSize.width, 200));

auto scene = Director::getInstance()->getRunningScene();
auto layer = scene->getChildByTag(1);
auto dog = layer->getChildByTag(1);

// flipx->reverse()获得对应的逆向动作
auto action = Sequence::create(moveto, flipx, moveback, flipx->reverse(), NULL);
dog->runAction(action);


瞬时动作是只能够立刻完成的动作,这类动作是在下一帧立刻完成的动作,如设定位置、设定缩放等。把它们包装成动作后,可以与其他动作类组合为复杂动作。


延时动作

动作会在指定的时间内完成,中间会有动画效果。延时动作通过属性值的逐渐变化来实现动画效果。需要注意的是XXTo和XXBy的区别在于XXTo是表示最终值,而XXBy则表示向量-改变值。比如
MoveTo
MoveBy
动作,如以下效果所示:

示例代码如下:
// 创建移动动作
ActionInterval *moveto = MoveTo::create(5, Vec2(0, 200));
ActionInterval *moveby = MoveBy::create(5, Vec2(-200, 0));

auto scene = Director::getInstance()->getRunningScene();
auto layer = scene->getChildByTag(1);
auto dog1 = layer->getChildByTag(1);
auto dog2 = layer->getChildByTag(2);

dog1->runAction(moveto);
dog2->runAction(moveby);


上面的那只狗是使用的
MoveBy
动作,而下面这只狗使用的是
MoveTo
动作。
MoveTo
动作都在用,下面就来看看稍微复杂点的贝塞尔曲线动作。

使用贝塞尔曲线,可以使节点进行曲线运动。每条贝塞尔曲线都包含一个起点和一个终点。在一条曲线中,起点和终点各自包含一个控制点,而控制点到端点的连线称作控制线。控制点决定了曲线的形状,包含角度和长度两个参数。如下图:

实现效果如下:

示例代码如下:
ccBezierConfig bezier;
bezier.controlPoint_1 = Point(200, 300);
bezier.controlPoint_2 = Point(400, 400);
bezier.endPosition = Point(50, 200);
auto bezierAction = BezierTo::create(2.0f, bezier);

dog1->runAction(bezierAction);


我们在实际开发中,主要的任务就是确定两个控制点,去协调精灵的移动弧度。


缓冲动作

在游戏中,我们经常要实现一些加速度或者减速度的效果。Cocos2d-x已经为我们做好了。

ActionEase
类可以实现动作的速度由快到慢、速度随时间改变的匀速运动。该类包含5类运动:

指数缓冲;
Sine缓冲;
弹性缓冲;
跳跃缓冲;
回震缓冲。

每类运动都包含3个不同时期的变换:In、Out和InOut。

In表示开始的时候加速;
Out表示结束的时候加速;
InOut表示开始和结束的时候加速。

上述5类运动分别对应以下的类:

指数缓冲:
EaseExponentialIn
EaseExponentialOut
EaseExponentialInOut

Sine缓冲:
EaseSineIn
EaseSineOut
EaseSineInOut

弹性缓冲:
EaseElasticIn
EaseElasticOut
EaseElasticInOut

跳跃缓冲:
EaseBounceIn
EaseBounceOut
EaseBounceInOut

回震缓冲:
EaseBackIn
EaseBackOut
EaseBackInOut


通过图表和描述也不能形象的说明上述5中运动,下面就通过指数缓冲的实际运行效果来进行展示:

指数缓冲

示例代码如下:
ActionInterval *moveto1 = MoveTo::create(5, Vec2(50, 100));
ActionInterval *moveto2 = MoveTo::create(5, Vec2(50, 300));
ActionInterval *moveto3 = MoveTo::create(5, Vec2(50, 500));

auto scene = Director::getInstance()->getRunningScene();
auto layer = scene->getChildByTag(1);
auto dog1 = layer->getChildByTag(1);
auto dog2 = layer->getChildByTag(2);
auto dog3 = layer->getChildByTag(3);

auto action1 = EaseExponentialIn::create(moveto1);
auto action2 = EaseExponentialOut::create(moveto2);
auto action3 = EaseExponentialInOut::create(moveto3);

dog1->runAction(action1);
dog2->runAction(action2);
dog3->runAction(action3);


从下到上的三次小狗,分别对应的是
EaseExponentialIn
EaseExponentialOut
EaseExponentialInOut


EaseExponentialIn表现的效果为速度越来越快;
EaseExponentialOut表现的效果为速度越来越慢;
EaseExponentialInOut表现的效果为中间速度非常快,两头速度较慢。


组合动作

在游戏中,很多时候,一个对象并不是单纯的执行一个动作,而是依次执行一系列动作或者同时执行一系列动作,那么这又该如何去完成呢?现在就来看看Cocos2d-x中是如何去完成这些的吧。

Sequence 可以使用
Sequence
定义一个动作序列,应用实例可以参见瞬时动作这小节。
Spawn
Spawn
也是定义一系列动作,但是定义的动作会同时执行,使用方法同
Sequence
是一样的。下面就通过具体的效果和代码来看看。

可以看到,移动和淡入的效果在同时进行。示例代码如下:
ActionInterval *moveto1 = MoveTo::create(5, Vec2(50, 100));
ActionInterval *fadein = FadeIn::create(5);

auto scene = Director::getInstance()->getRunningScene();
auto layer = scene->getChildByTag(1);
auto dog1 = layer->getChildByTag(1);

auto spawnAction = Spawn::create(moveto1, fadein, NULL);

dog1->runAction(spawnAction);


Repeat和RepeatForever
Repeat
RepeatForever
两个类可以使动作重复执行,
Repeat
可以在参数中指定重复次数;
RepeatForever
则是一直重复执行。

从左往右,第一只小狗只跳一次;第二只小狗使用的
Repeat
类,定义的跳跃5次;第三只小狗使用的
RepeatForever
,将会一直跳跃下去。实例代码如下:
// 创建跳跃动作
ActionInterval *jumpTo1 = JumpBy::create(3, Vec2(0, 0), 100, 1);
ActionInterval *jumpTo2 = JumpBy::create(3, Vec2(0, 0), 100, 1);
ActionInterval *jumpTo3 = JumpBy::create(3, Vec2(0, 0), 100, 1);

auto scene = Director::getInstance()->getRunningScene();
auto layer = scene->getChildByTag(1);
auto dog1 = layer->getChildByTag(1);
auto dog2 = layer->getChildByTag(2);
auto dog3 = layer->getChildByTag(3);

// 重复跳动5次
auto repeatJump = Repeat::create((FiniteTimeAction *)jumpTo2, 5);

// 一直重复跳动动作
auto repeatForeverJump = RepeatForever::create((ActionInterval *)jumpTo3);

dog1->runAction(jumpTo1);
dog2->runAction(repeatJump);
dog3->runAction(repeatForeverJump);

在实际试验中,发现,
Repeat
RepeatForever
对于
JumpTo
,并不能得到我们预期的结果。后来在源代码中,发现
JumpTo
没有重写
Action
类的
Update
方法,导致了错误的结果,希望在下个版本中有更改吧。
通常在开发中我们需要将各种动作组合起来再让节点执行,复合动作的作用就是将各种动作组合在一起。而且,复合动作本身也是动作。因此可以作为一个普通动作嵌入到其他动作中。

注意:Sequence动作不能嵌入其他复合动作内使用,DelayTime不属于复合动作,但是只能在复合动作内使用。


跟随动作

跟随动作
Follow
是一个节点跟随另外一个节点的动作。我都不理解这个动作,此处不做过多的总结。以后明白了,再回来补上。


函数回调动作

有的时候,我们需要在动作完成以后,回调一个我们自定义的函数,完成某些功能,比如攻击一个敌人,攻击动作完成以后,敌人需要减少血量,这个时候,就需要使用函数回调动作。

CallFunc
系列动作包括
CallFunc
CallFuncN
__CCCallFuncND
,以及
__CCCallFuncO
四个动作,用来在动作中进行方法的调用(之所以不是函数调用,是因为它们只能调用某个类中的实例方法,而不能调用普通的C函数)。

当某个对象执行
CallFunc
系列动作时,就会调用一个先前被设置好的方法,以完成某些特别的功能。

CallFunc
系列动作的4个类中:

CallFunc
调用的方法不包含参数;
CallFuncN
调用的方法包含一个
Node*
类型的参数,表示执行动作的对象;
__CCCallFuncND
调用的方法包含两个参数,不仅有一个节点参数,还有一个自定义参数(
Node*
void*
);
__CCCallFuncO
调用的方法则只包含一个
Ref*
类型的参数;

实际上,
CallFunc
系列动作的后缀”N”表示Node参数,指的是执行动作的对象, “D”表示Data参数,指的是用户自定义的数据,”O”表示对象,指的是一个用户自定义的Ref参数。(
__CCCallFuncND
__CCCallFuncO
的命名好奇葩,可能在后续的版本中又要变了。)

在不同的情况下,我们可以根据不同的需求来选择不同的
CallFunc
动作。看看代码示例。
// 使用Lambda表达式
auto callFunc1 = CallFunc::create([&]{log("cocos2d-x 1"); });

// 使用成员函数
auto callFunc2 = CallFuncN::create(this, callfuncN_selector(HelloWorld::testFunc));

// 顺序执行所有动作
auto action = Sequence::create(moveTo, callFunc1, callFunc2, NULL);

dog1->runAction(action);


动作管理

动作管理类
ActionManager
是管理所有动作的一个单例类,当我们调用
runAction
函数时,该函数会把动作通过动作管理类的
addAction
函数将对象传递给
ActionManager
的单例,该实例再把这个动作添加到自己的动作序列中。

动作管理单例通过定时刷新自己的
update
方法,在这个方法中去调用行为序列中每个动作的
step
,这些
step
方法再根据自身的完成进度去
update
或是结束行为。

实际上是由动作管理单例驱动每个动作去更新自己的逻辑,而
runAction
方法只是将动作对象添加进
ActionManager
的待执行动作队列中,由
ActionManager
去管理所有动作。当节点被清除或是动作结束时,动作管理类会自动将动作从队列中删除,不需要人为的去管理。

后续分析具体的小游戏的时候,遇到了
ActionManager
,再做详细的分析。


总结

终于总结完了,很显然,这部分的内容远不止这么点。我这里只是把比较重要的几个部分进行了详细的总结;后续的博文中,遇到了再做更详细的总结。希望这篇稍微有点长的博文对大家有帮助。

这篇文章稍微有点长,写的稍微有点累,写的真心不容易。各位,有钱的捧个钱场,有人的捧个人场。

2014年11月24日 于深圳。

原文地址:http://www.jellythink.com/archives/749
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: