⚔️

『RPGツクールMZ』シーンの生成から廃棄まで

に公開

『RPGツクールMZ』関連記事 目次

今回はシーンが生成から廃棄まで、どういう動作をしているのかを調べます。

以前にシーンとウィンドウの関係について書いたので、興味があればそちらもどうぞ。

『RPGツクールMZ』シーンのウィンドウ生成を調べてみた

例によって適宜『RPGツクールMZ』非公式JavaScriptリファレンスのページにクラスなどリンクします。

シーンのクラスツリー

これはシーン(Scene_Base)の子孫のクラスツリーです。
大量にあってちょっとたじろぎますが、名前を見れば「あああれね」と大体予想がつきますし、よく考えたら必要だなということもわかると思います。
画面の切り替えが発生した時に呼び出されるのがシーンで、シーン遷移によって『RPGツクールMZ』のUIの流れは作られています。
新たに何かシステムを導入しようと思ったらシーン単位で作ればなんとかなりそう、という見通しもつくんじゃないかな、つくといいな。

で、このシーンたちを管理しているのがSceneManagerです。
と言っても、画像も文字も音声もシーンが持っているんで、SceneManagerが全処理の起点だったりしますが。

今回はシーン以外の処理は極力避けて解説しようと思います。キリないですからね。

SceneManager からの処理

SceneManagerpush()pop()goto()いずれかが実行された時にシーンが開始されます。
push()pop()に関しては中でgoto()を呼び出しているので、goto()を見ます。

SceneManager.goto = function(sceneClass) {
    if (sceneClass) {
        this._nextScene = new sceneClass();
    }
    if (this._scene) {
        this._scene.stop();
    }
};
  • 引数に渡されたクラスを生成(new)して_nextSceneに代入。
  • すでに実行中のシーンがあるなら停止を依頼する。

というシンプルな作りです。
シーンを生成した時にどんな処理が行われるかは、またあとで調べることにして、この_nextSceneが次にどのタイミングで呼ばれるかを追ってみましょう。

SceneManager.changeScene()

update()updateMain()changeScene()と毎フレーム呼ばれています。
で、isSceneChanging()メソッドで_nextSceneに次のシーンが予約されていてかつ、isCurrentSceneBusy()メソッドで現在のシーンが終了可能かをチェックしてます。

SceneManager.changeScene = function() {
    if (this.isSceneChanging() && !this.isCurrentSceneBusy()) {
        if (this._scene) {
            this._scene.terminate();
            this.onSceneTerminate();
        }
        this._scene = this._nextScene;
        this._nextScene = null;
        if (this._scene) {
            this._scene.create();
            this.onSceneCreate();
        }
        if (this._exiting) {
            this.terminate();
        }
    }
};

チェックを通ったら、シーンを終了する_scene.terminate()メソッドが呼ばれ、onSceneTerminate()が呼ばれています。

そして_nextScene_sceneに代入する処理のあと、_scene.create()メソッドが呼び出されていますね。
またonSceneCreate()メソッドも呼び出されています。

_exiting(シーンを抜けるか)がtrueの場合、terminate()が呼ばれています。

シーン側の処理はいったん置いて、SceneManager内の処理を追ってみましょう。

SceneManager.onSceneTerminate()

SceneManager.onSceneTerminate = function() {
    this._previousScene = this._scene;
    this._previousClass = this._scene.constructor;
    Graphics.setStage(null);
};

現在のシーン(とコンストラクタ)を保存しています。直前のクラスによって処理(具体的にはフェードイン)を変えたい場合に使用します。
Graphics.setStage()メソッドはnullを渡しているので、表示用に使われるステージを空にしています。
ステージというのは PIXI.js が持っている表示用オブジェクトで、『RPGツクールMZ』の画像は全部そこに描画されます。
つまり、画面をクリアしているわけです。

SceneManager.onSceneCreate()

SceneManager.onSceneCreate = function() {
    Graphics.startLoading();
};

画像の読み込みを開始しています。
ちょっとSceneManagerを離れてstartLoading()メソッドを見てみます。

