Help us understand the problem. What is going on with this article?

Cocos2d-xのTransitionSceneを使用したScene切り替えにご用心

More than 3 years have passed since last update.

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で任せていたり、強引な解放処理を行っているような場合は思わぬ事故につながりますので注意しましょう。

t_hoshiyama
初めまして、有限会社ぴっくるの代表の星山です。 ゲーム業界で25年ほど働いています。 傭兵エンジニアとして、知見(バッドノウハウ)は山ほど溜まっているのですが、それをまとめてアウトプットする時間がなく、これではいけないと思い、なるべく記事にしていこうと思っています。 どうぞよろしくお願いします。 ぴっくる公式ページ:https://pickle.ne.jp
http://pickle.ne.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away