Cocos2d-x为我们提供了一套动作的复合机制,允许我们组合各种基本动作,产生更为复杂和生动的动作效果。复合动作是一类特殊的动作,因此它也需要使用CCNode
的runAction
方法执行。而它的特殊之处在于,作为动作容器,复合动作可以把许多动作组合成一个复杂的动作。因此,我们通常会使用一个或多个动作来创建复合动作,再把动作交给节点执行。
复合动作十分灵活,这是由于复合动作本身也是动作,因此也可以作为一个普通的动作嵌套在其他复合动作中。
1. 重复(CCRepeat
/CCRepeatForever
)
有的情况下,动作只需要执行一次即可,但我们还常常遇到一个动作反复执行的情况。对于一些重复的动作,如鱼的摆动、能量槽的转动,我们可以通过CCRepeat
与CCRepeatForever
这两个方式重复执行:
CCRepeat* CCRepeat::create(CCFiniteTimeAction *pAction, unsigned int times);
CCRepeatForever *CCRepeatForever::create(CCActionInterval *pAction);
在上述代码中,pAction
参数表示需要重复的动作,第一个方法允许指定动作的重复次数,第二个方法使节点一直重复该动作直到动作被停止。
2. 并列(CCSpawn
)
指的是使一批动作同时执行。在《捕鱼达人》游戏中,鱼一边沿曲线游动一边摆尾巴,炮弹一边发射一边喷射气体,金币一边旋转一边移动等动作,都可以通过CCSpawn
来实现。CCSpawn
从CCActionInterval
派生而来的,它提供了两个工厂方法:
CCSpawn::create(CCFiniteTimeAction *pAction1,...);
CCSpawn::create(CCFiniteTimeAction *pAction1, CCFiniteTimeAction *pAction2);
其中第一个静态方法可以将多个动作同时并列执行,参数表中最后一个动作后需要紧跟NULL
表示结束。第二个则只能指定两个动作复合,不需要在最后一个动作后紧跟NULL
。此外,执行的动作必须是能够同时执行的、继承自CCFiniteTimeAction
的动作。组合后,CCSpawn
动作的最终完成时间由其成员中最大执行时间的动作来决定。
3. 序列(CCSequence
)
除了让动作同时并列执行,我们更常遇到的情况是顺序执行一系列动作。CCSequence
提供了一个动作队列,它会顺序执行一系列动作,例如鱼游出屏幕外后需要调用回调函数,捕到鱼后显示金币数量,经过一段时间再让金币数量消失,等等。
CCSequence
同样派生自CCActionInterval
。与CCSpawn
一样,CCSquence
也提供了两个工厂方法:
CCSequence::create(CCFiniteTimeAction *pAction1,...);
CCSequence::create(CCFiniteTimeAction *pAction1,CCFiniteTimeAction *pAction2);
它们的作用分别是建立多个和两个动作的顺序执行的动作序列。同样要注意复合动作的使用条件,部分的非延时动作(如CCRepeatForever
)并不被支持。
在实现CCSequence
和CCSpawn
两个组合动作类时,有一个非常有趣的细节:成员变量中并没有定义一个可变长的容器来容纳每一个动作系列,而是定义了m_pOne
和m_pTwo
两个动作成员变量。如果我们创建了两个动作的组合,那么m_pOne
与m_pTwo
就分别是这两个动作本身;当我们创建更多动作的组合时,引擎会把动作分解为两部分来看待,其中后一部分只包含最后一个动作,而前一部分包含它之前的所有动作,引擎把m_pTwo
设置为后一部分的动作,把m_pOne
设置为其余所有动作的组合。例如,语句sequence = CCSequence::create(action1, action2, action3, action4, NULL);
就等价于:
CCSequence s1 = CCSequence::createWithTwoActions(action1, action2);
CCSequence s2 = CCSequence::createWithTwoActions(s1, action3);
sequence = CCSequence::createWithTwoActions(s2, action4);
CCSpawn
与CCSequence
所采用的机制类似,在此就不再赘述了。采用这种递归的方式,而不是直接使用容器来定义组合动作,实际上为编程带来了极大的便利。维护多个动作的组合是一个复杂的问题,现在我们只需要考虑两个动作组合的情况就可以了。下面是CCSpawn
的一个初始化方法,就是利用递归的思想简化了编程的复杂度:
CCFiniteTimeAction* CCSpawn::create(CCArray *arrayOfActions)
{
CCFiniteTimeAction* prev = (CCFiniteTimeAction*)arrayOfActions->objectAtIndex(0);
for (unsigned int i = 1; i < arrayOfActions->count(); ++i)
{
prev = create(prev, (CCFiniteTimeAction*)arrayOfActions->objectAtIndex(i));
}
return prev;
}
众所周知,递归往往会牺牲一些效率,但能换来代码的简洁。在这两个复合动作中,细节处理得十分优雅,所有的操作都只需要针对两个动作实施,多个动作的组合会被自动变换为递归实现:
void CCSpawn::update(float time)
{
if (m_pOne)
{
m_pOne->update(time);
}
if (m_pTwo)
{
m_pTwo->update(time);
}
}
CCActionInterval* CCSpawn::reverse(void)
{
return CCSpawn::create(m_pOne->reverse(), m_pTwo->reverse());
}
4. 延时(CCDelayTime
)
CCDelayTime
是一个“什么都不做”的动作,类似于音乐中的休止符,用来表示动作序列里一段空白期,通过占位的方式将不同的动作段串接在一起。实际上,这与一个定时期实现的延迟没有区别,但相比之下,使用CCDelayTime
动作来延时就可以方便地利用动作序列把一套动作连接在一起。CCDelayTime
只提供了一个工程方法,如下所示:
CCDelayTime::create(float d);
其中仅包含一个实型参数,表示动作占用的时间。
针对位置(position
)这一属性,引擎为我们提供了3种位置变化动作类型,下面将简要介绍这几种动作。
-
CCMoveTo
和CCMoveBy
:用于使节点做直线运动。设置了动作时间和终点位置后,节点就会在规定时间内,从当前位置直线移动到设置的终点位置。它们的初始化方法分别为:CCMoveTo::create(ccTime duration, CCPoint& pos); CCMoveBy::create(ccTime duration, CCPoint& pos);
其中,
duration
参数表示动作持续的时间,pos
参数表示移动的终点或距离。对于CCMoveTo
,节点会被移动到pos
对应的位置;对于CCMoveBy
,节点会相对之前的位置移动pos
的距离。 -
CCJumpTo
和CCJumpBy
:使节点以一定的轨迹跳跃到指定位置。它们的初始化方法如下:CCJumpTo::create(ccTime duration, CCPoint pos, float height, int jumps); CCJumpBy::create(ccTime duration, CCPoint pos, float height, int jumps);
其中
pos
表示跳跃的终点或距离,height
表示最大高度,jumps
表示跳跃次数。 -
CCBezierTo
和CCBezierBy
:使节点进行曲线运动,运动的轨迹由贝塞尔曲线描述。贝塞尔曲线是描述任意曲线的有力工具,在许多软件(如Adobe Photoshop)中,钢笔工具就是贝塞尔曲线的应用。实际上,在《捕鱼达人》游戏中,为了控制鱼的游动,我们就用到了贝塞尔曲线。
每一条贝塞尔曲线都包含一个起点和一个终点。在一条曲线中,起点和终点都各自包含一个控制点,而控制点到端点的连线称作控制线。控制线决定了从端点发出的曲线的形状,包含角度和长度两个参数:角度决定了它所控制的曲线的方向,即这段曲线在这一控制点的切线方向;长度控制曲线的曲率。控制线越长,它所控制的曲线离控制线越近。示例图如图4-1所示。
图4-1 三段贝塞尔曲线
任意一段曲线都可以由一段或几段相连的贝塞尔曲线组成,因此我们只需考虑一段贝塞尔曲线应该如何描述即可。一段独立的贝塞尔曲线如图4-2所示。
图4-2 贝塞尔曲线及其控制点
使用时我们要先创建ccBezierConfig
结构体,设置好终点endPosition
以及两个控制点controlPoint_1
和controlPoint_2
后,再把结构体传入CCBezierTo
或CCBezierBy
的初始化方法中:
ccBezierConfig bezier;
bezier.controlPoint_1 = ccp(20, 150);
bezier.controlPoint_2 = ccp(200, 30);
bezier.endPosition = ccp(160, 30);
CFiniteTimeAction * beizerAction = CCBezierTo::create(actualDuration / 4, bezier);
另一类动作是属性变化动作,它的特点是通过属性值的逐渐变化来实现动画效果。例如,下面要介绍的第一个动作CCScaleTo
,它会在一段时间内不断地改变游戏元素的scale
属性,使属性值平滑地变化到一个新值,从而使游戏元素产生缩放的动画效果。
-
CCScaleTo
和CCScaleBy
:产生缩放效果,使节点的缩放系数随时间线性变化,对应的初始化方法为:CCScaleTo::create(ccTime duration, float s); CCScaleBy::create(ccTime duration, float s);
其中,
s
为缩放系数的最终值或变化量。 -
CCRotateTo
和CCRotateBy
:产生旋转效果,对应的初始化方法为:CCRotateTo::create(ccTime duration, float fDeltaAngle); CCRotateBy::create(ccTime duration, float fDeltaAngle);
其中
fDeltaAngle
的单位是角度,正方向为顺时针方向。 -
CCFadeIn
和CCFadeOut
:产生淡入淡出效果,其中前者实现了淡入效果,后者实现了淡出效果,对应的初始化方法为:CCFadeIn::create(ccTime duration); CCFadeOut::create(ccTime duration);
这里需要说明的是,只有实现了
CCRGBAProtocol
接口的节点才可以执行这类动作,这是因为与透明度或颜色相关的属性都继承自CCRGBAProtocol
接口。不过许多常见的节点,例如CCSprite
与CCLayerColor
等,都实现了CCRGBAProtocol
接口,因此通常我们不必担心这个问题。以下介绍的几个动作也有类似的问题。
-
CCFadeTo
:用于设置一段时间内透明度的变化效果,其初始化方法为:CCFadeTo::create(ccTime duration, Glubyte opacity);
参数中的
Glubyte
是8位无符号整数,因此,opacity
可取0至255中的任意整数。与透明度相关的动作只能应用在精灵(CCSprite
)上,且子节点不会受到父节点的影响。 -
CCTintTo
和CCTintBy
:设置色调变化。这个动作较为少用,其初始化方法为:CCTintTo::create(ccTime duration,GLubyte r,Glubyte g,Glubyte b); CCTintBy::create(float duration, GLshort deltaRed, GLshort deltaGreen, GLshort deltaBlue);
与
CCFadeTo
类似,r
、g
和b
的取值范围也为0至255。
这一类动作用于实现一些特殊的视觉效果,下面将简要介绍其中的两个动作。
-
CCBlink
:使目标节点闪烁,其初始化方法为:CCBlink::create(ccTime duration, unsigned int uBlicks);
其中,
uBlicks
是闪烁次数。 -
CCAnimation
:播放帧动画,用帧动画的形式实现动画效果,例如鱼的游动。