Graphics.startLoading = function() {
    if (!document.getElementById("loadingSpinner")) {
        document.body.appendChild(this._loadingSpinner);
    }
};

これ読み込みを開始してるというより、読み込みの時に表示されるスピナー(クルクル回る画像)を表示してますね。
『RPGツクールMZ』では珍しくHTMLを直接操作していて貴重です。
…だからなんだと言われると困りますが。

SceneManager.terminate()

さて気を取り直してterminate()メソッドです。

SceneManager.terminate = function() {
    if (Utils.isNwjs()) {
        nw.App.quit();
    }
};

こちらも『RPGツクールMZ』では珍しい NW.js を操作している場面です。
ブラウザの場合は何もしませんが、アプリケーション化(NW.js上で動作)している場合は、アプリケーションを終了します。
なお[スクリプト]イベントコマンドでアプリケーションを終了させたい場合は、いきなりnw.App.quit()を呼ぶのではなく、その準備処理から行なってくれる次のスクリプトを呼ぶと良いです。

SceneManager.exit();

SceneManager.updateScene()

通常のゲームプレイ中の動作で重要なのがupdateScene()です。
update()updateMain()updateScene()と毎フレーム呼ばれています。

SceneManager.updateScene = function() {
    if (this._scene) {
        if (this._scene.isStarted()) {
            if (this.isGameActive()) {
                this._scene.update();
            }
        } else if (this._scene.isReady()) {
            this.onBeforeSceneStart();
            this._scene.start();
            this.onSceneStart();
        }
    }
};

他のオブジェクトでもそうですが、とにかくupdate()でユーザの入力やファイル状態、タイマーなどをチェックして表示の変更や音の再生を行うというのが『RPGツクールMZ』の基本です。
ここではシーンの準備が整っているか_scene.isStarted()で確認して、ゲームのウィンドウにフォーカスが合ってるかisGameActive()で確認してupdate()メソッドが呼ばれます。
普段はその辺は気にしないで「常に呼ばれているのがupdate()メソッド」ぐらいの感覚で使っていいかと思います。

そして_scene.isReady()で準備完了をチェックして、onBeforeSceneStart()を呼び、シーンの_scene.start()を呼び、onSceneStart()が呼ばれます。

SceneManager.onBeforeSceneStart()

新たなシーン開始前に今動いているものを停止させるメソッドです。
逆に言えば、今動いているシーンがないなら何もしません。

SceneManager.onBeforeSceneStart = function() {
    if (this._previousScene) {
        this._previousScene.destroy();
        this._previousScene = null;
    }
    if (Graphics.effekseer) {
        Graphics.effekseer.stopAll();
    }
};

onSceneTerminate()で保存していた_previousSceneを廃棄しています。
そして、エフェクシアーによるビジュアルエフェクトを停止しています。

SceneManager.onSceneStart()

メソッド名の通り、ここがシーンの開始点です。

SceneManager.onSceneStart = function() {
    Graphics.endLoading();
    Graphics.setStage(this._scene);
};

Graphics.endLoading()は読み込みを停止しているような印象を与えるメソッド名ですが、Graphics.startLoading()で動かしたスピナーを止める(削除する)ものです。
Graphics.setStage()メソッドは新たなシーンをPIXI.Applicationstage(描画対象)に指定します。stageはルート(root)とも言われたりします。
まぁ根っこですね。

シーン側での処理

ということで、SceneManager側から呼ばれているシーンのメソッドを一覧にします。
this._sceneの後ろについているメソッドを並べれば良いというわけなので簡単です。
また、どのシーンも区別せずに呼ばれていることから、一番親にあたるScene_Baseクラスにこれらのメソッドはすべて存在していることになります。

処理を依頼するメソッド

メソッド 説明
create() シーンに必要なウィンドウなど用意
start() 素材ファイルなど読み込まれシーン開始
update() 毎フレーム呼ばれる
stop() シーンの終了を指示(isBusy()trueの間は終了しない)
terminate() 後片付けが終わって、実際にシーンを終了

状態を問い合わせるメソッド

