好记性不如铅笔头

C && C++, cocos2dx, 编程

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

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

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

CONTENTS

场景切换动画代码:

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

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                                                      */
/* http://116.62.110.235/blog                                        */
/************************************************************************/

#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文件:

效果文件:

发表评论

2 × 4 =

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