上一章我们分析了Scene与Layer相关类的源码,对Cocos2d-x的场景有了初步了解,这章我们来分析一下场景变换TransitionScene源码。
直接看TransitionScene的定义
class CC_DLL TransitionScene : public Scene { public: /** Orientation Type used by some transitions */ enum class Orientation { /// An horizontal orientation where the Left is nearer LEFT_OVER = 0, /// An horizontal orientation where the Right is nearer RIGHT_OVER = 1, /// A vertical orientation where the Up is nearer UP_OVER = 0, /// A vertical orientation where the Bottom is nearer DOWN_OVER = 1, }; /** creates a base transition with duration and incoming scene */ static TransitionScene * create(float t, Scene *scene); /** called after the transition finishes */ void finish(void); /** used by some transitions to hide the outer scene */ void hideOutShowIn(void); // // Overrides // virtual void draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated) override; virtual void onEnter() override; virtual void onExit() override; virtual void cleanup() override; CC_CONSTRUCTOR_ACCESS: TransitionScene(); virtual ~TransitionScene(); /** initializes a transition with duration and incoming scene */ bool initWithDuration(float t,Scene* scene); protected: virtual void sceneOrder(); void setNewScene(float dt); Scene *_inScene; Scene *_outScene; float _duration; bool _isInSceneOnTop; bool _isSendCleanupToScene; private: CC_DISALLOW_COPY_AND_ASSIGN(TransitionScene); };
老套路,先从成员变量开始分析。
TransitionScene 类一共有五个成员变量这五个变量从变量命名上就已经能猜得差不多了。
Scene *_inScene; // 场景切换 切入的场景指针 Scene *_outScene; // 场景切换 切出的场景指针 float _duration; // 场景切换 消耗的时间 bool _isInSceneOnTop; // 场景切换 描述切入场景与切出场景的渲染层次关系,true 切入场景在切出场景的顶层 bool _isSendCleanupToScene; // 场景切换 标记是否已经给切出场景发送了清理的命令。
到这里,可以猜出TransitionScene 类到底是以一个什么样的过程来实现 场景切换的,
TransitionScene 是一个中介场景,它左手拿着要切入的场景(_inScene),右手拿着要切出的场景(_outScene),让这两个入出场景在自己身上以一种华丽的方式完成切场的过程,最后这个中介场景退出,把舞台交给新切入的场景。
上面我们猜测了一下TransitionScene实现场景切换的原理,我们便有了好奇,TransitionScene类实现切入场景与切出场景的具体过程是什么呢?我们猜测的切换原理是否正确呢?
带着问题我们继续看源码来找答案。
我们先从TransitionScene的创建方法开始。
TransitionScene的构造函数是个空函数忽略,我们看一下TransitionScene::Create这个静态方法开始分析。
TransitionScene * TransitionScene::create(float t, Scene *scene) { TransitionScene * pScene = new TransitionScene(); if(pScene && pScene->initWithDuration(t,scene)) { pScene->autorelease(); return pScene; } CC_SAFE_DELETE(pScene); return nullptr; }
看到create函数的结构 熟悉的不能再熟悉了,在Cocos2d-x基本年有Node类及子类的创建都是这个结构。
下面我们看一下initWithDuration这个初始化方法。
bool TransitionScene::initWithDuration(float t, Scene *scene) { CCASSERT( scene != nullptr, "Argument scene must be non-nil"); if (Scene::init()) { _duration = t; // retain _inScene = scene; _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(); return true; } else { return false; } }
从这个函数的参数来判断,这个初始化函数的作用是,通过指定场景切换的持续时间与要切换的场景指针来初始化一个场景切换中介场景(TransitionScene)的实例。
TransitionScene实例初始过程
void TransitionScene::sceneOrder() { _isInSceneOnTop = true; }
sceneOrder 这是一个虚函数,里面的过程很简单,只是设置了_isInSceneOnTop这个标记,来指定切入场景与切出场景的层次关系,就是谁在谁的上面。
TransitionScene 的实例创建我们分析完了,下面来寻找场景切换的过程是怎么样实现的。
看一下TransitionScene 类头文件发现了以下几个函数。
virtual void draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated) override; virtual void onEnter() override; virtual void onExit() override; virtual void cleanup() override;
从命名上初步分析这四个函数的作用分别是
draw 渲染方式
onEnter 中介场景进入舞台时调用
onExit 中介场景退出舞台时调用
clearup 清理场景方法。
一个一个分析先看onEnter
void TransitionScene::onEnter() { Scene::onEnter(); // disable events while transitions _eventDispatcher->setEnabled(false); // outScene should not receive the onEnter callback // only the onExitTransitionDidStart _outScene->onExitTransitionDidStart(); _inScene->onEnter(); }
函数过程分析:
再看onExit方法。
void TransitionScene::onExit() { Scene::onExit(); // enable events while transitions _eventDispatcher->setEnabled(true); _outScene->onExit(); // _inScene should not receive the onEnter callback // only the onEnterTransitionDidFinish _inScene->onEnterTransitionDidFinish(); }
函数过程分析:
我们onEnter和onExit这两个方法来比较着看。
通过分析,我们可以了解切入切出场景 进入舞台到离开舞台的函数调用顺序,在这里小结一下。
场景进入舞台 ---->离开舞台。 (这里说的舞台可以理解成就是屏幕上可以显示的区域)
onEnter(进入舞台) ---> onEnterTransitionDidFinish(进入完成) ---> onExitTransitionDidStart(开始离开舞台) ---> onExit(离开)
了解了场景的进入舞台函数调用顺序,我们就可以理解中介场景的onExit与onEnter这两个函数都是干了些什么。
中介场景进入舞台的时候正是切出场景开始离开舞台与切入场景进入舞台的时候
中介场景离开始了舞台,切入场景完成了进入舞台,切出场景离开了舞台。
就在这个时候中介场景与切出场景都离开始了舞台,在舞台上就留下了切入的场景,至此完成了场景的切换。
接下来我们看一下另外两个虚函数
void TransitionScene::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated) { Scene::draw(renderer, transform, transformUpdated); if( _isInSceneOnTop ) { _outScene->visit(renderer, transform, transformUpdated); _inScene->visit(renderer, transform, transformUpdated); } else { _inScene->visit(renderer, transform, transformUpdated); _outScene->visit(renderer, transform, transformUpdated); } }
void TransitionScene::cleanup() { Scene::cleanup(); if( _isSendCleanupToScene ) _outScene->cleanup(); }
clearup方法也是先调用其它的clearup然后根据切出场景是否已经清除过_isSendCleanupToScene这个标记来对切出场景进行清理操作。
下面我们看一下TransitionScene还有哪些方法
void TransitionScene::finish() { // clean up _inScene->setVisible(true); _inScene->setPosition(Point(0,0)); _inScene->setScale(1.0f); _inScene->setRotation(0.0f); _inScene->setAdditionalTransform(nullptr); _outScene->setVisible(false); _outScene->setPosition(Point(0,0)); _outScene->setScale(1.0f); _outScene->setRotation(0.0f); _outScene->setAdditionalTransform(nullptr); //[self schedule:@selector(setNewScene:) interval:0]; this->schedule(schedule_selector(TransitionScene::setNewScene), 0); }
这个finish方法,大家一看就能知道是场景切换完成时调用的方法
里面的函数过程也很简单,设置切入 场景为显示状态,切出场景不显示,并且将场景的旋转、缩放、位置都恢复成初始状态。
值得注意的是最后将一个回调函数加入到了定时调度器里 TransitionScene::setNewScene 这个回调间隔时间为0也就是在下一帧就会被调用。
下面看一下这个setNewScene这个函数是干什么的。
void TransitionScene::setNewScene(float dt) { CC_UNUSED_PARAM(dt); this->unschedule(schedule_selector(TransitionScene::setNewScene)); // Before replacing, save the "send cleanup to scene" Director *director = Director::getInstance(); _isSendCleanupToScene = director->isSendCleanupToScene(); director->replaceScene(_inScene); // issue #267 _outScene->setVisible(true); }
这个函数有一个参数但并没有使用,可能是预留的。
这个函数里面取到了Director类是清除场景的标记,并且赋值给了_isSendCleanupToScene这个变量。
注意这一行代码 director->replaceScene(_inScene);
我们知道,Director管理着场景,同时只能有一个runningScene,这行代码实际上是真正的把舞台通过Director交给了我们要切入的场景(_inScene)
void TransitionScene::hideOutShowIn() { _inScene->setVisible(true); _outScene->setVisible(false); }
函数hideOutShowIn隐藏切出场景显示切入场景,没什么可多说的。
分析到这里,所有TransitionScene类里的方法我们都分析过了,可能大家有一些疑问
带着疑问,我们继续在源码中寻找答案。
我们看CCTransition.h这个头文件里面除了TransitionScene还定义了好多TransitionScene类的子类,下面我们选择一个类来分析一下
/** @brief TransitionRotoZoom: Rotate and zoom out the outgoing scene, and then rotate and zoom in the incoming */ class CC_DLL TransitionRotoZoom : public TransitionScene { public: static TransitionRotoZoom* create(float t, Scene* scene); // // Overrides // virtual void onEnter() override; protected: TransitionRotoZoom(); virtual ~TransitionRotoZoom(); private: CC_DISALLOW_COPY_AND_ASSIGN(TransitionRotoZoom); };
看一下这个类的注释,切出场景缩放旋转着移出,切入场景旋转缩放着移入。
TransitionRotoZoom 类继承 TransitionScene类 并没有增加什么属性与方法,而是重载了onEnter方法,那么onEnter里面有什么变化呢?
跟进代码:
void TransitionRotoZoom:: onEnter() { TransitionScene::onEnter(); _inScene->setScale(0.001f); _outScene->setScale(1.0f); _inScene->setAnchorPoint(Point(0.5f, 0.5f)); _outScene->setAnchorPoint(Point(0.5f, 0.5f)); ActionInterval *rotozoom = (ActionInterval*)(Sequence::create ( Spawn::create ( ScaleBy::create(_duration/2, 0.001f), RotateBy::create(_duration/2, 360 * 2), nullptr ), DelayTime::create(_duration/2), nullptr )); _outScene->runAction(rotozoom); _inScene->runAction ( Sequence::create ( rotozoom->reverse(), CallFunc::create(CC_CALLBACK_0(TransitionScene::finish,this)), nullptr ) ); }
看到了onEnter实现,是有这么点意思了,里面有提到动画,延时……. 开始分析。
onEnter函数过程:
注意:这里用了ActionInterval 等动画相关的类,在这里我们只要知道这些动画类的大概作用就可以了,后面的章节我们一个一个地分析。
小鱼写了一个Demo给大家展示一下 TransitionRotoZoom 切换场景的过程。
通过对 TransitionScene 类的派生类 TransitionRotoZoom 的分析,我们了解了TransitionScene类的动作过程,总结如下:
现在上面的三个疑问基本都找到答案了。
这里还有一点可能有些读者会产生疑问。
小鱼在这里一起和大家再分析一下场景切换与Director类及引擎的关联。
先说一下TransitionScene 类的使用方法。
看以下代码。
auto visibleSize = Director::getInstance()->getVisibleSize(); Scene *myScene = Scene::create(); Sprite * sp = Sprite::create("mv2.jpg"); sp->setPosition( visibleSize.width / 2, visibleSize.height / 2 ); myScene->addChild( sp ); TransitionRotoZoom *tranScene = TransitionRotoZoom::create( 2.0, myScene ); Director::getInstance()->replaceScene( tranScene );
这个例子是我上面gif图的代码片断,主要用到了 TransitionRotoZoom 这种变化。
前五行代码很简单,就是我创建了一个新的场景我们叫场景2,将一个图片放到了场景2上面。
第六行代码,我创建了一个TransitionRotoZoom场景切换对象实例,并且设置切换时间为2秒,将场景2传入做为切出场景。
第七行代码 ,找到Director实例对象,用场景2来替换当前场景。
就这几行代码就实现了上面的效果。
一句话,在使用场景切换的时候就是将场景变换的中介场景加入到舞台上。上面我们已经分析了中介场景,在结束的时候会真正的将切入场景replace到舞台上面。
为了加深理解,我们以上面的实例代码为例子在这里再分析几断Director相关函数的代码。
我们先看replaceScene都干了些什么。
void Director::replaceScene(Scene *scene) // 这里传入的scene就是我们的中介场景 myScene { CCASSERT(_runningScene, "Use runWithScene: instead to start the director"); CCASSERT(scene != nullptr, "the scene should not be null"); if (_nextScene)// 这里判断如果已经指定了下一个要切的场景那就在这里面就把下个场景释放掉。 { if (_nextScene->isRunning()) { _nextScene->onExitTransitionDidStart(); _nextScene->onExit(); } _nextScene->cleanup(); _nextScene = nullptr; } // 这里将myScene放到场景栈顶,并且重新修改了_nextScene为 myScene ssize_t index = _scenesStack.size(); _sendCleanupToScene = true; _scenesStack.replace(index - 1, scene); _nextScene = scene; } // 函数结束后现在Director里面的_nextScene就是我们的myScene。 在director每一帧的主循环里我们找一下对_nextScene的处理。
void DisplayLinkDirector::mainLoop() { if (_purgeDirectorInNextLoop) { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (! _invalid) { drawScene();// 其它的不用看,我们再跟进drawScene找找对 _nextScene的处理。 // release the objects PoolManager::getInstance()->getCurrentPool()->clear(); } }
void Director::drawScene() { ………… // 上面省略
/* to avoid flickr, nextScene MUST be here: after tick and before draw. XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */ if (_nextScene)// 在drawScene里面有对_nextScene处理,这时_nextScene就是我们上面会话的myScene { setNextScene(); } kmGLPushMatrix();
………… // 下面省略 }
void Director::setNextScene() {// 下面两个转换大家要注意一下,此时的_runningScene是我们的场景1 就是一个普通的scene 所以在做dynamic_cast 转换时为null bool runningIsTransition = dynamic_cast<TransitionScene*>(_runningScene) != nullptr; //这里runningIsTransition == false bool newIsTransition = dynamic_cast<TransitionScene*>(_nextScene) != nullptr; //这里newIsTransition = true // If it is not a transition, call onExit/cleanup if (! newIsTransition) // 因为_nextScene是 transiton类型所以这个if判断里面不会走进来 { 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的引用计数,因为_runningScene要被_nextScene替代 { _runningScene->release(); } _runningScene = _nextScene; // _nextScene增加了一次引用计数 _nextScene->retain(); _nextScene = nullptr; if ((! runningIsTransition) && _runningScene) // 这个判断会进入,大家如果没弄明白仔细想想 虽然现在的_runningScene是Transition类型但我们取得 runningIsTransition值的时候是还没有将_nextScene进行替换。 { _runningScene->onEnter(); // 终于找到了,在这里调用了中介场景 myScene的 onEnter 然后就有一系列的 旋转、缩放变化。这就是我们前面分析TransitionScene 这个类的过程联系起来了。直到myScene的finish方法将场景2换到舞台上。 _runningScene->onEnterTransitionDidFinish(); } }
好啦,今天的内容基本就这些了。
通过上面的分析我们了 Cocos2d-x的场景变化是怎么做的。这里我只给了大家一个实例,如果想多了解Cocos2d-x都准备了哪些场景变化给我们,不妨把CCTransition.h里面的类做成Demo都试一下。
今天我们在分析中碰到了与 Action有关的类,现在我们只知道它与动画变化有关系,那么Action系列类究竟都是些什么玩意呢?
好,搞定它,我们下章就对Action相关源码做分析。
Cocos2d-X3.0 刨根问底(九)----- 场景切换(TransitionScene)源码分析,布布扣,bubuko.com
Cocos2d-X3.0 刨根问底(九)----- 场景切换(TransitionScene)源码分析
原文地址:http://blog.csdn.net/mmidd/article/details/37660661