前面介绍的简单动作显然不足以满足游戏开发的要求,在这些动作的基础上,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);
其中仅包含一个实型参数,表示动作占用的时间。