zoukankan      html  css  js  c++  java
  • cocos2d-x游戏引擎核心之四——动作调度机制

    一、动作机制的用法

    在深入学习动作机制在 Cocos2d-x 里是如何实现的之前,我们先来学习整套动作机制的用法,先知道怎么用,再深入学习它如何实现,是一个很好很重要的学习方法。

    (1)基本概念

    CCAction 是动作类的基类,所有的动作都派生自这个类,它创建的一个对象代表了一个动作。动作作用于 CCNode,因此,任何一个动作都需要由 CCNode 对象来执行。CCAction 作为一个基类,其实质是一个接口(即抽象类),由它派生的实现类(如运动和转动等)才是我们实际使用的动作。CCAction 的绝大多数实现类都派生自 CCFiniteTimeAction,这个类定义了在有限时间内可以完成的动作。CCFiniteTimeAction 定义了 reverse 方法,通过这个方法可以获得一个与原动作相反的动作(称作逆动作),例如隐藏一个精灵后,用逆转动作再显示出来。当然,并非所有的动作都有对应的逆动作,例如类似"放大到"等设置属性为常量的动作不存在逆动作,而设置属性为相对值的动作则往往存在相应的逆动作。由 CCFiniteTimeAction 派生出的两个主要类分别是瞬时动作(CCActionInstant)和持续性动作(CCActionInterval),这两类动作后面介绍的复合动作配合使用,能得到复杂生动的动作效果。

    (2)瞬时动作

    瞬时动作是指能立刻完成的动作,是 CCFiniteTimeAction 中动作持续时间为 0 的特例。下面介绍一些常用的瞬时动作:

    1. CCPlace:将节点放置到某个指定位置,其作用与修改节点的 Position 属性相同。

    2. CCFlipX 和 CCFlipY:这两个动作分别用于将精灵沿 X 和 Y 轴反向显示,其作用与设置精灵的 FlipX 和 FlipY 属性相同。

    3. CCShow 和 CCHide:这两个动作分别用于显示和隐藏节点,其作用与设置节点的 Visible 属性的作用一样。

    4. CCCallFunc:

      CCCallFunc 系列动作包括 CCCallFunc、CCCallFuncN、CCCallFuncND,以及 CCCall- FuncO 四个动作,用来在动作中进行方法的调用(之所以不是函数调用,是因为它们只能调用某个类中的实例方法,而不能调用普通的 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 方法。

    (3)持续性动作

    持续性动作是在持续的一段时间里逐渐完成的动作,每一种持续性动作通常都存在两个不同的变种动作,分别具有 To 和 By 后缀:后缀为 To 的动作描述了节点属性值的绝对变化,例如 CCMoveTo 将对象移动到一个特定的位置;而后缀为 By 的动作则描述了属性值相对的变化,如 CCMoveBy 将对象移动一段相对位移。

    根据作用效果不同,可以将持续性动作划分为以下 4 大类:

      1. 位置变化动作:CCMoveTo 和 CCMoveBy,CCJumpTo 和 CCJumpBy,CCBezierTo 和 CCBezierBy。
      2. 属性变化动作:CCScaleTo 和 CCScaleBy,CCRotateTo 和 CCRotateBy,CCFadeIn 和 CCFadeOut,CCFadeTo。
      3. 视觉特效动作:CCBlink,CCAnimation(播放帧动画,用帧动画的形式实现动画效果)
      4. 控制动作: CCDelayTime、CCRepeat 和CCRepeatForever 等,控制动作与复合动作息息相关。

    (4)复合动作

    1 重复(CCRepeat/CCRepeatForever):

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

    2 并列(CCSpawn):

    CCSpawn::create(CCFiniteTimeAction *pAction1,...);
    CCSpawn::create(CCFiniteTimeAction *pAction1, CCFiniteTimeAction *pAction2);

    3. 序列(CCSequence):

    CCSequence::create(CCFiniteTimeAction *pAction1,...);
    CCSequence::create(CCFiniteTimeAction *pAction1,CCFiniteTimeAction *pAction2);

    在实现 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;
    }

    4. 延时(CCDelayTime):CCDelayTime::create(float d);

    (5)变速动作

    变速动作包括 CCSpeed 动作与 CCEase 系列动作:

    1. CCSpeed:CCSpeed 用于线性地改变某个动作的速度,因此,可以实现成倍地快放或慢放功能。为了改变一个动作的速度,首先需要将目标动作包装到 CCSpeed 动作中:

    CCRepeatForever* repeat = CCRepeatForever::create(animation);
    CCSpeed* speed = CCSpeed::create(repeat, 1.0f);
    speed->setTag(action_speed_tag);
    fish->runAction(speed);

    接下来,在需要改变速度的地方,我们通过修改变速动作的 speed 属性来改变动作速度,下面的代码将会把上面设置的动画速度变为原来的两倍:

    CCSpeed * speed = fish->getActionByTag(action_speed_tag);
    speed->setSpeed(2.0f);

    2. CCActionEase:

    虽然使用 CCSpeed 能够改变动作的速度,然而它只能按比例改变目标动作的速度。如果我们要实现动作由快到慢、速度随时间改变的变速运动,需要不停地修改它的speed属性才能实现,显然这是一个很烦琐的方法。下面将要介绍的CCActionEase系列动作通过使用内置的多种自动速度变化来解决这一问题。CCActionEase 系列包含 15 个动作,它们可以被概括为 5 类动作:指数缓冲、Sine缓冲、弹性缓冲、跳跃缓冲和回震缓冲。每一类动作都有 3 个不同时期的变换:In、Out 和 InOut。

    CCActionEase 的使用方法与 CCSpeed 类似。以 Sine 缓冲为例,以下代码实现了 InSine 变速运动:

    CCEaseSineIn* sineIn = CCEaseSineIn::create(action);
    sineIn->setTag(action_sine_in_tag);
    fish->runAction(sineIn);

    (6)创建自定义动作

    CCAction 包含两个重要的方法:step 与 update。step 方法会在每一帧动作更新时触发,该方法接受一个表示调用时间间隔的参数 dt,dt 的积累即为动作运行的总时间。引擎利用积累时间来计算动作运行的进度(一个从 0 到 1 的实数),并调用 update 方法更新动作。update 方法是 CCAction 的核心,它由 step 方法调用,接受一个表示动作进度的参数,每一个动作都需要利用进度值改变目标节点的属性或执行其他指令。自定义动作只需要从这两个方法入手即可,我们通常只需要修改 update 方法就可以实现简单的动作。

      也许有的读者已经有了疑问,step 方法与 update 方法都可以做到每一帧判断一次方向,为什么选择重载 step 方法而不是update 方法呢?这是因为引擎在 step 方法中对动作对象的内部成员进行了更新,更新后才会由此方法调用 update 方法来更新目标节点。在方向追踪的动作中,我们除了在每一帧判断方向,还必须同步执行被包装的动作。这就需要我们调用被包装动作的 step 方法,以保证对象能够被完整地更新。

    二、动作机制实现原理

    (1)动作类的结构

    class CC_DLL CCAction : public CCObject 
    {
    public:
        CCAction(void);
        virtual ~CCAction(void);
    
        const char* description();
    
        virtual CCObject* copyWithZone(CCZone *pZone);
    
        //! return true if the action has finished
        virtual bool isDone(void);
    
        //! called before the action start. It will also set the target.
        virtual void startWithTarget(CCNode *pTarget);
    
        /** 
        called after the action has finished. It will set the 'target' to nil.
        IMPORTANT: You should never call "[action stop]" manually. Instead, use: "target->stopAction(action);"
        */
        virtual void stop(void);
    
        //! called every frame with it's delta time. DON'T override unless you know what you are doing.
        virtual void step(float dt);
    
        /** 
        called once per frame. time a value between 0 and 1
    
        For example: 
        - 0 means that the action just started
        - 0.5 means that the action is in the middle
        - 1 means that the action is over
        */
        virtual void update(float time);
        
        inline CCNode* getTarget(void) { return m_pTarget; }
        /** The action will modify the target properties. */
        inline void setTarget(CCNode *pTarget) { m_pTarget = pTarget; }
        
        inline CCNode* getOriginalTarget(void) { return m_pOriginalTarget; } 
        /** Set the original target, since target can be nil.
        Is the target that were used to run the action. Unless you are doing something complex, like CCActionManager, you should NOT call this method.
        The target is 'assigned', it is not 'retained'.
        @since v0.8.2
        */
        inline void setOriginalTarget(CCNode *pOriginalTarget) { m_pOriginalTarget = pOriginalTarget; }
    
        inline int getTag(void) { return m_nTag; }
        inline void setTag(int nTag) { m_nTag = nTag; }
    
    public:
        /** Allocates and initializes the action 
        @deprecated: Please use create() instead. This interface will be deprecated sooner or later.
        */
        CC_DEPRECATED_ATTRIBUTE static CCAction* action();
    
        /** Create an action */
        static CCAction* create();
    protected:
        CCNode    *m_pOriginalTarget;
        /** The "target".
        The target will be set with the 'startWithTarget' method.
        When the 'stop' method is called, target will be set to nil.
        The target is 'assigned', it is not 'retained'.
        */
        CCNode    *m_pTarget;
        /** The action tag. An identifier of the action */
        int     m_nTag;
    };

    继承自 CCAction 的 CCFiniteTimeAction 主要新增了一个用于保存该动作总的完成时间的成员变量:ccTime m_fDuration。对于 CCFiniteTimeAction 的两个子类 CCActionInstant 和 CCActionInterval,前者没有新增任何函数和变量,而后者增加了两个成员变量--ccTime m_elapsed 和 bool m_bFirstTick,其中 m_elapsed 是从动作开始起逝去的时间,而 m_bFirstTick是一个控制变量,在后面的分析中,我们将看到它的作用。

    (2)动作的更新

    1. 当我们对 CCNode 调用 runAction(CCAction* action)方法时,动作管理类 CCActionManager(它是一个单例对象)会将新的 CCAction 和对应的目标节点添加到其管理的动作表。在 CCActionManager 的 addAction 方法中,我们将动作添加到动作队列之后,就会对该 CCAction 调用成员函数startWithTarget(CCNode* pTarget)来绑定该动作的执行者。而在 CCAction 的子类中(如 CCActionInterval),还初始化了一些参数:

    void CCActionInterval::startWithTarget(CCNode *pTarget)
    {
        CCFiniteTimeAction::startWithTarget(pTarget);
        m_elapsed = 0.0f;
        m_bFirstTick = true;
    }

    2. 当这些准备工作都完成后,每一帧刷新屏幕时,系统都会在 CCActionManager 中遍历其动作表中的每一个动作,并调用该动作的 step(ccTimedt)方法。step 方法主要负责计算 m_elapsed 的值,并调用 update(float time)方法,相关代码如下:

    void CCActionInterval::step(float dt)
    {
        if (m_bFirstTick)
        {
            m_bFirstTick = false;
            m_elapsed = 0;
        }
        else
        {
            m_elapsed += dt;
        }
        this->update(MAX (0,
                 MIN(
    1, m_elapsed / MAX(m_fDuration, FLT_EPSILON))         )         ); }

    传入 update 方法的 time 参数表示逝去的时间与动作完成需要的时间的比值,是介于 0 和 1 之间的一个数,即动作完成的百分比

    CCActionInterval并没有进一步实现update方法。下面我们继续以继承自CCActionInterval的CCRotateTo动作的update方法为例,分析 update 函数是如何实现的,其实现代码如下:

    void CCRotateTo::update(float time)
    {
        if (m_pTarget)
        {
            m_pTarget->setRotation(m_fStartAngle + m_fDiffAngle * time);
        }
    }

    看到这里,我们已经能看出 Cocos2d-x 的动作机制的整个工作流程了。在 CCRotateTo 中,最终完成的操作是修改目标节点的 Rotation 属性值,更新该目标节点的旋转属性值。

    3. 最后,在每一帧刷新结束后,在 CCActionManager 类的 update 方法中都会检查动作队列中每一个动作的 isDone 函数是否返回 true。如果返回 true,则动作已完成,将其从队列中删除。isDone 函数的代码如下:

    bool CCActionInterval::isDone(void)
    {
        return m_elapsed >= m_fDuration;
    }

    对于不同的动作类,虽然整体流程大致都是先调用 step 方法,然后按照各个动作的具体定义来更新目标节点的属性,但是不同动作的具体实现会有所不同。例如,CCRepeatForever 动作的 isDone 函数始终返回 false,因为它是永远在执行的动作;又如CCActionInstant 及其子类的 step 函数中,向 update 传递的参数值始终是 1,因为瞬时动作会在下一帧刷新后完成,不需要多次执行 update。

    (3)CCActionManager 的工作原理

    学习了 CCAction 在每一帧中如何被更新之后,我们不妨回头看看动作管理类 CCActionManager 的工作原理。在对CCDirector 进行初始化时,也会对 CCActionManager 进行初始化。下面的代码是 CCDirector::init()方法中的一部分:

        // action manager
        m_pActionManager = new CCActionManager();
        m_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false);

    可以看到,在 CCActionManager 被初始化后,马上就调用了定时调度器 CCScheduler 的 scheduleUpdateForTarget 方法。在 scheduleUpdateForTarget 函数中,我们为 CCActionManager 注册了一个定期更新的服务,这意味着动作的调度与定时器的调度都统一受到 CCScheduler 的控制。具体地说,我们可以方便地同时暂停或恢复定时器与动作的运行,而不必考虑它们不同步的问题。

    CCScheduler 在每一帧更新时,都会触发 CCActionManager 注册的 update 方法。从下面给出的 CCActionManager::update方法的代码可以看到,CCActionManager 在这时对每一个动作都进行了更新。与调度器 CCScheduler 类似的一点是,为了防止动作调度过程中所遍历的表被修改,Cocos2d-x 对动作的删除进行了仔细地处理,保证任何情况下都可以安全地删除动作:

    // main loop
    void CCActionManager::update(float dt)
    {
        for (tHashElement *elt = m_pTargets; elt != NULL; )
        {
            m_pCurrentTarget = elt;
            m_bCurrentTargetSalvaged = false;
    
            if (! m_pCurrentTarget->paused)
            {
                // The 'actions' CCMutableArray may change while inside this loop.
                for (m_pCurrentTarget->actionIndex = 0; m_pCurrentTarget->actionIndex < m_pCurrentTarget->actions->num;
                    m_pCurrentTarget->actionIndex++)
                {
                    m_pCurrentTarget->currentAction = (CCAction*)m_pCurrentTarget->actions->arr[m_pCurrentTarget->actionIndex];
                    if (m_pCurrentTarget->currentAction == NULL)
                    {
                        continue;
                    }
    
                    m_pCurrentTarget->currentActionSalvaged = false;
    
                    m_pCurrentTarget->currentAction->step(dt);
    
                    if (m_pCurrentTarget->currentActionSalvaged)
                    {
                        // The currentAction told the node to remove it. To prevent the action from
                        // accidentally deallocating itself before finishing its step, we retained
                        // it. Now that step is done, it's safe to release it.
                        m_pCurrentTarget->currentAction->release();
                    } else
                    if (m_pCurrentTarget->currentAction->isDone())
                    {
                        m_pCurrentTarget->currentAction->stop();
    
                        CCAction *pAction = m_pCurrentTarget->currentAction;
                        // Make currentAction nil to prevent removeAction from salvaging it.
                        m_pCurrentTarget->currentAction = NULL;
                        removeAction(pAction);
                    }
    
                    m_pCurrentTarget->currentAction = NULL;
                }
            }
    
            // elt, at this moment, is still valid
            // so it is safe to ask this here (issue #490)
            elt = (tHashElement*)(elt->hh.next);
    
            // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
            if (m_bCurrentTargetSalvaged && m_pCurrentTarget->actions->num == 0)
            {
                deleteHashElement(m_pCurrentTarget);
            }
        }
    
        // issue #635
        m_pCurrentTarget = NULL;
    }

     三、CCSequence动作案例分析——CCSequence不能执行CCRepeatForever

    (1)示例代码

    CCBlink* blink=CCBlink::create(0.5f,10);//创建闪烁动画,duration=0.5s  
    CCAnimation* animation=CCAnimation::create();  
    animation->addSpriteFrameWithFileName("CloseNormal.png");  
    animation->addSpriteFrameWithFileName("CloseSelected.png");  
    animation->setDelayPerUnit(1.0f);//帧间间隔1s  
    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次以后,帧动画不执行了。

    (2)原因

    //创建CCSequence  
    CCSequence* CCSequence::create(CCFiniteTimeAction *pAction1, ...) 

    内部调用了createWithVariableList,从实现可以看出这是一个递归调用。

    //获取动作列表,创建CCSequence  
    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);//用前两个动作创建CCSequence并赋给第一个动作  
                bOneAction = false;//置false  
            }  
            else//如果不存在  
            {  
                // If only one action is added to CCSequence, make up a CCSequence by adding a simplest finite time action.  
                if (bOneAction)//如果只有一个动作  
                {  
                    pPrev = createWithTwoActions(pPrev, ExtraAction::create());  
                }  
                break;//跳出循环  
            }  
        }  
          
        return ((CCSequence*)pPrev);//返回第一个动作  
    } 

    假如有3个动作要被串联,则先把第1个和第2个串联一个CCSequence,再把这个CCSequence和第3个动作串联成最终的CCSequence,然后返回。从CCSequence的成员变量可以看到:

    CCFiniteTimeAction *m_pActions[2];//表明只包含2个动作对象指针  

    使用递归多少会降低程序的运行效率,但是却可以换来代码的简洁性,同样的CCSpawn也是这么实现的。在createWithTwoActions中,调用了initWithTwoActions函数,实现了把两个动作串成一个CCSequence,关键代码如下:

    float d = pActionOne->getDuration() + pActionTwo->getDuration();//获取两个动作的duration  
    CCActionInterval::initWithDuration(d);//赋给新的CCSequence  
      
    m_pActions[0] = pActionOne;//同时把两个动作赋给m_pActions指针数组  
    pActionOne->retain();  
      
    m_pActions[1] = pActionTwo;  
    pActionTwo->retain();  

    从示例可以看出,闪烁动画blink的duration是0.5s,那CCRepeatForever呢?1s?当然不是,1s只是帧动画animate的帧间间隔,每个帧动画包含2帧,而CCRepeatForever的duration是0。因此,当示例中的闪烁动画blink和重复动画repeat串联成CCSequence sequence的时候,sequence的duration就变成0.5+0=0.5s,这很重要。

    CCSequence中有这么一个成员变量

    float m_split;//记录了第一个动画时长占总时长的比例,也就是2个动画的时长分界  

    当执行runAction的时候,CCSequence会调用

    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::update(float dt)函数的执行,就会恍然大悟了。

    int found = 0;//当前播放动作索引  
    float new_t = 0.0f;//新播放进度  
      
    if( t < m_split ) {//播放进度<分界进度  
        found = 0;//设置当前播放的是第一个动作  
        if( m_split != 0 )//如果第一个动作时长占比!=0  
            new_t = t / m_split;//计算出第一个动作新的播放进度  
        else  
            new_t = 1;//设置第一个已播放完毕  
      
    } else {//播放进度>=分界进度  
        found = 1;//设置当前播放的是第二个动作  
        if ( m_split == 1 )//如果第一个动作时长占比==1  
            new_t = 1;//设置第二个动作已完成  
        else  
            new_t = (t-m_split) / (1 - m_split );//计算出第二个动作新的播放进度  
    }  

      CCSpawn也会有这个问题,所以CCSpawn也无法执行加入其中的CCRepeatForever动作。

      CCRepeatForever的反转动作也是无效了,一个不会停止的动作从什么地方开始反转?当然你可以先把动作反转了再加入CCRepeatForever中,这是没问题的。

    (3)解决方案

    对于同时动作,不使用CCSpawn,采用分别执行

    close->runAction(blink);  
    close->runAction(repeat);  

    对于连续动作,不直接往CCSequence中加入CCRepeatForever,而是把CCRepeatForever放入瞬时动作CCCallFunc中,再把CCCallFunc加入CCSequence中执行。

    close=CCSprite::create("CloseNormal.png");  
    CCBlink* blink=CCBlink::create(0.5f,10);  
    CCCallFunc* callFunc=CCCallFunc::create(this,callfunc_selector(TestScene::repeatFunc));//创建CCCallFunc对象  
    CCSequence* sequence=CCSequence::create(blink,callFunc,NULL);//把CCCallFunc对象加入CCSequence中  
    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);  

    对于CCAnimation帧动画,可以设置循环属性,而不使用CCRepeatForever。

    animation->setLoops(-1);  

    (4)虽然CCRepeatForever也同样继承于CCActionInterval,理论上是延时动作的子类,但是和一般的延时动作又有很大的不同,所以平时在使用的时候必须很小心,不能当成一般的CCActionInterval使用。

  • 相关阅读:
    (转)十分钟搞定CSS选择器
    (转)我所理解的OOP——UML六种关系
    闲话:你今天OO了吗?
    oledb方式读取excel文件
    (转)asp.net 高质量缩略图
    (转载)重温SQL——行转列,列转行
    第九讲,资源表解析
    第八讲,TLS表(线程局部存储)
    第七讲,重定位表
    第六讲,导出表
  • 原文地址:https://www.cnblogs.com/yyxt/p/4059087.html
Copyright © 2011-2022 走看看