シーン遷移について

ゲームにおけるシーンってなんぞやというお話です。
ただし、個人の見解です。
開発現場や開発の仕方によっては異なる場合があります。
言語仕様の話があるので、必要がない方は シーン遷移とは のみで大丈夫です。

<!DOCTYPE html>
<html lang="ja" dir="ltr">
    <head>
        <meta charset="utf-8">
        <title>scene</title>
        <link rel="stylesheet" href="style.css">
        <script type="text/javascript" src="scene.js"></script>
        <script type="text/javascript" src="main.js"></script>
    </head>
    <body>
        <div class="wrap">
            <canvas id="canvas" width="300" height="300"></canvas><br>
            <button type="button" id="toTitleButton" name="button">タイトル画面へ</button>
            <button type="button" id="toMainButton" name="button">メイン画面へ</button>
            <button type="button" id="toEndButton" name="button">エンド画面へ</button>
        </div>
    </body>
    <script type="text/javascript" src="Loading.js"></script>
</html>
window.onload = () => {
    // シーンオブジェクトの初期化
    Scene.init();
    // ボタンをクリックした時にシーン遷移する(ボタンにリスナーを追加する)
    const toTitleButtonId = 'toTitleButton';
    const toTitleButton = document.getElementById(toTitleButtonId);
    toTitleButton.addEventListener('click', function() {
        Scene.drawScene('タイトル画面');
    });
    const toMainButtonId = 'toMainButton';
    const toMainButton = document.getElementById(toMainButtonId);
    toMainButton.addEventListener('click', () => {
        Scene.drawScene('メイン画面');
    });
    const toEndButtonId = 'toEndButton';
    const toEndButton = document.getElementById(toEndButtonId);
    toEndButton.addEventListener('click', { handleEvent: Scene.drawScene.bind(Scene, 'エンド画面') });
    // ローディング画面の非表示
    hideLoading();
}
window.onload = () => {
    // シーンオブジェクトの初期化
    Scene.init();
    // ボタンをクリックした時にシーン遷移する(ボタンにリスナーを追加する)
    const toTitleButtonId = 'toTitleButton';
    const toTitleButton = document.getElementById(toTitleButtonId);
    toTitleButton.addEventListener('click', function() {
        Scene.drawScene('タイトル画面');
    });
    const toMainButtonId = 'toMainButton';
    const toMainButton = document.getElementById(toMainButtonId);
    toMainButton.addEventListener('click', () => {
        Scene.drawScene('メイン画面');
    });
    const toEndButtonId = 'toEndButton';
    const toEndButton = document.getElementById(toEndButtonId);
    // toEndButton.addEventListener('click', { handleEvent: Scene.drawScene.bind(Scene, 'エンド画面') });
    toEndButton.addEventListener('click', Scene.drawScene.bind(Scene, 'エンド画面'));
    // ローディング画面の非表示
    hideLoading();
}

main.js

今回はシーンオブジェクトを main.js で使用する形にしてみました。
scene.html は解説しなくても大丈夫かと思いますので飛ばします。
シーンオブジェクトを初期化→ボタンの初期設定をしています。

addEventListener

さて、今回は addEventListener の書き方を3種類用意してみました。
HTML と JavaScript で書いた通り、~された時に何かをする、そんな構文です。
一つ目のタイトル画面を読み込むボタンでは、

    toTitleButton.addEventListener('click', function() {
        // ここで宣言した変数や関数は、このブロックの外では使えない
        const blockValue = "block value";
        Scene.drawScene('タイトル画面');
    });
    // ここで blockValue を参照することはできない

とし、二つ目の引数に function() { } としています。
この場合、第二引数に指定した匿名関数(function() {})に新たなスコープが作られます。
そのため、匿名関数内で宣言した変数や関数が、リスナーの外のスコープでは参照できません。

