前言:cocos2dx引擎中自带了几十种场景切换的动画,可以使场景切换的过程中平滑过渡,不至于太生硬。那么问题来了,这些动画是怎么实现的呢?如果觉得系统自带的场景切换动画太丑陋,或者满足不了我们游戏的个性化需求,这时我们就完全可以自定义。
PS: 使用引擎版本3.10
一、引擎中怎么实现场景切换动画的?
随便查看一个场景切换动画类(比如TransitionMoveInR、TransitionSlideInL、TransitionShrinkGrow ...等等)的源码,可以看到他们都是继承自Scene类的,那么就说明他们本身就是一个场景。
下面我们跟踪一遍场景切换代码的流程,就可以清晰的明白其中的原理了:
1、先定义一个我们自己需要切换到的普通场景:auto pScene = HomeScene::createScene(); (这个很简单,没什么好说的)
2、使用任意一个场景切换动画包装一下这个场景:auto pTransitionMove = TransitionMoveInR::create(0.6f, pScene);
这段代码,跟进create源码中可以看到调用了bool TransitionScene::initWithDuration(float t,Scene *scene)方法进行初始化:
bool TransitionScene::initWithDuration(float t, Scene *scene)
{
CCASSERT( scene != nullptr, "Argument scene must be non-nil");
if (Scene::init())
{
_duration = t;
// retain
_inScene = scene; // 我们需要切换到的场景(下一个场景HomeScene)
_inScene->retain();
_outScene = Director::getInstance()->getRunningScene(); // 当前显示的场景
if (_outScene == nullptr)
{
_outScene = Scene::create();
}
_outScene->retain();
CCASSERT( _inScene != _outScene, "Incoming scene must be different from the outgoing scene" );
sceneOrder(); // 设置_isInSceneOnTop变量,控制播放动画时,_inScene和_outScene绘制的层级(这里默认就是设置_inScene在上面)
return true;
}
else
{
return false;
}
}
可以看到,这里并没有立即把场景切换到我们的HomeScene场景中去,而只是在TransitionMoveInR(也是一个场景)中简单的保存了下来。
3、执行场景切换代码:Director::getInstance()->replaceScene(pTransitionMove); 跟进replaceScene方法
void Director::replaceScene(Scene *scene)
{
//CCASSERT(_runningScene, "Use runWithScene: instead to start the director");
CCASSERT(scene != nullptr, "the scene should not be null");
if (_runningScene == nullptr) {
runWithScene(scene);
return;
}
if (scene == _nextScene)
return;
if (_nextScene)
{
if (_nextScene->isRunning())
{
_nextScene->onExit();
}
_nextScene->cleanup();
_nextScene = nullptr;
}
ssize_t index = _scenesStack.size();
_sendCleanupToScene = true;
_scenesStack.replace(index - 1, scene); // 将场景栈(其实就是一个Vector)中保存的当前场景替换为TransitionMoveInR场景
_nextScene = scene;// 到这里就大概可以看出来了,本质上是先切换到TransitionMoveInR场景,再切换到HomeScene场景,多么巧妙的过渡!
}
void Director::setNextScene()
{
bool runningIsTransition = dynamic_cast<TransitionScene*>(_runningScene) != nullptr;
bool newIsTransition = dynamic_cast<TransitionScene*>(_nextScene) != nullptr;
// If it is not a transition, call onExit/cleanup
if (! newIsTransition)
{
if (_runningScene)
{
_runningScene->onExitTransitionDidStart();
_runningScene->onExit();
}
// issue #709. the root node (scene) should receive the cleanup message too
// otherwise it might be leaked.
if (_sendCleanupToScene && _runningScene)
{
_runningScene->cleanup();
}
}
if (_runningScene)
{
_runningScene->release();
}
_runningScene = _nextScene; // 这是当前运行的场景_runningScene就为TransitionMoveInR场景了
_nextScene->retain();
_nextScene = nullptr;
if ((! runningIsTransition) && _runningScene) // 执行TransitionMoveInR场景的onEnter()和onEnterTransitionDidFinish()方法
{
_runningScene->onEnter();
_runningScene->onEnterTransitionDidFinish();
}
}
5、跟进TransitionMoveInR场景的 onEnter() 方法(即父类TransitionMoveInL的 onEnter() 方法):
void TransitionMoveInL::onEnter()
{
TransitionScene::onEnter();
this->initScenes(); // 初始化场景
ActionInterval *a = this->action();
//_inScene在上面第2点中说了保存的就是HomeScene场景。这里就执行了一个Action动作,就是我们看到的场景切换动画!(因此自定义动画只需要自己在这里修改即可)
_inScene->runAction(Sequence::create(this->easeActionWithAction(a), CallFunc::create(CC_CALLBACK_0(TransitionScene::finish,this)), nullptr)); // 动画执行完了之后调用回调方法TransitionScene::finish()}
6、跟进回调方法TransitionScene::finish() :
void TransitionScene::finish()
{
// clean up
_inScene->setVisible(true);
_inScene->setPosition(0,0);
_inScene->setScale(1.0f);
_inScene->setRotation(0.0f);
_inScene->setAdditionalTransform(nullptr);
_outScene->setVisible(false);
_outScene->setPosition(0,0);
_outScene->setScale(1.0f);
_outScene->setRotation(0.0f);
_outScene->setAdditionalTransform(nullptr);
//[self schedule:@selector(setNewScene:) interval:0];
this->schedule(CC_SCHEDULE_SELECTOR(TransitionScene::setNewScene), 0);// 处理非常简单,但是这里为什么要用定时器,而不直接调用,有点奇怪!
}
void TransitionScene::setNewScene(float dt)
{
CC_UNUSED_PARAM(dt);
this->unschedule(CC_SCHEDULE_SELECTOR(TransitionScene::setNewScene));
// Before replacing, save the "send cleanup to scene"
Director *director = Director::getInstance();
_isSendCleanupToScene = director->isSendCleanupToScene();
director->replaceScene(_inScene); //关键点就在这里! 这里有调用了一遍replaceScene方法,而_inScene就是我们的HomeScene场景,这下就明白了吧!
// issue #267
_outScene->setVisible(true);
}
二、如何自定义一个场景切换动画?
明白了原理,就好办了,不多说。下面就直接给一个我的项目中用到的一个动画吧,很简单!就是实现类似iOS里面的一个功能:当我切换场景到下一个场景后(场景动画是从右向左滑入界面),我需要回退到上一个场景(场景动画是当前界面向右滑出),但是cocos2dx中自带没有这个动画!
1、头文件.hpp:
#ifndef XCaseTransitionMoveOutR_hpp
#define XCaseTransitionMoveOutR_hpp
#include <stdio.h>
#include "cocos2d.h"
USING_NS_CC;
class XCaseTransitionMoveOutR : public TransitionScene, public TransitionEaseScene {
public:
XCaseTransitionMoveOutR();
~XCaseTransitionMoveOutR();
static XCaseTransitionMoveOutR* create(float t, Scene* scene);
virtual bool init(float t, Scene* scene);
virtual ActionInterval* action(void);
virtual ActionInterval* easeActionWithAction(ActionInterval * action) override;
virtual void onEnter() override;
protected:
/** initializes the scenes */
virtual void initScenes();
};
#endif /* XCaseTransitionMoveOutR_hpp */
2、实现文件.cpp:
#include "XCaseTransitionMoveOutR.hpp"
XCaseTransitionMoveOutR::XCaseTransitionMoveOutR()
{
}
XCaseTransitionMoveOutR::~XCaseTransitionMoveOutR()
{
}
XCaseTransitionMoveOutR* XCaseTransitionMoveOutR::create(float t, Scene* scene)
{
auto newScene = new (std::nothrow) XCaseTransitionMoveOutR();
if(newScene && newScene->init(t, scene))
{
newScene->autorelease();
return newScene;
}
CC_SAFE_DELETE(newScene);
return nullptr;
}
bool XCaseTransitionMoveOutR::init(float t, Scene* scene)
{
bool isInit = false;
do{
CC_BREAK_IF(!TransitionScene::initWithDuration(t, scene));
_isInSceneOnTop = false;
isInit = true;
}while (0);
return isInit;
}
ActionInterval* XCaseTransitionMoveOutR::action(void)
{
Size s = Director::getInstance()->getWinSize();
return MoveTo::create(_duration, Vec2(s.width,0));
}
ActionInterval* XCaseTransitionMoveOutR::easeActionWithAction(ActionInterval * action)
{
return EaseOut::create(action, 2.0f);
}
void XCaseTransitionMoveOutR::onEnter()
{
TransitionScene::onEnter();
this->initScenes();
ActionInterval *a = this->action();
_outScene->runAction(Sequence::create(this->easeActionWithAction(a), CallFunc::create(CC_CALLBACK_0(TransitionScene::finish, this)), nullptr)
);
}
void XCaseTransitionMoveOutR::initScenes()
{
_outScene->setPosition(0, 0);
_inScene->setPosition(0, 0);
}