1. 程式人生 > >‎Cocos2d-x 學習筆記(3.2) TransitionScene 過渡場景和場景切換的過程

‎Cocos2d-x 學習筆記(3.2) TransitionScene 過渡場景和場景切換的過程

1. 簡介

過渡場景TransitionScene直接繼承了場景Scene。能夠在場景切換過程中實現“過渡”效果,而不是讓視窗在下一幀突然展示另一個場景。

2. create

建構函式:

TransitionScene::TransitionScene()
: _inScene(nullptr)
, _outScene(nullptr)
, _duration(0.0f)
, _isInSceneOnTop(false)
, _isSendCleanupToScene(false)
{
}

create方法有2個引數:float t, Scene *scene。呼叫了initWithDuration,傳入這兩個引數。

引數scene不能為空。

1. 成員變數時間_duration設為引數t,成員變數_inScene設為引數scene。過渡場景引用了引數場景,引數場景要retain。

2. 導演執行getRunningScene,把當前正執行的場景作為_outScene。

3. 如果當前正執行的場景為空,則呼叫Scene::create()建立一個空場景,空場景作為_outScene。

4. _outScene指向的場景被引用了,_outScene執行retain。

5. 執行sceneOrder(),決定inScene和outScene的繪製順序。該方法可以被子類重寫,只是設定了成員變數_isInSceneOnTop的值為true/false。

3. onEnter

場景的onEnter方法在該場景每次進入視窗時執行。

TransitionScene的子類會重寫onEnter方法,重寫後的方法體內會先呼叫父類TransitionScene的onEnter方法。

1. 首先呼叫父類Scene onEnter方法,實際上執行的是Scene的父類Node的onEnter方法。

2. 導演的事件分發器執行setEnabled(false),停止事件分發器的工作。

3. _outScene->onExitTransitionDidStart() 要退出的場景執行onExitTransitionDidStart。

4. _inScene->onEnter() 要出現的場景執行onEnter。

簡而言之,過渡的開始時,要退出的準備退出,要出現的開始出現。

4. onExit()

場景的onExit方法在該場景每次退出視窗時執行。

1. 首先呼叫父類Scene onExit方法,實際上執行的是Scene的父類Node的onExit方法。

2. 導演的事件分發器執行setEnabled(true),事件分發器的可以分發事件了。

3. _outScene->onExit(); 要退出的場景執行onExit,完全退出。

4. _inScene->onEnterTransitionDidFinish() 要出現的場景執行onEnterTransitionDidFinish。

簡而言之,過渡的結束時,要退出的完成退出,要進入的完成進入。此時,視窗只有我們設定的要進入的場景了。

5. draw(...)

過渡場景繪製方法的原始碼:

void TransitionScene::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    Scene::draw(renderer, transform, flags);

    if( _isInSceneOnTop ) {
        _outScene->visit(renderer, transform, flags);
        _inScene->visit(renderer, transform, flags);
    } else {
        _inScene->visit(renderer, transform, flags);
        _outScene->visit(renderer, transform, flags);
    }
}

可以看出,繪製根據_isInSceneOnTop決定inScene和outScene的繪製順序。

建構函式預設false。在create方法會呼叫sceneOrder()設定。子類可以重寫sceneOrder方法,實現TransitionScene不同子類的要進入場景和要退出場景繪製順序的不同。

6. 子類 TransitionJumpZoom

TransitionScene是所有過渡場景的父類,每種子類都是一種有不同特效的過渡場景。

這次用子類TransitionJumpZoom為例進行學習。

該過渡場景除去構造和析構僅2個方法。該過渡場景重寫了onEnter方法,說明在該方法包含了過渡特效的邏輯。

- TransitionJumpZoom::onEnter()

1. 首先執行父類TransitionScene::onEnter()。讓outScene(runningScene)準備退出。

2. 設定inScene和outScene在過渡開始時的初始屬性。

    _inScene->setScale(0.5f);
    _inScene->setPosition(s.width, 0);
    _inScene->setAnchorPoint(Vec2(0.5f, 0.5f));
    _outScene->setAnchorPoint(Vec2(0.5f, 0.5f));

3.建立3個動作。有跳躍和縮放

    ActionInterval *jump = JumpBy::create(_duration/4, Vec2(-s.width,0), s.width/4, 2);
    ActionInterval *scaleIn = ScaleTo::create(_duration/4, 1.0f);
    ActionInterval *scaleOut = ScaleTo::create(_duration/4, 0.5f);

4. 建立兩個動作佇列。

    auto jumpZoomOut = Sequence::create(scaleOut, jump, nullptr);
    auto jumpZoomIn = Sequence::create(jump, scaleIn, nullptr);

5. 兩個場景runAction。

    ActionInterval *delay = DelayTime::create(_duration/2);

    _outScene->runAction(jumpZoomOut);
    _inScene->runAction
    (
        Sequence::create
        (
            delay,
            jumpZoomIn,
            CallFunc::create(CC_CALLBACK_0(TransitionScene::finish,this)),
            nullptr
        )
    );

