正文  游戏开发 > 游戏引擎 >

Cocos2d-X3.0 刨根问底(九)----- 场景切换(TransitionScene)源码分析

直接看TransitionScene的定义 复制代码 1 class CC_DLL TransitionScene : public Scene 2 { 3 public: 4  ...

直接看TransitionScene的定义
 
复制代码
 1 class CC_DLL TransitionScene : public Scene
 2 {
 3 public:
 4     /** Orientation Type used by some transitions
 5      */
 6     enum class Orientation
 7     {
 8         /// An horizontal orientation where the Left is nearer
 9         LEFT_OVER = 0,
10         /// An horizontal orientation where the Right is nearer
11         RIGHT_OVER = 1,
12         /// A vertical orientation where the Up is nearer
13         UP_OVER = 0,
14         /// A vertical orientation where the Bottom is nearer
15         DOWN_OVER = 1,
16     };
17     
18     /** creates a base transition with duration and incoming scene */
19     static TransitionScene * create(float t, Scene *scene);
20 
21     /** called after the transition finishes */
22     void finish(void);
23 
24     /** used by some transitions to hide the outer scene */
25     void hideOutShowIn(void);
26 
27     //
28     // Overrides
29     //
30     virtual void draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated) override;
31     virtual void onEnter() override;
32     virtual void onExit() override;
33     virtual void cleanup() override;
34     
35 CC_CONSTRUCTOR_ACCESS:
36     TransitionScene();
37     virtual ~TransitionScene();
38 
39     /** initializes a transition with duration and incoming scene */
40     bool initWithDuration(float t,Scene* scene);
41     
42 protected:
43     virtual void sceneOrder();
44     void setNewScene(float dt);
45 
46     Scene *_inScene;
47     Scene *_outScene;
48     float _duration;
49     bool _isInSceneOnTop;
50     bool _isSendCleanupToScene;
51 
52 private:
53     CC_DISALLOW_COPY_AND_ASSIGN(TransitionScene);
54 };
复制代码
这个类并不大,从类的头信息继承关系上可以看出场景切换的类其实也是一个场景。
 
老套路,先从成员变量开始分析。
 
TransitionScene 类一共有五个成员变量这五个变量从变量命名上就已经能猜得差不多了。
 
1     Scene *_inScene;            // 场景切换 切入的场景指针
2     Scene *_outScene;           // 场景切换 切出的场景指针 
3     float _duration;            // 场景切换 消耗的时间 
4     bool _isInSceneOnTop;       // 场景切换 描述切入场景与切出场景的渲染层次关系,true 切入场景在切出场景的顶层 
5     bool _isSendCleanupToScene; // 场景切换 标记是否已经给切出场景发送了清理的命令。 
到这里,可以猜出TransitionScene 类到底是以一个什么样的过程来实现 场景切换的,
 
TransitionScene 是一个中介场景,它左手拿着要切入的场景(_inScene),右手拿着要切出的场景(_outScene),让这两个入出场景在自己身上以一种华丽的方式完成切场的过程,最后这个中介场景退出,把舞台交给新切入的场景。
 
 
 
上面我们猜测了一下TransitionScene实现场景切换的原理,我们便有了好奇,TransitionScene类实现切入场景与切出场景的具体过程是什么呢?我们猜测的切换原理是否正确呢?
 
 
 
带着问题我们继续看源码来找答案。
 
 
 
我们先从TransitionScene的创建方法开始。
 
 
 
TransitionScene的构造函数是个空函数忽略,我们看一下TransitionScene::Create这个静态方法开始分析。
 
复制代码
 1 TransitionScene * TransitionScene::create(float t, Scene *scene)
 2 {
 3     TransitionScene * pScene = new TransitionScene();
 4     if(pScene && pScene->initWithDuration(t,scene))
 5     {
 6         pScene->autorelease();
 7         return pScene;
 8     }
 9     CC_SAFE_DELETE(pScene);
10     return nullptr;
11 }
复制代码
看到create函数的结构 熟悉的不能再熟悉了,在Cocos2d-x基本年有Node类及子类的创建都是这个结构。
 
下面我们看一下initWithDuration这个初始化方法。
 
复制代码
 1 bool TransitionScene::initWithDuration(float t, Scene *scene)
 2 {
 3     CCASSERT( scene != nullptr, "Argument scene must be non-nil");
 4 
 5     if (Scene::init()) 
 6     {
 7         _duration = t;
 8 
 9         // retain
10         _inScene = scene;
11         _inScene->retain();
12         _outScene = Director::getInstance()->getRunningScene();
13         if (_outScene == nullptr)
14         {
15             _outScene = Scene::create();
16         }
17         _outScene->retain();
18 
19         CCASSERT( _inScene != _outScene, "Incoming scene must be different from the outgoing scene" );
20         
21         sceneOrder();
22 
23         return true;
24     }
25     else
26     {
27         return false;
28     }
29 }
复制代码
从这个函数的参数来判断,这个初始化函数的作用是,通过指定场景切换的持续时间与要切换的场景指针来初始化一个场景切换中介场景(TransitionScene)的实例。
 
TransitionScene实例初始过程
 
调用基类Scene初始化方法。Scene::init
将传入的持续时间参数进行赋值 _duration = t;
切入场景赋值 _inScene = scene;注意这里增加了一次对切入场景的引用,因为在中介场景引用了一次切入场景,所以增加了一次引用计数。
切出场景赋值 这里的切出场景当然就是指的当前Director类正在运行的场景 通过 Director::getInstance()->getRunningScene(); 得到当前正在运行的场景赋值给_outScene还有一个判断,如果没有正在运行的场景那么会创建一个空的场景做为切出场景,并且增加了一次对切也场景的引用记数。
调用了sceneOrder()函数,从这个函数的命名上来看是对场景进行了一次排序,具体都干了些啥事呢?下在对sceneOrder进行分析。
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();
}
复制代码
函数过程分析:
 
调用Scene基类的onEnter
将事件分发器设置为不可用状态,场景切换是一个动画过程,在这个过程中不处理事件,以免发生意想不到的错误。
调用切出场景的onExitTransitionDidStart方法。看过以前章节的朋友应该还有印象,这个方法是在Node基类里面定义的,函数意义为场景变化切出开始时的回调方法。中介场景进入舞台当然是当前场景离开的时候在这里调用这个方法合情合理
最后调用了切入场景的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();
}
复制代码
函数过程分析:
 
调用基类Scene的onExit方法。
恢复了事件分发器的可用状态。
调用了切出场景的离开回调
调用了切入场景的进入完成回调。
我们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);
    }
}
复制代码
draw方法没什么难度,就是根据_isInSceneOnTop这里记录的切入切出两个场景层次关系,来做分别的渲染。
 
复制代码
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类里的方法我们都分析过了,可能大家有一些疑问
 
只分析到了一些场景切入舞台与场景离开舞台的回调,而没有看到那些动态的场景切换的过程。
中介场景是如何进入舞台的?如何使用中介场景来做场景切换呢?
finish函数并没有被调用,那么这个finish好像名存实亡。
带着疑问,我们继续在源码中寻找答案。
 
 
 
我们看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实现,是有这么点意思了,里面有提到动画,延时……. 开始分析。