之前在遇到这么一个问题,在CCSequence中加入CCRepeatForever,发现其他动作执行没问题,就是CCRepeatForever无法执行。代码并没有问题,很奇怪。
示例
示例1 2 3 4 5 6 7 8 9 10 11 12
| CCBlink* blink=CCBlink::create(0.5f,10); CCAnimation* animation=CCAnimation::create(); animation->addSpriteFrameWithFileName("CloseNormal.png"); animation->addSpriteFrameWithFileName("CloseSelected.png"); animation->setDelayPerUnit(1.0f); CCAnimate* animate=CCAnimate::create(animation); CCRepeatForever* repeat=CCRepeatForever::create(animate); CCSequence* sequence=CCSequence::create(blink,repeat,NULL); CCSprite* close=CCSprite::create("CloseNormal.png"); close->setPosition(ccp(240,160)); this->addChild(close); close->runAction(sequence);
|
结果精灵闪烁10次以后,帧动画不执行了。
原因
先了解一下CCSequence的创建和执行原理。
CCSequence的创建
创建CCSequence调用
创建CCSequence1
| CCSequence* CCSequence::create(CCFiniteTimeAction *pAction1, ...)
|
内部调用了createWithVariableList,从实现可以看出这是一个递归调用。
createWithVariableList1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| CCSequence* CCSequence::createWithVariableList(CCFiniteTimeAction *pAction1, va_list args) { CCFiniteTimeAction *pNow; CCFiniteTimeAction *pPrev = pAction1; bool bOneAction = true; while (pAction1) { pNow = va_arg(args, CCFiniteTimeAction*); if (pNow) { pPrev = createWithTwoActions(pPrev, pNow); bOneAction = false; } else { if (bOneAction) { pPrev = createWithTwoActions(pPrev, ExtraAction::create()); } break; } } return ((CCSequence*)pPrev); }
|
假如有3个动作要被串联,则先把第1个和第2个串联一个CCSequence,再把这个CCSequence和第3个动作串联成最终的CCSequence,然后返回。从CCSequence的成员变量可以看到:
动作对象指针成员变量1
| CCFiniteTimeAction *m_pActions[2];
|
使用递归多少会降低程序的运行效率,但是却可以换来代码的简洁性,同样的CCSpawn也是这么实现的。
在createWithTwoActions中,调用了initWithTwoActions函数,实现了把两个动作串成一个CCSequence,关键代码如下:
initWithTwoActions1 2 3 4 5 6 7 8
| float d = pActionOne->getDuration() + pActionTwo->getDuration(); CCActionInterval::initWithDuration(d); m_pActions[0] = pActionOne; pActionOne->retain(); m_pActions[1] = pActionTwo; pActionTwo->retain();
|
duration
从示例可以看出,闪烁动画blink的duration是0.5s,那CCRepeatForever呢?1s?当然不是,1s只是帧动画animate的帧间间隔,每个帧动画包含2帧,而CCRepeatForever的duration是0。因此,当示例中的闪烁动画blink和重复动画repeat串联成CCSequence sequence的时候,sequence的duration就变成0.5+0=0.5s,这很重要。
m_split
CCSequence中有这么一个成员变量
当执行runAction的时候,CCSequence会调用
startWithTarget1 2 3 4 5 6
| void CCSequence::startWithTarget(CCNode *pTarget) { CCActionInterval::startWithTarget(pTarget); m_split = m_pActions[0]->getDuration() / m_fDuration; m_last = -1; }
|
而这里由于blink占了0.5s,repeat占了0s,总时长0.5s,所以m_split是0.5/0.5=1。blink占满了整个CCSequence,所以CCSequence无法执行repeat。
这时候再来看CCSequence::update(float dt)函数的执行,就会恍然大悟了。
update1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int found = 0; float new_t = 0.0f; if( t < m_split ) { found = 0; if( m_split != 0 ) new_t = t / m_split; else new_t = 1; } else { found = 1; if ( m_split == 1 ) new_t = 1; else new_t = (t-m_split) / (1 - m_split ); }
|
注意
(1)CCSpawn也会有这个问题,所以CCSpawn也无法执行加入其中的CCRepeatForever动作。
(2)CCRepeatForever的反转动作也是无效了,一个不会停止的动作从什么地方开始反转?当然你可以先把动作反转了再加入CCRepeatForever中,这是没问题的。
解决方案
(1)对于同时动作,不使用CCSpawn,采用分别执行
分别执行1 2
| close->runAction(blink); close->runAction(repeat);
|
(2)对于连续动作,不直接往CCSequence中加入CCRepeatForever,而是把CCRepeatForever放入瞬时动作CCCallFunc中,再把CCCallFunc加入CCSequence中执行。
CCSequence串联1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| close=CCSprite::create("CloseNormal.png"); CCBlink* blink=CCBlink::create(0.5f,10); CCCallFunc* callFunc=CCCallFunc::create(this,callfunc_selector(TestScene::repeatFunc)); CCSequence* sequence=CCSequence::create(blink,callFunc,NULL); close->setPosition(ccp(240,160)); this->addChild(close); close->runAction(sequence); void TestScene::repeatFunc() { CCAnimation* animation=CCAnimation::create(); animation->addSpriteFrameWithFileName("CloseNormal.png"); animation->addSpriteFrameWithFileName("CloseSelected.png"); animation->setDelayPerUnit(1.0f); CCAnimate* animate=CCAnimate::create(animation); CCRepeatForever* repeat=CCRepeatForever::create(animate); close->runAction(repeat); }
|
(3)对于CCAnimation帧动画,可以设置循环属性,而不使用CCRepeatForever。
setLoops1
| animation->setLoops(-1);
|
总结
虽然CCRepeatForever也同样继承于CCActionInterval,理论上是延时动作的子类,但是和一般的延时动作又有很大的不同,所以平时在使用的时候必须很小心,不能当成一般的CCActionInterval使用。而在cocos2d-x动作的分类上是不是应该把它从CCAction继承出来会比较好一点?