二つ目のメイン画面を読み込むボタンでは、

    toMainButton.addEventListener('click', () => {
        // ここで宣言した変数や関数は、このブロックの外でも使える
        const blockValue = "block value";
        Scene.drawScene('メイン画面');
    });
    // ここで blockValue を参照することができる!!

とし、二つ目の引数に () => {} としています。
この場合、第二引数に指定した匿名関数(() => {})に新たなスコープが作られません。
そのため、匿名関数内で宣言した変数や関数が、リスナーの外のスコープでも参照できます。
これは、関数式とアロー関数の違いになります。

三つ目のエンド画面を読み込むボタンでは、

toEndButton.addEventListener('click', Scene.drawScene.bind(Scene, 'エンド画面'));

とし、二つ目の引数にシーンオブジェクトの関数を引数としています。
さて、見慣れない .bind というものが出てきました。
.bind は、Scene.drawScene 内で使用する this と引数 txt を渡しています
正確には違います・・・・・・・・が、そんな感じのことをしています。
詳しくはこちらに掲載されています。

Scene

scene.js では Scene オブジェクトを作成しています。
HTML 上の canvas 要素を取得し、キャンバスに色を塗ったりテキストを描画したりしています。
Scene オブジェクトの drawScene という関数をボタンが呼び出しています。

    // シーンの描画
    drawScene : function(txt) {
        this.reFill();
        this.drawText(txt);
    },

さて、関数の中で this というキーワードが出てきました。
この this は、この関数の中では Scene オブジェクト自身・・・・・・・・・・・・・・・・・を指しています。
this を使用することで、自身のオブジェクトに定義されている変数(プロパティ)や関数(メソッド)にアクセスすることができます。
ですのでこの関数は、キャンバスを無地の状態にし、引数に渡された txt をキャンバス上に描画する関数となっています。
ちょっと嘘が混ざっていますが、難しい話になりますので割愛します。
また、どこかの記事で説明できたらと思います。

シーン遷移とは

さて、本題になります。
大前提として、ゲームのジャンルや仕様によって変わりますし、開発方法や開発環境によっても変わります。
あくまで一例、知識としてください。

まず、ゲームにおけるシーン(場面)とは何か、を考えると、ゲームの区切りです。

某RPGゲームを例にしてみると、
タイトル画面があり、
キャラクターを自由に操作し探索できる画面があり、
敵キャラクターとの戦闘画面があり、
ラスボスを倒すとエンディングの画面があります。
この、画面・・がシーンになります。
タイトル画面ではセーブデータを選択したり、削除したり、初めからスタートしたりできます。
キャラクターを自由に操作する画面では、町で住民から話を聞いたり、洞窟等を探索したりできます。
戦闘画面では各キャラクターの動作を決定し、敵を倒したり、敵から逃げたりできます。
エンディング画面では、スタッフロールが見れたり、後日談が見れたりします。
ゲームの区切りがあるタイミングで、画面(シーン・場面)が切り替わっています。

ここで、プログラミングにおけるゲームの区切りはどこになるのか、というと、主役(操作対象)が変わるタイミングです。
タイトルでは矢印や指が操作対象になり、
探索する際はキャラクターが操作対象になり、
戦闘では矢印や指が捜査対象になり、
エンディングでは傍観(スキップや早送りできる場合)があります。
ですので、
プログラミングにおけるゲームの区切り = シーン遷移
シーン遷移 = 操作対象(操作方法)が変更される
となります。

仕様や実装にもよりますが、
街マップからフィールドマップへ遷移する際、シーンとして遷移するのではなく、
表示しているマップを切り替えて、キャラクターの位置を移動することでマップの遷移を行う、
などが、考えられます。
ちなみに伝っても出られない では、

左上から順に、
タイトルシーン、ストーリーシーン、探索シーン(3~5枚目)、エンドシーンとなっています。
探索シーンは3~5枚目となっており、操作対象(キャラクターと▶)の切り替えをフラグで管理し、選択肢によってマップを切り替えています。
同一のキャンバスに描画するものを変えることで、シーンを表現しています。