好记性不如铅笔头

C && C++, cocos2dx, 编程

cocos2dx学习笔记:场景切换动画及自定义动画

cocos2dx里面有很多场景切换动画,使用起来也很简单,最简单的代码如下:

CCScene* newScene = new TransitionsTestScene();
newScene = CCTransitionProgressRadialCW::create(timeNeed, newScene);
CCDirector::sharedDirector()->replaceScene(newScene);

CONTENTS [hide]

场景切换动画代码:

首先看下场景切换动画的继承关系:

CCTransition.h:
class CC_DLL CCTransitionScene : public CCScene
{。。。。。}
class CC_DLL CCTransitionSceneOriented : public CCTransitionScene
{。。。。。}
class CC_DLL CCTransitionRotoZoom : public CCTransitionScene
{。。。。。}
class CC_DLL CCTransitionJumpZoom : public CCTransitionScene
{。。。。。}
。。。。

可以看出,cocos2dx内部的场景切换动画继承自CCTransitionScene这个类,而CCTransitionScene这个类继承自CCScene,因此也是个场景类。
参考之前的【 笔记 】可以知道,CCTransitionScene在场景切换动画中担当了桥梁和动画播放的功能。
下面仔细分析下CCTransitionScene类的部分代码:

。。。。。
。。。。。
//==>>create函数,内部调用了initWithDuration,
//==>>传入参数scene是新的场景的实例。
CCTransitionScene * CCTransitionScene::create(float t, CCScene *scene)
{
    CCTransitionScene * pScene = new CCTransitionScene();
    if(pScene && pScene->initWithDuration(t,scene))
。。。。。
}
 
