Cocos2d-x 之复合动作

Cocos2d-x为我们提供了一套动作的复合机制,允许我们组合各种基本动作,产生更为复杂和生动的动作效果。复合动作是一类特殊的动作,因此它也需要使用CCNoderunAction方法执行。而它的特殊之处在于,作为动作容器,复合动作可以把许多动作组合成一个复杂的动作。因此,我们通常会使用一个或多个动作来创建复合动作,再把动作交给节点执行。

复合动作十分灵活,这是由于复合动作本身也是动作,因此也可以作为一个普通的动作嵌套在其他复合动作中。

1. 重复(CCRepeat/CCRepeatForever

有的情况下,动作只需要执行一次即可,但我们还常常遇到一个动作反复执行的情况。对于一些重复的动作,如鱼的摆动、能量槽的转动,我们可以通过CCRepeatCCRepeatForever这两个方式重复执行:

CCRepeat* CCRepeat::create(CCFiniteTimeAction *pAction, unsigned int times);
CCRepeatForever *CCRepeatForever::create(CCActionInterval *pAction);

在上述代码中,pAction参数表示需要重复的动作,第一个方法允许指定动作的重复次数,第二个方法使节点一直重复该动作直到动作被停止。

2. 并列(CCSpawn

指的是使一批动作同时执行。在《捕鱼达人》游戏中,鱼一边沿曲线游动一边摆尾巴,炮弹一边发射一边喷射气体,金币一边旋转一边移动等动作,都可以通过CCSpawn来实现。CCSpawnCCActionInterval派生而来的,它提供了两个工厂方法:

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)并不被支持。

在实现CCSequenceCCSpawn两个组合动作类时,有一个非常有趣的细节:成员变量中并没有定义一个可变长的容器来容纳每一个动作系列,而是定义了m_pOnem_pTwo两个动作成员变量。如果我们创建了两个动作的组合,那么m_pOnem_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);

CCSpawnCCSequence所采用的机制类似,在此就不再赘述了。采用这种递归的方式,而不是直接使用容器来定义组合动作,实际上为编程带来了极大的便利。维护多个动作的组合是一个复杂的问题,现在我们只需要考虑两个动作组合的情况就可以了。下面是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种位置变化动作类型,下面将简要介绍这几种动作。

  • CCMoveToCCMoveBy:用于使节点做直线运动。设置了动作时间和终点位置后,节点就会在规定时间内,从当前位置直线移动到设置的终点位置。它们的初始化方法分别为:

    CCMoveTo::create(ccTime duration, CCPoint& pos);
    CCMoveBy::create(ccTime duration, CCPoint& pos);

    其中,duration参数表示动作持续的时间,pos参数表示移动的终点或距离。对于CCMoveTo,节点会被移动到pos对应的位置;对于CCMoveBy,节点会相对之前的位置移动pos的距离。

  • CCJumpToCCJumpBy:使节点以一定的轨迹跳跃到指定位置。它们的初始化方法如下:

    CCJumpTo::create(ccTime duration, CCPoint pos, float height, int jumps);
    CCJumpBy::create(ccTime duration, CCPoint pos, float height, int jumps);

    其中pos表示跳跃的终点或距离,height表示最大高度,jumps表示跳跃次数。

  • CCBezierToCCBezierBy:使节点进行曲线运动,运动的轨迹由贝塞尔曲线描述。贝塞尔曲线是描述任意曲线的有力工具,在许多软件(如Adobe Photoshop)中,钢笔工具就是贝塞尔曲线的应用。实际上,在《捕鱼达人》游戏中,为了控制鱼的游动,我们就用到了贝塞尔曲线。

每一条贝塞尔曲线都包含一个起点和一个终点。在一条曲线中,起点和终点都各自包含一个控制点,而控制点到端点的连线称作控制线。控制线决定了从端点发出的曲线的形状,包含角度和长度两个参数:角度决定了它所控制的曲线的方向,即这段曲线在这一控制点的切线方向;长度控制曲线的曲率。控制线越长,它所控制的曲线离控制线越近。示例图如图4-1所示。

enter image description here[+]查看原图

图4-1 三段贝塞尔曲线

任意一段曲线都可以由一段或几段相连的贝塞尔曲线组成,因此我们只需考虑一段贝塞尔曲线应该如何描述即可。一段独立的贝塞尔曲线如图4-2所示。

enter image description here

图4-2 贝塞尔曲线及其控制点

使用时我们要先创建ccBezierConfig结构体,设置好终点endPosition以及两个控制点controlPoint_1controlPoint_2后,再把结构体传入CCBezierToCCBezierBy的初始化方法中:

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属性,使属性值平滑地变化到一个新值,从而使游戏元素产生缩放的动画效果。

  • CCScaleToCCScaleBy:产生缩放效果,使节点的缩放系数随时间线性变化,对应的初始化方法为:

    CCScaleTo::create(ccTime duration, float s);
    CCScaleBy::create(ccTime duration, float s);

    其中,s为缩放系数的最终值或变化量。

  • CCRotateToCCRotateBy:产生旋转效果,对应的初始化方法为:

    CCRotateTo::create(ccTime duration, float fDeltaAngle);
    CCRotateBy::create(ccTime duration, float fDeltaAngle);

    其中fDeltaAngle的单位是角度,正方向为顺时针方向。

  • CCFadeInCCFadeOut:产生淡入淡出效果,其中前者实现了淡入效果,后者实现了淡出效果,对应的初始化方法为:

    CCFadeIn::create(ccTime duration);
    CCFadeOut::create(ccTime duration);

    这里需要说明的是,只有实现了CCRGBAProtocol接口的节点才可以执行这类动作,这是因为与透明度或颜色相关的属性都继承自CCRGBAProtocol接口。不过许多常见的节点,例如CCSpriteCCLayerColor等,都实现了CCRGBAProtocol接口,因此通常我们不必担心这个问题。

    以下介绍的几个动作也有类似的问题。

  • CCFadeTo:用于设置一段时间内透明度的变化效果,其初始化方法为:

    CCFadeTo::create(ccTime duration, Glubyte opacity);

    参数中的Glubyte是8位无符号整数,因此,opacity可取0至255中的任意整数。与透明度相关的动作只能应用在精灵(CCSprite)上,且子节点不会受到父节点的影响。

  • CCTintToCCTintBy:设置色调变化。这个动作较为少用,其初始化方法为:

    CCTintTo::create(ccTime duration,GLubyte r,Glubyte g,Glubyte b);
    CCTintBy::create(float duration, GLshort deltaRed, GLshort deltaGreen, GLshort deltaBlue);

    CCFadeTo类似,rgb的取值范围也为0至255。

这一类动作用于实现一些特殊的视觉效果,下面将简要介绍其中的两个动作。

  • CCBlink:使目标节点闪烁,其初始化方法为:

    CCBlink::create(ccTime duration, unsigned int uBlicks);

    其中,uBlicks是闪烁次数。

  • CCAnimation:播放帧动画,用帧动画的形式实现动画效果,例如鱼的游动。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章