Cocos2d-x(1) CCAction动作机制
动作机制
1.CCAction
CCAction是动作类的基类,所有的动作都派生自这个类,它创建的一个对象代表了一个动作。动作作用于CCNode,因此,
任何一个动作都需要由CCNode对象来执行。以下代码实现了一条鱼用1 秒钟的时间移动到了(0, 0)点:
CCSprite* sprite = CCSprite ::create("fish.png"); CCAction* action = CCMoveTo ::create(1.0f, ccp(0, 0)); sprite->runAction(action);
值得注意的是,一个 CCAction只能使用一次,这是因为动作对象不仅描述了动作,还保存了这个动作持续过程中不断改变的一些中间参数。对于需要反复使用的动作对象,可以通过copy方法复制使用。
CCAction作为一个基类,其实质是一个接口(即抽象类),由它派生的实现类(如运动和转动等)才是我们实际使用的动作。CCAction的绝大多数实现类都派生自 CCFiniteTimeAction,这个类定义了在有限时间内可以完成的动作。CCFiniteTimeAction定义了reverse 方法,通过这个方法可以获得一个与原动作相反的动作(称作逆动作),例如隐藏一个精灵后,用逆转动作再显示出来。当然,并非所有的动作都有对应的逆动作,例如类似" 放大到" 等设置属性为常量的动作不存在逆动作,而设置属性为相对值的动作则往往存在相应的逆动作。关于逆动作,后面将会看到更多演示。
由CCFiniteTimeAction派生出的两个主要类分别是瞬时动作(CCActionInstant )和持续性动作(CCActionInterval),这两类动作与4.4 节中介绍的复合动作配合使用,能得到复杂生动的动作效果。
如图,为CCAction的继承关系图

