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。