知易游戏开发教程cocos2d-x移植版003
从本章开始,我们开始讲解cocos2d-x库的动作(Action)。游戏的世界是一个动态的世界:无论是主角精灵还是NPC精灵都处于不断的运动当中,甚至是背景中漂流的树叶,随风而动的小草。这些明显的或者不明显的运动构成了我们栩栩如生的游戏世界。
仔细研究游戏中精灵的运动,我们发现:所有这样的运动都可以细分为若干个基本动作和基本动作的组合。通过进一步扩展,我们可以将同一精灵的更多动作和不同精灵之间的不同动作连贯起来,形成关于整个运动世界的连续模拟。
我们给出示例ZYG003,展示cocos2d-x支持的主要动作:
基本动作
从技术上来说,基本动作的本质就是改变某个图形对象的属性:位置、角度、大小等。cocos2d-x提供超过20种基本动作供我们使用。根据改变完成所需的时间,可以分为瞬时动作和延时动作。其中,延时动作的执行速度又可以按照不同的方式来改变(位置、大小、颜色、闪烁……)。
再进一步介绍基本动作之前,我们先来简单明确一下动作是如何与CCNode关联起来的。CCNode有一个成员函数叫runAction,定义为:
1 /** Executes an action, and returns the action that is executed.
2 The node becomes the action's target.
3 @warning Starting from v0.8 actions don't retain their target anymore.
4 @since v0.7.1
5 @return An Action pointer
6 */
7 CCAction* runAction(CCAction* action);
此接口确保所有的精灵都可以执行各种动作。也正是为了服从这个接口的定义,导致后续各种组合动作也都从CCAction派生。
下面的代码是通常调用某个动作的方法:
1 CCSize s = CCDirector::sharedDirector()->getWinSize();
2 // 创建动作
3 CCActionInterval *actionTo = CCMoveTo::actionWithDuration(2.0f, ccp(s.width - 40.0f, s.height - 40.0f));
4 // 使用动作(tamara是一个CCSprite指针)
5 tamara->runAction(actionTo);
瞬时动作
顾名思义,瞬时动作就是不需要时间,马上就完成的动作。瞬时动作的共同基类是CCActionInstant。
cocos2d-x提供以下瞬时动作:
瞬时动作大都有与之对应的属性设置方法,之所以作为一个动作来实现,是为了可以与其他动作形成一个连续动作。下面来看一下瞬时动作的使用。
放置 - CCPlace
效果类似于setPosition(ccp(x, y))。示例代码如下:
1 void InstantActionLayer::onPlace(CCObject* pSender)
2 {
3 CCSize s = CCDirector::sharedDirector()->getWinSize();
4 // 理论上使用伪随机数前应该srand一下,但是知易的例子中没有,不知道是不是故意这么设计的。
5 // 如果使用time(NULL)初始化伪随机数种子,要注意time_t有可能是64位的!
6 CCPoint p = ccp(CCRANDOM_0_1() * s.width, CCRANDOM_0_1() * s.height);
7 flight->runAction(CCPlace::actionWithPosition(p));
8 }
隐藏 - CCHide
效果类似于setIsVisible(false)。示例代码如下:
1 void InstantActionLayer::onHide(CCObject* pSender)
2 {
3 flight->runAction(CCHide::action());
4 }
显示 - CCShow
效果类似于setIsVisible(true)。示例代码如下:
1 void InstantActionLayer::onShow(CCObject* pSender)
2 {
3 flight->runAction(CCShow::action());
4 }
可见切换 - CCToggleVisibility
效果类似于setIsVisible(!getIsVisible())。示例代码如下:
1 void InstantActionLayer::onToggle(CCObject* pSender)
2 {
3 flight->runAction(CCToggleVisibility::action());
4 }
水平翻转 - CCFlipX
效果类似于setFlipX(true/false)。示例代码如下:
1 void InstantActionLayer::onFlipX(CCObject* pSender)
2 {
3 flight->runAction(CCFlipX::actionWithFlipX(true));
4 }
垂直翻转 - onFlipY
效果类似于setFlipY(true/false)。示例代码如下:
1 void InstantActionLayer::onFlipY(CCObject* pSender)
2 {
3 flight->runAction(CCFlipY::actionWithFlipY(true));
4 }
还有两个较为特殊的动作(网格重用 - CCReuseGrid|停止网格 - CCStopGrid),我们以后介绍。
延时动作
延时动作就是指动作的完成需要一段时间。因此,几乎所有的延时动作都使用执行时间作为第一个参数,它们有着共同的基类CCActionInterval。
cocos2d-x中常用的延时动作:
这里有一个简单的类命名规则:
CCXxxxTo - 绝对动作,执行的结果与当前的状态关系不密切;
CCXxxxBy - 相对动作,在当前的状态上执行某种动作,执行的结果与当前状态是紧密相关的。
移动到 - CCMoveTo
移动 - CCMoveBy
跳跃到 - CCJumpTo
参数为终点位置、跳跃高度和跳跃次数。
跳跃 - CCJumpBy
贝赛尔曲线 - CCBezierBy
支持三次贝赛尔曲线:P0-起点,P1-起点切线方向,P2-终点切线方向,P3-终点。
首先设置贝塞尔参数,然后执行。
放大到 - CCScaleTo
放大 - CCScaleBy
如果参数为小数,那就是缩小了。
旋转到 - CCRotateTo
旋转 - CCRotateBy
闪烁 - CCBlink
色调变化到 - CCTintTo
色调变换 - CCTintBy
变暗到 - CCFadeTo
由无变亮 - CCFadeIn
由亮变无 - CCFadeOut
组合动作
按照一定的次序将上述基本动作组合起来,形成连贯的一套组合动作。组合动作包括以下几类:
序列 - CCSequence
序列的使用非常简单,该类从CCActionInterval派生,本身就可以被CCNode对象执行。该类的作用就是线性排列若干个动作,然后按先后次序逐个执行。
1 void CompositionActionLayer::onSequence(CCObject* pSender)
2 {
3 CCSize s = CCDirector::sharedDirector()->getWinSize();
4 // 创建5个动作
5 CCFiniteTimeAction *action0 = CCPlace::actionWithPosition(ccp(s.width / 2, 50));
6 CCFiniteTimeAction *action1 = CCMoveTo::actionWithDuration(1.2f, ccp(s.width - 50.0f, s.height - 50.0f));
7 CCFiniteTimeAction *action2 = CCJumpTo::actionWithDuration(1.2f, ccp(150, 50), 30.0f, 5);
8 CCFiniteTimeAction *action3 = CCBlink::actionWithDuration(1.2f, 3);
9 CCFiniteTimeAction *action4 = CCTintBy::actionWithDuration(0.5f, 0, 255, 255);
10 // 将5个动作组合为一个序列,注意不要忘了用NULL结尾。
11 flight->runAction(CCSequence::actions(action0, action1, action2, action3, action4, action0, NULL));
12 }
同步 - CCSpawn
同步的使用非常简单,该类也是从CCActionInterval派生,可以被CCNode对象执行。该类的作用就是同时并列执行若干个动作,但要求动作本身是可以同时执行的,比如:移动式翻转、变色、缩放等。
需要特别注意的是,同步执行最后完成的时间由基本动作中用时最大者决定。
1 void CompositionActionLayer::onSpawn(CCObject* pSender)
2 {
3 CCSize s = CCDirector::sharedDirector()->getWinSize();
4 flight->setRotation(0.0f);
5 flight->setPosition(ccp(s.width / 2, 50));
6 // 创建4个需要并行的动作,确保动作用时可组合。(action1/action2/sequence的执行时间都是2秒)
7 CCFiniteTimeAction *action1 = CCMoveTo::actionWithDuration(2.0f, ccp(s.width - 50.0f, s.height - 50.0f));
8 CCFiniteTimeAction *action2 = CCRotateTo::actionWithDuration(2.0f, 180.0f);
9 CCFiniteTimeAction *action3 = CCScaleTo::actionWithDuration(1.0f, 4.0f);
10 CCFiniteTimeAction *action4 = CCScaleBy::actionWithDuration(1.0f, 0.5f);
11 CCFiniteTimeAction *sequence = CCSequence::actions(action3, action4, NULL);
12 // 创建并执行同步动作。
13 flight->runAction(CCSpawn::actions(action1, action2, sequence, NULL));
14 }
重复有限次数 - CCRepeat
CCRepeat用来将某一动作重复有限次数,示例代码如下:
1 void CompositionActionLayer::onRepeat(CCObject* pSender)
2 {
3 CCSize s = CCDirector::sharedDirector()->getWinSize();
4 flight->setRotation(0.0f);
5 flight->setPosition(ccp(s.width / 2, 50));
6 // 创建动作序列
7 CCFiniteTimeAction *action1 = CCMoveTo::actionWithDuration(2.0f, ccp(s.width - 50.0f, s.height - 50.0f));
8 CCFiniteTimeAction *action2 = CCJumpBy::actionWithDuration(2.0f, ccp(-400, -200), 30.0f, 5);
9 CCFiniteTimeAction *action3 = CCJumpBy::actionWithDuration(2.0f, ccp(s.width / 2, 0), 20.0f, 3);
10 CCFiniteTimeAction *sequence = CCSequence::actions(action1, action2, action3, NULL);
11 // 重复运行上述动作序列3次
12 flight->runAction(CCRepeat::actionWithAction(sequence, 3));
13 }
反动作 - Reverse
反动作就是反向(逆向)执行某个动作,支持针对动作序列的反动作序列。反动作不是一个专门的类,而是CCFiniteTimeAction引入的一个接口。不是所有的类都支持反动作,CCXxxxTo类通常不支持反动作,而CCXxxxBy类通常支持,示例如下:
1 void CompositionActionLayer::onReverse(CCObject* pSender)
2 {
3 CCSize s = CCDirector::sharedDirector()->getWinSize();
4 flight->setRotation(0.0f);
5 flight->setPosition(ccp(s.width / 2, 50));
6
7 CCFiniteTimeAction *action1 = CCMoveBy::actionWithDuration(2.0f, ccp(190, 220));
8 // 创建某个动作的反动作
9 CCFiniteTimeAction *action2 = action1->reverse();
10
11 flight->runAction(CCRepeat::actionWithAction(CCSequence::actions(action1, action2, NULL), 2));
12 }
动画 - CCAnimate
动画就是让精灵自身连续执行一段影像,形成模拟运动的效果:行走时的状态,打斗时的状态等。
1 void CompositionActionLayer::onAnimation(CCObject* pSender)
2 {
3 CCSpriteBatchNode *mgr = static_cast<CCSpriteBatchNode *>(this->getChildByTag(4));
4
5 CCAnimation *animation = CCAnimation::animation();
6 animation->setName("flight");
7 animation->setDelay(0.2f);
8 for (int i = 0; i < 3; ++i)
9 {
10 // 定义每一帧的内容
11 float x = static_cast<float>(i % 3);
12 animation->addFrameWithTexture(mgr->getTexture(), CCRectMake(x * 32, 0, 31, 30));
13 }
14 // 创建并执行动画效果,而且要重复10次。
15 CCAnimate *action = CCAnimate::actionWithAnimation(animation);
16 flight->runAction(CCRepeat::actionWithAction(action, 10));
17 }
无限重复 - CCRepeatForever
在CCRepeatForever Class Reference中,有这样一条警告“Warning: This action can't be Sequenceable because it is not an IntervalAction”,而实际上它的确是派生自CCActionInterval,这真有点儿把我也搞懵了。
仅从其本意来说,该类的作用就是无限期执行某个动作或动作序列,直到被停止。因此无法参与序列和同步,自身也无法反向执行(但是你可以将某一动作反向,然后无限重复执行)。
1 void CompositionActionLayer::onRepeatForever(CCObject* pSender)
2 {
3 CCSpriteBatchNode *mgr = static_cast<CCSpriteBatchNode *>(this->getChildByTag(4));
4 // 飞行喷火模拟动画
5 CCAnimation *animation = CCAnimation::animation();
6 animation->setName("flight");
7 animation->setDelay(0.1f);
8 for (int i = 0; i < 3; ++i)
9 {
10 float x = static_cast<float>(i % 3);
11 animation->addFrameWithTexture(mgr->getTexture(), CCRectMake(x * 32, 0, 31, 30));
12 }
13 CCAnimate *action = CCAnimate::actionWithAnimation(animation);
14 // 将该动画作为精灵的本征动画,一直运行。
15 flight->runAction(CCRepeatForever::actionWithAction(action));
16
17 CCSize s = CCDirector::sharedDirector()->getWinSize();
18 flight->setRotation(0.0f);
19 flight->setPosition(ccp(100, 50));
20
21 // 创建第二个连续无限期动作序列,叠加两者形成完整效果。
22 ccBezierConfig bezier;
23 bezier.controlPoint_1 = ccp(0, s.height / 2);
24 bezier.controlPoint_2 = ccp(300, -s.height / 2);
25 bezier.endPosition = ccp(300, 100);
26 CCFiniteTimeAction *action1 = CCBezierBy::actionWithDuration(3.0f, bezier);
27 CCFiniteTimeAction *action2 = CCTintBy::actionWithDuration(0.5f, 0, 255, 255);
28 CCFiniteTimeAction *action3 = CCSpawn::actions(action1, CCRepeat::actionWithAction(action2, 4), NULL);
29 CCFiniteTimeAction *action4 = CCSpawn::actions(action1->reverse(), CCRepeat::actionWithAction(action2, 4), NULL);
30 // CCSequence的actions成员函数返回的是CCFiniteTimeAction指针类型,
31 // 而CCRepeatForever的actionWithAction接受的是CCActionInterval指针类型,
32 // 所以这里需要强转一下,转成CCSequence指针类型,
33 // 只要保证序列中有2个或2个以上的动作,这么做是绝对没有问题的。
34 flight->runAction(CCRepeatForever::actionWithAction(static_cast<CCSequence *>(CCSequence::actions(action3, action4, NULL))));
35 }
速度变化
基本动作和组合动作实现了针对精灵的各种运动和动画效果,但它们的速度通常是恒定不变的。通过CCActionEase类系和CCSpeed类,我们可以很方便地改变精灵执行动作的速度,是由快至慢还是由慢至快。
CCEaseIn - 由慢至快(速度线性变化)
CCEaseOut - 由快至慢
CCEaseInOut - 由慢至快再由快至慢
CCEaseSineIn - 由慢至快(速度正弦变化)
CCEaseSineOut - 由快至慢
CCEaseSineInOut - 由慢至快再由快至慢
CCEaseExponentialIn - 由慢至极快(速度指数级变化)
CCEaseExponentialOut - 由极快至慢
CCEaseExponentialInOut - 由慢至极快再由极快至慢
CCSpeed - 人工设定速度,还可通过setSpeed不断调整。
扩展动作
我们已经掌握了各种各样的动作,也可以按照不同的速度要求修改动作执行的时间,cocos2d-x还提供了针对现有动作的扩展,以实现各种灵活的效果。
延时 - CCDelayTime
通过CCDelayTime,我们可以在动作序列中增加一个时间间歇。
1 void ExtendActionLayer::onDelay(CCObject* pSender)
2 {
3 CCFiniteTimeAction *action1 = CCMoveBy::actionWithDuration(1.2f, ccp(200, 200));
4 CCFiniteTimeAction *action2 = action1->reverse();
5 // 实现一个等待间歇
6 flight->runAction(CCSequence::actions(action1, CCDelayTime::actionWithDuration(1.0f), action2, NULL));
7 }
函数调用
在动作序列中间或者末尾调用某个函数,执行任何任务:动作、状态修改等。在cocos2d-x中,调用函数的动作一共有4种。
1.CCCallFunc
仅函数调用,无任何参数。
1 void ExtendActionLayer::onCallBack1()
2 {
3 flight->runAction(CCTintBy::actionWithDuration(0.5f, 255, 0, 255));
4 }
5
6 void ExtendActionLayer::onCallFunc(CCObject* pSender)
7 {
8 CCFiniteTimeAction *action1 = CCMoveBy::actionWithDuration(2.0f, ccp(200, 200));
9 CCFiniteTimeAction *action2 = action1->reverse();
10 CCFiniteTimeAction *actionF = CCCallFunc::actionWithTarget(this, callfunc_selector(ExtendActionLayer::onCallBack1));
11 flight->runAction(CCSequence::actions(action1, actionF, action2, NULL));
12 }
2.CCCallFuncN
调用函数,并将当前对象的指针(CCNode指针)作为第一个参数传递进去。
1 void ExtendActionLayer::onCallBack2(CCNode* pSender)
2 {
3 // 在这个例子里,pSender就是flight,因为是他执行了那个actionF
4 pSender->runAction(CCTintBy::actionWithDuration(1.0f, 255, 0, 255));
5 }
6
7 void ExtendActionLayer::onCallFuncN(CCObject* pSender)
8 {
9 CCFiniteTimeAction *action1 = CCMoveBy::actionWithDuration(2.0f, ccp(200, 200));
10 CCFiniteTimeAction *action2 = action1->reverse();
11 CCFiniteTimeAction *actionF = CCCallFuncN::actionWithTarget(this, callfuncN_selector(ExtendActionLayer::onCallBack2));
12 flight->runAction(CCSequence::actions(action1, actionF, action2, NULL));
13 }
3.CCCallFuncND
在前一种方式的基础上增加一个数据参数,这是void指针类型。
1 void ExtendActionLayer::onCallBack3(CCNode* pSender, void* pData)
2 {
3 pSender->runAction(CCTintBy::actionWithDuration(static_cast<float>((int)pData), 255, 0, 255));
4 }
5
6 void ExtendActionLayer::onCallFuncND(CCObject* pSender)
7 {
8 CCFiniteTimeAction *action1 = CCMoveBy::actionWithDuration(2.0f, ccp(200, 200));
9 CCFiniteTimeAction *action2 = action1->reverse();
10 // 这里直接将整数常量强转成(void *)类型似乎有欠妥当,但对这些Action的生命周期不太清楚,稍后深入一下。
11 CCFiniteTimeAction *actionF = CCCallFuncND::actionWithTarget(this, callfuncND_selector(ExtendActionLayer::onCallBack3), (void *)2);
12 flight->runAction(CCSequence::actions(action1, actionF, action2, NULL));
13 }
4.CCCallFuncO
调用函数,并传递一个CCObject指针作为参数。这个似乎不太常用,资料比较少,以后再深入。
小结
至此,我们对cocos2d-x支持的动作有了整体了解。动作是我们的好帮手,它让游戏世界充满生机。在后面的章节中,我们会对部分动作以及动作系统继续深入。