-----------------------CCAction-的分割线----------------------------------------------------
2.瞬时动作(CCActionInstant )
瞬时动作是指能立刻完成的动作,是 CCFiniteTimeAction中动作持续时间为 0 的特例。更准确地说,这类动作是在下一帧会立刻执行并完成的动作,如设定位置、设定缩放等。这些动作原本可以通过简单地对 CCNode赋值完成,但是把它们包装为动作后,可以方便地与其他动作类组合为复杂动作。
下面介绍一些常用的瞬时动作。
2.1 CCPlace
该动作用于将节点放置到某个指定位置,其作用与修改节点的Position属性相同。例如,将鱼放置到屏幕坐标(100, 100)
处,再执行曲线运动curveMove 的代码如下:
CCFiniteTimeAction* placeAction = CCPlace::create(ccp(100, 100)); CCAction* action = CCSequence::create(placeAction, curveMove, NULL);
其中CCSequence又称为动作序列,是一种复合动作,它在初始化时,会接受多个动作,当它被执行时,这些动作会按顺序逐个执行,形成有序的一列动作。在4.4 节中,我们将详细介绍复合动作。
2.2 CCFlipX 和CCFlipY
这两个动作分别用于将精灵沿X 和Y 轴反向显示,其作用与设置精灵的 FlipX 和FlipY 属性相同。将其包装为动作类也是为了便于与其他动作进行组合。例如,我们想使鱼从屏幕的一端游动到另一端,然后按原路返回。为了更自然一点(虽然仍然不是很自然),我们设置一个序列,鱼先执行移动的动作,在鱼到达另一端时反向显示,然后再执行移动回起点的动作,相关代码如下:
CCFiniteTimeAction* flipXAction = CCFlipX::create(true); CCAction* action = CCSequence::create(curveMove, flipXAction, curveMove->reverse(), NULL);
其中reverse 的作用是取得原动作的逆动作。在这个例子中,鱼沿 X 轴翻转后将会沿原路返回起点。
2.3 CCShow和CCHide
这两个动作分别用于显示和隐藏节点,其作用与设置节点的Visible 属性的作用一样。例如,为了使鱼完成游动之后隐藏起来,我们使用如下代码:
CCFiniteTimeAction* hideAction = CCHide::create();
CCAction* action = CCSequence::create(curveMove, hideAction, NULL);
2.4 CCCallFunc
CCCallFunc系列动作包括CCCallFunc、CCCallFuncN 、CCCallFuncND,以及CCCallFuncO四个动作,用来在动作中进行方法的调用(之所以不是函数调用,是因为它们只能调用某个类中的实例方法,而不能调用普通的C 函数)。当某个对象执行CCCallFunc系列动作时,就会调用一个先前被设置好的方法,以完成某些特别的功能。后面我们将举例说明它的用途。
在CCCallFunc系列动作的 4 个类中,CCCallFunc调用的方法不包含参数,CCCallFuncN 调用的方法包含一个 CCNode*类型的参数,表示执行动作的对象。CCCallFuncND 调用的方法包含两个参数,不仅有一个节点参数,还有一个自定义参数(CCNode*与void*)。CCCallFuncO 调用的方法则只包含一个 CCObject*类型的参数。
实际上,CCCallFunc系列动作的后缀"N" 表示 Node 参数,指的是执行动作的对象,"D" 表示 Data参数,指的是用户自定义的数据,"O" 表示对象,指的是一个用户自定义的 CCObject参数。在不同的情况下,我们可以根据不同的需求来选择不同的CCCallFunc动作。考虑一种情况,我们创建了许多会在屏幕中移动的精灵,希望精灵在移动结束之后就从游戏中删除。为了实现这个效果,我们可以创建一系列动作:首先让精灵移动,然后调用一个removeSelf(CCNode* nodeToRemove)方法来删除nodeToRemove对象。在 removeSelf方法中需要访问执行此动作的精灵,因此我们就采用 CCCallFuncN 来调用removeSelf方法。
在《捕鱼达人》中,当鱼游动到屏幕外之后,我们有必要将其从屏幕中除去。否则,鱼会在我们看不到的屏幕之外一直存在,导致内存上的浪费。我们一般会在其完成指定动作后,调用相应函数将其清除,相关代码如下:
CCFiniteTimeAction* actionMoveDone = CCCallFuncN::create(this, callfuncN_selector(GameScene::moveActionEnd)); CCAction* action = CCSequence::create(curveMove, actionMoveDone, NULL);
这样,我们就指定了一个动作序列,当执行了curveMove 这个动作之后,再调用actionMoveDone函数。在actionMoveDone中,我们将完成动作的鱼从屏幕中移出。
----------------------瞬时动作(CCActionInstant)-的分割线----------------------------------------------------
3. 持续性动作(CCActionInterval)
持续性动作是在持续的一段时间里逐渐完成的动作,如精灵从一个点连续地移动到另一个点。与瞬时动作相比,持续性动作的种类更丰富。由于这些动作将持续一段时间,所以大多数的持续性动作都会带有一个用于控制动作执行时间的实型参数duration。
每一种持续性动作通常都存在两个不同的变种动作,分别具有 To和By 后缀:后缀为 To的动作描述了节点属性值的绝对变化,例如 CCMoveTo将对象移动到一个特定的位置;而后缀为 By的动作则描述了属性值相对的变化,如 CCMoveBy将对象移动一段相对位移。
根据作用效果不同,可以将持续性动作划分为以下4 大类:
位置变化动作 ; 属性变化动作 ; 视觉特效动作 ; 控制动作 ;
EX
复合动作 ; 变速动作
3.1 位置变化动作
针对位置(position)这一属性,引擎为我们提供了 3 种位置变化动作类型,下面将简要介绍这几种动作。
3.1.1 CCMoveTo和CCMoveBy:用于使节点做直线运动。设置了动作时间和终点位置后,节点就会在规定时间内,从当前位置直线移动到设置的终点位置。它们的初始化方法分别为:
CCMoveTo::create(ccTime duration, CCPoint& pos);
CCMoveBy::create(ccTime duration, CCPoint& pos);
其中,duration参数表示动作持续的时间,pos 参数表示移动的终点或距离。对于 CCMoveTo,节点会被移动到pos 对应的
位置;对于CCMoveBy,节点会相对之前的位置移动 pos 的距离。
3.1.2 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 表示跳跃次数。
2.1.3 CCBezierTo和CCBezierBy:使节点进行曲线运动,运动的轨迹由贝塞尔曲线描述。贝塞尔曲线是描述任意曲线的有力工具,在许多软件(如Adobe Photoshop)中,钢笔工具就是贝塞尔曲线的应用。实际上,在《捕鱼达人》游戏中,为了控制鱼的游动,我们就用到了贝塞尔曲线。
每一条贝塞尔曲线都包含一个起点和一个终点。在一条曲线中,起点和终点都各自包含一个控制点,而控制点到端点的连线称作控制线。控制线决定了从端点发出的曲线的形状,包含角度和长度两个参数:角度决定了它所控制的曲线的方向,即这段曲线在这一控制点的切线方向;长度控制曲线的曲率。控制线越长,它所控制的曲线离控制线越近。示例图如图4 - 1所示。

