ゲームにおけるシーンってなんぞやというお話です。
ただし、個人の見解です。
開発現場や開発の仕方によっては異なる場合があります。
言語仕様の話があるので、必要がない方は シーン遷移とは のみで大丈夫です。
<!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枚目となっており、操作対象(キャラクターと▶)の切り替えをフラグで管理し、選択肢によってマップを切り替えています。
同一のキャンバスに描画するものを変えることで、シーンを表現しています。