在要進入的場景的動作序列結束前,執行父類的TransitionScene::finish。

- TransitionScene finish

簡要的說,是設定inScene和outScene的屬性。從而對螢幕的視窗而言,過渡的特效執行完成,inScene可見,outScene不可見,兩場景的位置、旋轉、縮放都設為了預設值。

在屬性設定之後,執行:

    this->schedule(CC_SCHEDULE_SELECTOR(TransitionScene::setNewScene), 0);

該語句建了一個Timer,時間間隔為0,說明下一幀就要執行引數回撥函式setNewScene。

- TransitionScene setNewScene

1. 首先unschedule了上一幀的Timer。

2. 把導演屬性_sendCleanupToScene賦給TransitionScene屬性_isSendCleanupToScene。

3. 執行導演的replaceScene(_inScene),將inScene作為了導演的nextScene。

4. 最後outScene可見。

7. 場景切換的執行過程

這裡用replaceScene為例。

例子中,我們點選選單項,觸發replaceScene函式,引數是某個過渡場景。

過渡場景的開始 到onEnter()

1. 本幀的GLView接收到觸控事件(滑鼠點選選單項),觸發了回撥函式,回撥函式中新建了一個新場景和過渡場景,過渡場景的inScene為執行導演的replaceScene,引數為新場景。

2. replaceScene中:導演_sendCleanupToScene置true,新場景入棧,新場景成為nextScene。nextScene原本為NULL。

3. 本幀結束。進入下一幀。

4. 下一幀在scheduler update之後判斷到存在nextScene,執行setNextScene方法。

5. setNextScene:

void Director::setNextScene()
{
    _eventDispatcher->dispatchEvent(_beforeSetNextScene);

    bool runningIsTransition = dynamic_cast<TransitionScene*>(_runningScene) != nullptr; // false
    bool newIsTransition = dynamic_cast<TransitionScene*>(_nextScene) != nullptr; // true

     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;
    _nextScene->retain();
    _nextScene = nullptr; //nextScene至此成為了runningScene

    if ((! runningIsTransition) && _runningScene) //曾經的runningscene為false,執行
    {
        _runningScene->onEnter(); //過渡場景的onEnter
        _runningScene->onEnterTransitionDidFinish();
    }
    
    _eventDispatcher->dispatchEvent(_afterSetNextScene);
}

6. 執行過渡場景的onEnter:

  1. 執行Node onEnter。

  2. 執行TransitionScene onEnter,導演的eventDispatcher停用,要退出的場景onExitTransitionDidStart,要進入的場景onEnter。

  3. 為inScene和outScene分別建立動作序列。分別runAction。

過渡場景的結束 到onExit()

1. 過渡場景中,新場景動作序列最後一個動作是回撥動作CallFunc,呼叫TransitionScene finish(),在設定屬性之後,在下一幀通過Scheduler呼叫setNewScene方法。

2. setNewScene中:replaceScene(inScene),實現了nextScene指向新場景。此時,runningScene是過渡場景。

3. 在下下一幀會執行導演setNextScene,因為runningScene和nextScene都存在,runningScene過渡場景執行onExitTransitionDidStart onExit。

bool runningIsTransition = dynamic_cast<TransitionScene*>(_runningScene) != nullptr; // true
    bool newIsTransition = dynamic_cast<TransitionScene*>(_nextScene) != nullptr; //false

     if (! newIsTransition) //執行
     {
         if (_runningScene) //過渡場景onExit
         {
             _runningScene->onExitTransitionDidStart();
             _runningScene->onExit();
         }
 
         if (_sendCleanupToScene && _runningScene)
         {
             _runningScene->cleanup();
         }
     }
 //......

過渡場景的onExit中,要退出的場景執行onExit,完全退出,要出現的場景執行onEnterTransitionDidFinish,完全進入。同時,導演的eventDispatcher啟用。

cleanup()

繼續setNextScene,_sendCleanupToScene在replaceScene時置true,過渡場景執行TransitionScene cleanup()。

    Scene::cleanup();
    if( _isSendCleanupToScene )
        _outScene->cleanup();

呼叫了Node cleanup()。_isSendCleanupToScene由導演_sendCleanupToScene設定,為true時,對要退出的場景cleanup()。

_sendCleanupToScene

在導演replaceScene時置true,導演pushScene置false,導演popScene置true。

為true時,在導演setNextScene時,對導演的runningScene cleanup(),然後runningScene被nextScene替代。

如果此時runnningScene是過渡場景,過渡場景的cleanup()除了對自己cleanup,還要通過_isSendCleanupToScene判斷是否對要退出的場景cleanup。

_isSendCleanupToScene是在過渡場景動作結束時,導演_sendCleanupToScene直接賦值。

所以,導演用過渡場景作為replaceScene引數時,過渡場景和要退出的場景都會被cleanup。