任意一段曲线都可以由一段或几段相连的贝塞尔曲线组成,因此我们只需考虑一段贝塞尔曲线应该如何描述即可。一段独立的贝塞尔曲线如图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);
-----------------3.1 位置变化动作 -----小小的分割线----------------------------------------------
3.2 属性变化动作
另一类动作是属性变化动作,它的特点是通过属性值的逐渐变化来实现动画效果。例如,下面要介绍的第一个动作CCScaleTo,它会在一段时间内不断地改变游戏元素的scale 属性,使属性值平滑地变化到一个新值,从而使游戏元素产生缩放的动画效果。
3.2.1 CCScaleTo和CCScaleBy :产生缩放效果,使节点的缩放系数随时间线性变化。对应的初始化方法为:
CCScaleTo::create(ccTime duration, float s); CCScaleBy::create(ccTime duration, float s);
其中,s 为缩放系数的最终值或变化量。
3.2.2 CCRotateTo和CCRotateBy:产生旋转效果。对应的初始化方法为:
CCRotateTo::create(ccTime duration, float fDeltaAngle); CCRotateBy::create(ccTime duration, float fDeltaAngle);
其中fDeltaAngle 的单位是角度,正方向为顺时针方向。
3.2.3 CCFadeIn和CCFadeOut:产生淡入淡出效果,其中前者实现了淡入效果,后者实现了淡出效果。对应的初始化方法为:
这里需要说明的是,只有实现了 CCRGBAProtocol接口的节点才可以执行这类动作,这是因为与透明度或颜色相关的属性都继承自CCRGBAProtocol接口。不过许多常见的节点,例如CCSprite 与CCLayerColor 等,都实现了 CCRGBAProtocol接口,因此通常我们不必担心这个问题。
3.2.4 CCFadeTo:用于设置一段时间内透明度的变化效果。其初始化方法为:
CCFadeTo::create(ccTime duration, Glubyte opacity);
参数中的Glubyte 是8 位无符号整数,因此,opacity 可取0 至255 中的任意整数。与透明度相关的动作只能应用在精灵(CCSprite)上,且子节点不会受到父节点的影响。
3.2.5 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 。
--------------3.2 属性变化动作 ---------小小的分割线---------------------------
3.3 视觉特效动作
这一类动作用于实现一些特殊的视觉效果,下面将简要介绍其中的两个动作。
3.3.1 CCBlink:使目标节点闪烁。其初始化方法为:
CCBlink::create(ccTime duration, unsigned int uBlicks);
其中,uBlicks 是闪烁次数。
3.3.2 CCAnimation :播放帧动画,用帧动画的形式实现动画效果,例如鱼的游动。在后面(第 5 章)中,我们将详细介绍它。
--------------3.3 视觉特效动作 ---------小小的分割线---------------------------
3.4 控制动作
控制动作是一类特殊的动作,用于对一些列动作进行精细控制。利用这一类动作可以实现一些实用的功能,因此它们是十分常用的。这类动作包括CCDelayTime 、CCRepeat 和CCRepeatForever 等。CCDelayTime 可以将动作延时一定的时间,CCRepeat可以把现有的动作重复一定次数,CCRepeateForever可以使一个动作不断重复下去。
事实上,控制动作与复合动作息息相关,因此我们将在3.5 节中对它们进行详细的讨论。
--------------3.4 控制动作 ---------小小的分割线---------------------------
3.5 复合动作
前面介绍的简单动作显然不足以满足游戏开发的要求,在这些动作的基础上,Cocos2d-x 为我们提供了一套动作的复合机制,允许我们组合各种基本动作,产生更为复杂和生动的动作效果。复合动作是一类特殊的动作,因此它也需要使用 CCNode的runAction 方法执行。而它的特殊之处在于,作为动作容器,复合动作可以把许多动作组合成一个复杂的动作。因此,我们通常会使用一个或多个动作来创建复合动作,再把动作交给节点执行。
复合动作十分灵活,这是由于复合动作本身也是动作,因此也可以作为一个普通的动作嵌套在其他复合动作中。
3.5.1 重复(CCRepeat/CCRepeatForever)
有的情况下,动作只需要执行一次即可,但我们还常常遇到一个动作反复执行的情况。对于一些重复的动作,如鱼的摆动、能量槽的转动,我们可以通过CCRepeat与CCRepeatForever 这两个方式重复执行:
CCRepeat* CCRepeat::create(CCFiniteTimeAction *pAction, unsigned int times); CCRepeatForever *CCRepeatForever::create(CCActionInterval *pAction);
在上述代码中,pAction 参数表示需要重复的动作,第一个方法允许指定动作的重复次数,第二个方法使节点一直重复该动作直到动作被停止。
------------------------3.5.1 重复(CCRepeat/CCRepeatForever)---------------小小的分割线----------------------------------
3.5.2 并列(CCSpawn)
指的是使一批动作同时执行。在《捕鱼达人》游戏中,鱼一边沿曲线游动一边摆尾巴,炮弹一边发射一边喷射气体,金币一边旋转一边移动等动作,都可以通过CCSpawn 来实现。CCSpawn 从CCActionInterval 派生而来的,它提供了两个工厂方法:
CCSpawn::create(CCFiniteTimeAction *pAction1,...);
CCSpawn::create(CCFiniteTimeAction *pAction1, CCFiniteTimeAction *pAction2);
其中第一个静态方法可以将多个动作同时并列执行,参数表中最后一个动作后需要紧跟 NULL表示结束。第二个则只能指定两个动作复合,不需要在最后一个动作后紧跟NULL。此外,执行的动作必须是能够同时执行的、继承自CCFiniteTimeAction的动作。组合后,CCSpawn 动作的最终完成时间由其成员中最大执行时间的动作来决定。
---------------------------3.5.2 并列(CCSpawn)---------------小小的分割线----------------------------------
3.5.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_pTw就分别是这两个动作本身;当我们创建更多动作的组合时,引擎会把动作分解为两部分来看待,其中后一部分只包含最后一个动作,而前一部分包含它之前的所有动作,引擎把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()); }
------------------------3.5.3 序列(CCSequence)---------------小小的分割线----------------------------------
3.5.4 延时(CCDelayTime)
CCDelayTime 是一个" 什么都不做" 的动作,类似于音乐中的休止符,用来表示动作序列里一段空白期,通过占位的方式将不同的动作段串接在一起。实际上,这与一个定时期实现的延迟没有区别,但相比之下,使用CCDelayTime 动作来延时就可以方便地利用动作序列把一套动作连接在一起。CCDelayTime 只提供了一个工程方法,如下所示:
CCDelayTime::create(float d);
其中仅包含一个实型参数,表示动作占用的时间。
------------------------3.5.4 延时(CCDelayTime)---------------小小的分割线----------------------------------
3.6 变速动作
大部分动作的变化过程是与时间成线性关系的,即一个动作经过相同时间产生的变化相同,例如,CCMoveBy会使节点在同样长的时间内经过同样的位移。这是因为Cocos2d-x 把动作的速度变化控制抽离了出来,形成一个独立的机制。借助这个机制,我们可以很方便地实现诸如鱼的变速游动、金币的加速飞行以及后面将要介绍的动作平滑化等效果。普通动作配合变速动作,可以构造出很有趣的动作效果。
与复合动作类似,变速动作也是一种特殊的动作,它可以把任何一种动作按照改变后的速度执行。因此,在初始化变速动作时,需要传入一个动作。
变速动作包括CCSpeed 动作与CCEase系列动作,下面我们详细介绍这些动作。
3.6.1 CCSpeed
CCSpeed 用于线性地改变某个动作的速度,因此,可以实现成倍地快放或慢放功能。为了改变一个动作的速度,首先需要将目标动作包装到CCSpeed 动作中:
CCRepeatForever* repeat = CCRepeatForever::create(animation); CCSpeed* speed = CCSpeed::create(repeat, 1.0f); speed->setTag(action_speed_tag); fish->runAction(speed);
在上面的代码中,我们创建了一个animation 动作的CCRepeatForever 复合动作repeat ,使动画被不断地重复执行。然后,我们又使用repeat动作创建了一个 CCSpeed 变速动作。create 初始化方法中的两个参数分别为目标动作与变速比率。设置变速比率为1 ,目标动作的速度将不会改变。最后,我们为 speed 动作设置了一个tag 属性,并把动作交给 fish精灵,让精灵执行变速动作。此处设置的tag 属性与CCNode的tag 属性类似,用于从节点中方便地查找动作。
接下来,在需要改变速度的地方,我们通过修改变速动作的speed 属性来改变动作速度。下面的代码将会把上面设置的动画速度变为原来的两倍:
CCSpeed * speed = fish->getActionByTag(action_speed_tag); speed->setSpeed(2.0f);
------------------------3.6.1 CCSpeed ---------------小小的分割线----------------------------------
3.6.2 CCActionEase
虽然使用CCSpeed 能够改变动作的速度,然而它只能按比例改变目标动作的速度。如果我们要实现动作由快到慢、速度随时间改变的变速运动,需要不停地修改它的speed 属性才能实现,显然这是一个很烦琐的方法。下面将要介绍的 CCActionEase系列动作通过使用内置的多种自动速度变化来解决这一问题。
CCActionEase系列包含15 个动作,它们可以被概括为 5 类动作:指数缓冲、Sine缓冲、弹性缓冲、跳跃缓冲和回震缓冲。每一类动作都有3 个不同时期的变换:In、Out 和InOut。下面使用时间变换图像表示每组 CCActionEase 动作的作用效果,其中横坐标表示实际动画时间,纵坐标表示变换后的动画时间。因此,线性动作的图像应该是一条自左下角到右上角的直线。图4 -3 展示了Cocos2d-x 的各种变速曲线。


CCActionEase的使用方法与CCSpeed 类似。以Sine 缓冲为例,以下代码实现了 InSine变速运动:
CCEaseSineIn* sineIn = CCEaseSineIn::create(action); sineIn->setTag(action_sine_in_tag); fish->runAction(sineIn);
CCAction到此暂停!!!
浙公网安备 33010602011771号