メソッド 説明
isReady() 素材ファイルなどの準備ができているか
isStarted() シーンが開始して動作しているか
isBusy() シーン操作を受け付けるか

これらをきっかけに、ウィンドウの描画などやっていけばいいということで、実際に使われている箇所(コード)を見ていきましょう。

Scene_Base.create()

オブジェクトが生成される場合はnewされてconstructor()が呼ばれ『RPGツクールMZ』の場合はそこからさらにinitialize()が呼ばれてオブジェクトの初期設定がなされることが定番です。
シーンが生成される時も同じなのですが、それとは別にcreate()がシーン開始前に呼ばれます。
正直必要ないんじゃかなという疑念は拭い切れないのですが、

なお、Object.create()というメソッドがありますが、全然関連性はないです。名前も標準のメソッド名と被らない方が良かったんでは?と思ったり。

Scene_Base.prototype.create = function() {
    //
};

さて見ての通りScene_Baseではcreate()の中身は空なんですが、多くのシーンではウィンドウオブジェクトの生成が行われています。
データベースの情報の読み込みなどが開始されるのも、このメソッドです。

Scene_Base.start()

start()isReady()trueになった時に実行されるメソッドです。
Scene_Baseでの実装が素材の読み込み完了をチェックしています。

Scene_Base.prototype.isReady = function() {
    return (
        ImageManager.isReady() &&
        EffectManager.isReady() &&
        FontManager.isReady()
    );
};

ウィンドウの表示に必要な画像の読み込みはcreate()でのウィンドウ生成時に開始されます。
なので、読み込みが完了した時に呼ばれる各シーンのstart()メソッドでは、ウィンドウのrefresh()メソッドで画像を含めた再描画を行うのが定番パターンです。
そのほか、データベースの情報の読みなどのチェックが行われている場合もあります。

Scene_Base.update()

『RPGツクールMZ』は毎フレームupdate()メソッドを呼んで、その中でリアルタイムな処理を行う仕組みで、シーンに関しても同様です。

Scene_Base.prototype.update = function() {
    this.updateFade();
    this.updateColorFilter();
    this.updateChildren();
    AudioManager.checkErrors();
};

この中のupdateChildren()がシーンに含まれるオブジェクト(主にウィンドウ)のupdate()メソッドを呼んでいます。
キー操作やアニメーションが必要な場合、大抵はウィンドウの方で処理されています。
ただ、決定やキャンセルに関してはウィンドウのsetHandler()メソッドによりシーン側で処理している場合も少なくありません。

Scene_Base.stop()

goto()が呼ばれたんで、シーンを終了して移動するので片付けてくださいよってメソッドです。
Scene_Baseではアクティブでなくして、不要な動作(update())を止めてます。

Scene_Base.prototype.stop = function() {
    this._active = false;
};

継承された各シーンでは終了処理…具体的にはウィンドウを閉じるアニメーションや切り替わり用のフェードアウト演出を入れてます。

Scene_Base.terminate()

SceneManager.changeScene()にあるSceneManager.isCurrentSceneBusy()メソッドでシーンのisBusy()を調べてtrueの間は待機します。

Scene_Base.prototype.isBusy = function() {
    return this.isFading();
};

Scene_BaseではisFading()でフェードイン・アウトが実行中であるかチェックしています。
継承された各シーンによってtrueになる条件はさまざまですが、主にウィンドウが閉じている間trueになります。
falseになったところで、terminate()メソッドが呼ばれます。

Scene_Base.prototype.terminate = function() {
    //
};

Scene_Baseでは空ですね。

継承された各シーンによって、使ってるメモリを開放したり、データを保存したり、やることは色々です。

まとめ

シーンが生成され破棄されるまでに、決まったメソッド(ハンドラ)が呼ばれるので、そこで定型的な準備と片付けの処理をしておけばいいということがわかりました。
自分で新規のシーンを作る場合の参考になれば幸いです。
そんなの作る人がどのくらいいるのかは置いといて。

自分の備忘録ですねっ!

エンジョイ! ツクールライフ!!

Discussion