bool CCTransitionScene::initWithDuration(float t, CCScene *scene)
{
    CCAssert( scene != NULL, "Argument scene must be non-nil");
 
    if (CCScene::init())
    {
        m_fDuration = t;
 
        // retain
        m_pInScene = scene;
        m_pInScene->retain();
        m_pOutScene = CCDirector::sharedDirector()->getRunningScene();
        if (m_pOutScene == NULL)
        {
            m_pOutScene = CCScene::create();
            m_pOutScene->init();
        }
        m_pOutScene->retain();
//==>>通过上述代码我们可以知道:
//==>>m_pInScene:是新的场景的实例
//==>>m_pOutScene:是当前的场景的实例
 
        CCAssert( m_pInScene != m_pOutScene, "Incoming scene must be different from the outgoing scene" );
         
//==>>注意这里调用了函数sceneOrder,该函数是虚函数,可以被子类重写。
//==>>该函数的作用是设置当场景切换时新/旧场景哪个在上面。
        sceneOrder();
。。。。。
}
//==>>默认的设置为场景切换时,旧的场景绘制在上面,新的场景绘制在下面。
void CCTransitionScene::sceneOrder()
{
    m_bIsInSceneOnTop = true;
}
 
void CCTransitionScene::draw()
{
    CCScene::draw();
//==>>根据m_bIsInSceneOnTop的值来绘制新/旧场景。
    if( m_bIsInSceneOnTop ) {
        m_pOutScene->visit();
        m_pInScene->visit();
    } else {
        m_pInScene->visit();
        m_pOutScene->visit();
    }
}
//==>>场景切换动画完成之后,需要调用finish方法。
//==>>该方法主要供继承类使用
void CCTransitionScene::finish()
{
    // clean up    
     m_pInScene->setVisible(true);
     m_pInScene->setPosition(ccp(0,0));
     m_pInScene->setScale(1.0f);
     m_pInScene->setRotation(0.0f);
     m_pInScene->getCamera()->restore();
  
     m_pOutScene->setVisible(false);
     m_pOutScene->setPosition(ccp(0,0));
     m_pOutScene->setScale(1.0f);
     m_pOutScene->setRotation(0.0f);
     m_pOutScene->getCamera()->restore();
 
    //[self schedule:@selector(setNewScene:) interval:0];
    this->schedule(schedule_selector(CCTransitionScene::setNewScene), 0);
 
}
//==>>由finish方法调用,主要用来完成最后的清理工作。
void CCTransitionScene::setNewScene(float dt)
{   
    CC_UNUSED_PARAM(dt);
 
    this->unschedule(schedule_selector(CCTransitionScene::setNewScene));
     
    // Before replacing, save the "send cleanup to scene"
    CCDirector *director = CCDirector::sharedDirector();
    m_bIsSendCleanupToScene = director->isSendCleanupToScene();
     
    director->replaceScene(m_pInScene);
     
    // issue #267
    m_pOutScene->setVisible(true);
}
//==>>该方法主要供继承类使用
void CCTransitionScene::hideOutShowIn()
{
    m_pInScene->setVisible(true);
    m_pOutScene->setVisible(false);
}
 
//==>>重写了onEnter函数,主要是为了保证onEnterXXX,onExitXXX调用流程的正确
// custom onEnter
void CCTransitionScene::onEnter()
{
    CCScene::onEnter();
     
    // disable events while transitions
    CCDirector::sharedDirector()->getTouchDispatcher()->setDispatchEvents(false);
     
    // outScene should not receive the onEnter callback
    // only the onExitTransitionDidStart
    m_pOutScene->onExitTransitionDidStart();
     
    m_pInScene->onEnter();
}
 
// custom onExit
void CCTransitionScene::onExit()
{
    CCScene::onExit();
     
    // enable events while transitions
    CCDirector::sharedDirector()->getTouchDispatcher()->setDispatchEvents(true);
     
    m_pOutScene->onExit();
 
    // m_pInScene should not receive the onEnter callback
    // only the onEnterTransitionDidFinish
    m_pInScene->onEnterTransitionDidFinish();
}
 
// custom cleanup
void CCTransitionScene::cleanup()
{
    CCScene::cleanup();
 
    if( m_bIsSendCleanupToScene )
        m_pOutScene->cleanup();
}

由于CCTransitionScene是个基类,真正的场景动画实现都是继承类实现的,这里分析几个场景切换动画的实现:

CCTransitionJumpZoom:
CCTransitionJumpZoom算是一个比较简单的动画实现,直接搞了一大堆动画:

CCTransitionJumpZoom* CCTransitionJumpZoom::create(float t, CCScene* scene)
{
。。。。。
}
//==>>重写了onEnter函数,在里面初始化动画效果。
void CCTransitionJumpZoom::onEnter()
{
//==>>先调用了父类的函数,保证流程正确。
    CCTransitionScene::onEnter();
 
//==>>构造一大堆动画
    CCSize s = CCDirector::sharedDirector()->getWinSize();
 
    m_pInScene->setScale(0.5f);
    m_pInScene->setPosition(ccp(s.width, 0));
    m_pInScene->setAnchorPoint(ccp(0.5f, 0.5f));
    m_pOutScene->setAnchorPoint(ccp(0.5f, 0.5f));
 
    CCActionInterval *jump = CCJumpBy::create(m_fDuration/4, ccp(-s.width,0), s.width/4, 2);
    CCActionInterval *scaleIn = CCScaleTo::create(m_fDuration/4, 1.0f);
    CCActionInterval *scaleOut = CCScaleTo::create(m_fDuration/4, 0.5f);
 
    CCActionInterval *jumpZoomOut = (CCActionInterval*)(CCSequence::create(scaleOut, jump, NULL));
    CCActionInterval *jumpZoomIn = (CCActionInterval*)(CCSequence::create(jump, scaleIn, NULL));
 
    CCActionInterval *delay = CCDelayTime::create(m_fDuration/2);
 
//==>>让两个场景分别执行不同的动画,达成弹跳的效果
    m_pOutScene->runAction(jumpZoomOut);
    m_pInScene->runAction
    (
        CCSequence::create
        (
            delay,
            jumpZoomIn,
//==>>这里可以看到,动画中的最后执行了父类的finish方法。
            CCCallFunc::create(this, callfunc_selector(CCTransitionScene::finish)),
            NULL
        )
    );
}

CCTransitionProgress:
CCTransitionProgress是一系列动画的父类,但是它也是CCTransitionScene的子类,它主要是实现一系列的进度条形式的动画。这里笔记下代码,看下它的实现方式:

class CC_DLL CCTransitionProgress : public CCTransitionScene
。。。。。
// CCTransitionProgress
void CCTransitionProgress::onEnter()
{
    CCTransitionScene::onEnter();
//==>>初始化了几个变量,子类可以重写该方法
    setupTransition();
     
    // create a transparent color layer
    // in which we are going to add our rendertextures
    CCSize size = CCDirector::sharedDirector()->getWinSize();
 
    // create the second render texture for outScene
    CCRenderTexture *texture = CCRenderTexture::create((int)size.width, (int)size.height);
    texture->getSprite()->setAnchorPoint(ccp(0.5f,0.5f));
    texture->setPosition(ccp(size.width/2, size.height/2));
    texture->setAnchorPoint(ccp(0.5f,0.5f));
 
    // render outScene to its texturebuffer
    texture->clear(0, 0, 0, 1);
    texture->begin();
    m_pSceneToBeModified->visit();
    texture->end();
//==>>上面的代码是进度条动画的核心。通过上面的代码,我们可以知道进度条动画的实现的方式为:
//==>>首先新建一个CCRenderTexture的实例,将其大小覆盖全屏,然后将m_pSceneToBeModified在CCRenderTexture上重新绘制一遍,
//==>>这样就得到了m_pSceneToBeModified的静态图片,通过将CCProgressTimer动画和静态图片相结合的方式,我们就可以看到一个
//==>>进度条的场景切换动画了,实际上场景的静态截图随着进度条不断变化实现的。
 
    //    Since we've passed the outScene to the texture we don't need it.
    if (m_pSceneToBeModified == m_pOutScene)
    {
        hideOutShowIn();
    }
 
//==>>子类会重写该方法,以便根据不同的进度条样式生成不同的进度条动画
    //    We need the texture in RenderTexture.
    CCProgressTimer *pNode = progressTimerNodeWithRenderTexture(texture);
 
    // create the blend action
    CCActionInterval* layerAction = (CCActionInterval*)CCSequence::create(
//==>>播放进度条动画
        CCProgressFromTo::create(m_fDuration, m_fFrom, m_fTo),
//==>>播放到最后调用父类的finish方法,完成场景切换
        CCCallFunc::create(this, callfunc_selector(CCTransitionProgress::finish)),
        NULL);
    // run the blend action
    pNode->runAction(layerAction);
 
    // add the layer (which contains our two rendertextures) to the scene
    addChild(pNode, 2, kCCSceneRadial);
}
。。。。。

在学习场景切换动画代码中,作者感觉这部分代码的实现比较怪异,每个新的场景动画都要复写父类的onEnter方法,而且要在复写的函数中调用父类的onEnter方法。作者的建议是CCTransitionScene类可以声明一个虚函数func专门供子类复写以实现不同的动画效果,CCTransitionScene可以在onEnter函数的最后调用func,这样子类就不需要复写onEnter方法,只需要复写func方法就可以了。一点点个人看法 呵呵~~

自定义的场景切换动画:

在特定的场景下我们可能想实现一些自定义的场景切换动画,来达到比较好的效果,这里作者笔记下一种实现方式:
首先看下cocos2dx的内部的场景切换动画,看下有没有相似的。比如作者这里想找一个类似于进度条的动画,但是进图条的样式作者想自定义,那么我们就可以先看下cocos2dx自带的进度条切换动画是如何实现的,然后我们就可以自己写一个类,继承CCTransitionScene来实现。代码如下:

头文件:

/************************************************************************/
/* by cstriker1407                                                      */
/************************************************************************/
 
#ifndef _CUSTOMTRANSPROGRESSINOUT_H_
#define _CUSTOMTRANSPROGRESSINOUT_H_
 
#include "cocos2d.h"
NS_CC_BEGIN
 
class CustomTransProgressInOut : public CCTransitionScene
{
public:
     
    //==>>t:切换用时间
    //==>>scene:新的场景
    //==>>pStencilFileName:自定义的图片
    //==>>direction:true->从里向外放大, false->从外向里缩小
    static CustomTransProgressInOut* create(float t, CCScene* scene, const char *pStencilFileName, bool direction);
    virtual void sceneOrder();
    virtual void onEnter();
    virtual void onExit();
     
protected:
    CCSprite *m_pStencil;
    bool     m_bDirection;
};
 
NS_CC_END
 
#endif /* _CUSTOMTRANSPROGRESSINOUT_H_ */

cpp文件:

#include "CustomTransProgressInOut.h"
NS_CC_BEGIN
 
CustomTransProgressInOut* CustomTransProgressInOut::create( float t, CCScene* scene, const char *pStencilFileName, bool direction)
{
    CustomTransProgressInOut* pScene = new CustomTransProgressInOut();
    if(pScene && pScene->initWithDuration(t, scene))
    {
        pScene->m_pStencil = CCSprite::create(pStencilFileName);
        pScene->m_bDirection = direction;
//==>>由于direction的改变会使场景的绘制顺序发生改变,这里手动调用sceneOrder,保证正确性
        pScene->sceneOrder();
 
        pScene->autorelease();
        return pScene;
    }
    CC_SAFE_DELETE(pScene);
    return NULL;
}
 
void CustomTransProgressInOut::sceneOrder()
{
    //==>>从里向外,当前场景在上,从外向里,新的场景在上
    m_bIsInSceneOnTop = !m_bDirection;
}
 
void CustomTransProgressInOut::onEnter()
{
    CCTransitionScene::onEnter();
 
    CCSize size = CCDirector::sharedDirector()->getWinSize();
 
    //==>>和进度条类似,首先新建一个CCRenderTexture,以便生成静态贴图。
    CCRenderTexture *texture = CCRenderTexture::create((int)size.width, (int)size.height);
    texture->getSprite()->setAnchorPoint(ccp(0.5f,0.5f));
    texture->setPosition(ccp(size.width/2, size.height/2));
    texture->setAnchorPoint(ccp(0.5f,0.5f));
    texture->clear(0, 0, 0, 1);
    texture->begin();
    //==>>根据不同的方向生成不同的贴图。
    if (m_bDirection)
    {
        m_pInScene->visit();
    }else
    {
        m_pOutScene->visit();
    }  
    texture->end();
 
    CCClippingNode *clippingNode = CCClippingNode::create();
    clippingNode->setStencil(m_pStencil);
 
    CCSprite *background = CCSprite::createWithTexture(texture->getSprite()->getTexture());
    background->setFlipY(true);
    clippingNode->addChild(background);
 
    clippingNode->setAlphaThreshold(GLfloat(0.5f));
    clippingNode->setAnchorPoint(ccp(0.5,0.5));
    clippingNode->setPosition(ccp(size.width/2, size.height/2));
    this->addChild(clippingNode, 2, 9999);
 
    //==>>计算最大和最小的缩放值
    CCSize orgSize = m_pStencil->getContentSize();
    CCSize maxSize = size;
    CCSize minSize = CCSizeMake(2,2);
     
    float maxScale = maxSize.height * 1.0f / orgSize.height * 2.0f;
    float minScale = minSize.height * 1.0f / orgSize.height;
 
    if (!m_bDirection)
    {
        float tmp = maxScale;
        maxScale = minScale;
        minScale = tmp;
    }
    m_pStencil->setScale(minScale);
 
    CCSequence* layerAction = CCSequence::create
        (
        CCScaleTo::create(m_fDuration, maxScale),
        CCCallFunc::create(this, callfunc_selector(CustomTransProgressInOut::finish)),
        NULL
        );
    m_pStencil->runAction(layerAction);
}
 
void CustomTransProgressInOut::onExit()
{
    this->removeChildByTag(9999);
    CCTransitionScene::onExit();
}
 
NS_CC_END

测试代码:

TransitionsTest.cpp:
CCTransitionScene* createTransition(int nIndex, float t, CCScene* s)
{
    return CustomTransProgressInOut::create(t,s, "stencil.png",true);
}

效果截图:

stencil文件:

效果文件:

发表评论

12 + 1 =

此站点使用 Akismet 来减少垃圾评论。了解我们如何处理您的评论数据