Cocos2d-xのScene切り替えで少しハマったのでメモ。
シーンの切り替えにはDirector::getInstance()->replaceScene()
を使用します。
この際に既存のシーンと切り替える新しいシーンは、以下のような流れでメソッドが呼ばれます。
Director Old Scene New Scene
constructor
init()
onEnter()
onEnterTransitionDidFinish()
constructor
init()
ReplaceScene()
SetNextScene()
onExitTransitionDidStart()
onExit()
cleanup()
release()
destructor()
onEnter()
onEnterTransitionDidFinish()
Sceneクラスを生成した場合、init()が呼ばれます。
その後、replaceScene()でシーン切り替えが予約され、SetNextScene()で切り替え処理が行われます。
replaceScene()とSetNextScene()の間には、ほんの少しだけ間があります。
そしてこの時点ではまだ既存のシーンが生きている状態です。
なのでinit()では、自身のシーン内の初期化などを行うだけにします。
間違っても大きなメモリを確保したり、2つのシーンで共有するマネージャやリソースにアクセスするのは少し危険です。
例えば、古いSceneのonExit()で既存のイベントハンドラを全部リリースするような多少乱暴な操作を行っている場合、init()で設定したイベントハンドラも意図せずリリースしてしまうことになります。
反対にinit()の中で解放し忘れのイベントハンドラを全部リリースするつもりで何らかの操作を行うと、古いSceneのonExit()あたりで二重解放を行ってしまうことになります。
新しいSceneのOnEnter()が呼ばれた時点で、古いSceneクラスは破棄されています。
ですので、マネージャなどへの登録処理などはOnEnter()で行いましょうということになります。
しかし、これがTransitionSceneを使用すると、少々話が違ってきます。
TransitionSceneは、2つのSceneをフェードで切り替えるなどといった、切り替え時のちょっとした演出を行ってくれる便利なSceneクラスです。
TransitionSceneを使用すると、2つのSceneは以下のような順番で呼び出されるようになります。
Director Old Scene New Scene
constructor
init()
onEnter()
onEnterTransitionDidFinish()
constructor
init()
ReplaceScene()
SetNextScene()
onExitTransitionDidStart()
onEnter()
onExit()
onEnterTransitionDidFinish()
cleanup()
release()
destructor()
大きな違いは、古いシーンの生存期間です。
新しいシーンのonEnterTransitionDidFinish()が呼び出された段階でも、まだ古いシーンが生きています。
この呼び出され順の変化が大きな影響を与える場合があります。
(瞬間的にメモリ確保が増えるなど)
TransitionSceneを使いつつ、新しいSceneのonEnter()時に古いSceneが消えているようにするには、新しいSceneに繋ぐ為のSceneを作ってやり橋渡しをしてやるか、TransitionSceneのOnExit()をオーバーライドして、古いシーンのcleanupやらreleaseを新しいSceneのonEnterTransitionDidFinish()の前に行ってやるなどがあります。
「使った分は自分で片付ける」を徹底していれば、さほど大きな影響はありませんが、Sceneにぶら下がったNodeの開放をSceneのreleaseで任せていたり、強引な解放処理を行っているような場合は思わぬ事故につながりますので注意しましょう。