前回は場面の遷移処理を作りました。
これにより、画像を表示したりメッセージや選択肢を表示するのに配列変数の値を変更するだけで、処理を変更する必要がなくなりました。
しかし、この処理ではステータス画面やフィールド画面のような特殊なレイアウトの画面を表示することはできません。
今回はこの問題を解消する方法を記します。
以下の例では、選択肢から「ステータスを見る」をクリックするとステータス画面を表示します。
ファイルとそれを入れるフォルダーを以下のような構成します。
※背景と人物の画像はBing Image Creatorで生成しています(AI生成)。
<article>タグでステータスウィンドウを追加しています。
<article class="ステータスウィンドウ 消去 透明化">
<!-- 内部に画像やテキストなどさまざまな要素が配置 -->
</article>
この CSS では、いくつかのアニメーションを定義しています。
このコードは export class サンプルデータ { … } の中に全てのデータを定義しています。
static 画像データリスト = [
{ id: '町', src: 'img/bg02.jpg' },
{ id: '宿屋', src: 'img/bg03.jpg' },
{ id: 'プレイヤー', src: 'img/chara01.png' },
{ id: '受付', src: 'img/chara03.png' }
];
解説:
static 遷移データリスト = [
["スタート", , , [["町の広場へ行く",]],],
["町の広場へ行く", "<var>キャラクター情報管理.0.名前</var>は町の広場に来た<w>10</w>", , [["町の広場",]], [ /* 表示時の演出用の配列 */ ]],
// …(さらに複雑なデータが続きます)
];
解説:
具体的な動き:
ポイント:
static キャラクター情報データリスト = [
[0, "プレイヤー", "プレイヤー", 0, 0, 640, 640, 1, 0, 200, 100, 20, 20, 20, 20, 200, 0, 0, 0]
];
解説:
武器情報データリスト:
static 武器情報データリスト = [
[0, "青銅の剣", "剣", 10, 100, "青銅で出来たまあまあな切れ味の剣"],
[1, "鉄の剣", "剣", 20, 1000, "鉄製のよく斬れる剣"],
[2, "鋼の剣", "剣", 50, 2000, "鋼鉄製のかなり斬れる剣"]
];
鎧情報データリスト:
static 鎧情報データリスト = [
[0, "革の鎧", "鎧", 5, 20, "軽くてまあまあ丈夫な鎧"],
[1, "青銅の鎧", "鎧", 10, 500, "青銅で出来た結構丈夫な鎧"],
[2, "鉄の鎧", "鎧", 20, 1000, "鉄製の頑丈な鎧"],
[3, "鋼の鎧", "鎧", 50, 3000, "鋼鉄製でかなり守備力が高い鎧"]
];
盾情報データリスト:
static 盾情報データリスト = [
[0, "木の盾", "盾", 5, 20, "軽くてまあまあ丈夫な盾"],
[1, "青銅の盾", "盾", 10, 300, "青銅で出来た結構丈夫な盾"],
[2, "鉄の盾", "盾", 25, 1000, "鉄製の頑丈な盾"],
[3, "鋼の盾", "盾", 50, 2000, "鋼鉄製でかなり守備力が高い盾"]
];
解説:
export class キャラクター情報クラス {
constructor(
[
id, 名前, 画像id, 画像x1, 画像y1, 画像x2, 画像y2,
レベル, 経験値, 生命力, 魔力, 技力, ちから, 素早さ, 強靭さ, 所持金
],
画像要素, 装備武器, 装備鎧, 装備盾
) {
Object.assign(this, {
id, 名前, 画像id, 画像x1, 画像y1, 画像x2, 画像y2,
レベル, 経験値, 生命力, 魔力, 技力, ちから, 素早さ, 強靭さ, 所持金,
最大生命力: 生命力, 最大魔力: 魔力,
画像要素, 装備武器, 装備鎧, 装備盾
});
}
get 攻撃力() {
return this.ちから + (this.装備武器?.攻撃力 ?? 0);
}
get 守備力() {
return this.強靭さ + (this.装備鎧?.守備力 ?? 0) + (this.装備盾?.守備力 ?? 0);
}
}
配列を使った引数の受け取り:
Object.assign() でプロパティを自動セット:
このクラスでは、計算によって値を求めるために getter を使用しています。
攻撃力のゲッター:
get 攻撃力() {
return this.ちから + (this.装備武器?.攻撃力 ?? 0);
}
守備力のゲッター:
get 守備力() {
return this.強靭さ + (this.装備鎧?.守備力 ?? 0) + (this.装備盾?.守備力 ?? 0);
}
class アイテム情報クラス {
constructor(p) {
Object.assign(this, p);
}
}
このクラスは、すべてのアイテム情報の基本となるクラスです。
export class 武器情報クラス extends アイテム情報クラス {
constructor([id, 名前, 種別, 攻撃力, 価格, 説明]) {
super({ id, 名前, 種別, 価格, 説明 });
Object.assign(this, { 攻撃力 });
}
}
このクラスは「武器」に特有の情報を管理するために作っています。
export class 防具情報クラス extends アイテム情報クラス {
constructor([id, 名前, 種別, 守備力, 価格, 説明]) {
super({ id, 名前, 種別, 価格, 説明 });
Object.assign(this, { 守備力 });
}
}
こちらは「防具」に固有の情報を管理するクラスです。
class 選択肢情報クラス {
constructor([id, 名称]) {
Object.assign(this, { id, 名称 });
}
}
このクラスは、画面上に表示する選択肢(例:「次へ進む」「戻る」など)のデータを管理するためのものです。
class 画像情報クラス {
constructor([
スプライトid, レイヤーid,
画像id, x1, y1, x2, y2,
表示メソッド, p1, p2, p3
]) {
Object.assign(this, { スプライトid, レイヤーid, 画像id, x1, y1, x2, y2, 表示メソッド, p1, p2, p3 });
}
}
このクラスは、画像を表示する際の情報である、どのスプライトを使うか、どのレイヤーに配置するか、画像のID、画像の表示位置(x1, y1, x2, y2)や表示のためのメソッド、さらに補助的なパラメータ(p1, p2, p3)をまとめて管理します。
export class 場面情報クラス {
constructor([id, メッセージ, 遷移メソッド, 選択肢情報リスト, 画像情報リスト]) {
Object.assign(this, { id, メッセージ, 遷移メソッド });
this.選択肢情報リスト = Array.isArray(選択肢情報リスト)
? 選択肢情報リスト.map(x => new 選択肢情報クラス(x))
: [];
this.画像情報リスト = Array.isArray(画像情報リスト)
? 画像情報リスト.map(x => new 画像情報クラス(x))
: [];
}
}
このクラスは、ひとつの「場面」に関する情報をまとめて管理するクラスです
このコードは、さまざまなモジュール(画像管理、ループ処理、ウィンドウ、データ管理など)を連携させて、一連の処理を順番に初期化し、最終的にシーン(場面)の遷移を開始する仕組みになっています。
コード全体は、各パーツのインスタンス化と初期化を行い、プログラムをセットアップするための「ブートストラップ」的な役割を果たしています。
import { サンプルデータ } from './sampleData.js'; // クラスをインポート
import { 画像管理クラス } from './imageManager.js'; // クラスをインポート
import { ループ処理クラス } from './loop.js'; // クラスをインポート
import { データ管理クラス } from './dataManager.js'; // クラスをインポート
import { メッセージウィンドウクラス } from './messageWindow.js'; // クラスをインポート
import { コマンドウィンドウクラス } from './commandWindow.js'; // クラスをインポート
import { ステータスウィンドウクラス } from './statusWindow.js'; // クラスをインポート
import { レイヤークラス } from './layer.js'; // クラスをインポート
import { 武器情報クラス, 防具情報クラス } from './itemInfo.js'; // クラスをインポート
import { キャラクター情報クラス } from './charaInfo.js'; // クラスをインポート
import { 画像表示クラス } from './imageDisplay.js'; // クラスをインポート
import { 場面遷移クラス } from './sceneTransition.js'; // クラスをインポート
import { Com } from './com.js'; // クラスをインポート
まず、必要な各モジュール(クラス)をインポートしています。
たとえば、画像管理、ループ処理、各ウィンドウ(メッセージ、コマンド、ステータス)、場面遷移、そしてキャラクターやアイテムのデータを扱うクラスなど、多岐にわたるコンポーネントがここに含まれています。
export class サンプル {
constructor() {
this.main();
}
async main() {
await this.使用する画像を読み込む();
this.ループ処理を作成する();
this.データ管理を作成する();
this.メッセージウィンドウを作成する();
this.コマンドウィンドウを作成する();
this.ステータスウィンドウを作成する();
this.レイヤーを作成する();
this.武器情報を作成する();
this.鎧情報を作成する();
this.盾情報を作成する();
this.キャラクター情報を作成する();
this.画像表示を作成する();
this.場面遷移を作成する();
this.場面遷移.開始する();
}
この main メソッドは非同期処理(async)になっていて、初期化の手順を順番に呼び出しています。
手順としては以下の通りです。
async 使用する画像を読み込む() {
this.画像管理 = new 画像管理クラス();
await this.画像管理.画像を読み込む(サンプルデータ.画像データリスト);
}
ループ処理を作成する() {
this.ループ処理 = new ループ処理クラス(Com);
this.ループ処理.開始する();
}
メッセージウィンドウを作成する() {
this.メッセージウィンドウ = new メッセージウィンドウクラス(".スクリーン", ".メッセージウィンドウ", this.ループ処理, this.データ管理, Com, 0.5);
}
コマンドウィンドウやステータスウィンドウも同様にして作成します。
addEventListener('load', () => new サンプル());
このクラスは、場面を切り替える処理を一手に担っています。
constructor(遷移データリスト, 開始id, メッセージウィンドウ, コマンドウィンドウ, データ管理, 画像表示) {
this.場面情報管理 = {};
遷移データリスト.forEach(x => this.場面情報管理[x[0]] = new 場面情報クラス(x));
this.場面情報 = this.場面情報管理[開始id];
this.メッセージウィンドウ = メッセージウィンドウ;
this.コマンドウィンドウ = コマンドウィンドウ;
this.データ管理 = データ管理;
this.画像表示 = 画像表示;
}
コンストラクタでは、外部から渡される遷移データリストや各ウィンドウ、データ管理、画像表示などの必要なコンポーネントを受け取り、初期の状態を設定しています。
async 開始する() {
if (!this.場面情報) throw new Error("場面情報が初期化されていません");
while (1) {
await this.画像表示.画像を表示する(this.場面情報);
await this.メッセージを表示する();
if (await this.遷移メソッドを実行する(this.場面情報)) continue;
if (this.選択肢が不要な場合は1つ目の場面に遷移する()) continue;
await this.コマンドウィンドウを表示する();
}
}
このメソッドは、場面の遷移処理をループして実行します。
このループを通して、場面の画像表示、テキスト表示、遷移処理、そしてユーザーの選択が順次実行され、プログラムが進行していきます。
async メッセージを表示する() {
if (!this.場面情報.メッセージ) return; // 表示するメッセージが無い場合は何もしない
await this.メッセージウィンドウ.表示する();
await this.メッセージウィンドウ.メッセージを表示する(this.場面情報.メッセージ);
}
async 遷移メソッドを実行する(場面情報) {
if (!場面情報.遷移メソッド) return false;
await this.メッセージウィンドウ.消去する();
const メソッド情報 = {
メソッドの構文: 場面情報.遷移メソッド,
メソッド部: 場面情報.遷移メソッド,
引数リスト: [],
カレントオブジェクト: null,
メソッド名: null
};
this.データ管理.メソッドの構文からメソッド部と引数リストを取得する(メソッド情報);
this.データ管理.メソッド部からカレントオブジェクトとメソッド名を取得する(メソッド情報);
const id = await メソッド情報.カレントオブジェクト[メソッド情報.メソッド名](...メソッド情報.引数リスト);
const 選択肢情報 = 場面情報.選択肢情報リスト[id];
if (!選択肢情報 || !this.場面情報管理[選択肢情報.id]) {
throw new Error(`不正な遷移IDまたは選択肢情報が見つかりません。id: ${id}`);
}
this.場面情報 = this.場面情報管理[選択肢情報.id];
return true;
}
このメソッドは、特殊なレイアウトの画面に遷移させたい場合に使います。
選択肢が不要な場合は1つ目の場面に遷移する() {
const 選択肢情報 = this.場面情報.選択肢情報リスト[0];
if (選択肢情報.名称) return false;
if (!this.場面情報管理[選択肢情報.id]) {
throw new Error(`遷移IDが不正です。id: ${選択肢情報.id}`);
}
this.場面情報 = this.場面情報管理[選択肢情報.id];
return true;
}
async コマンドウィンドウを表示する() {
const id = await this.コマンドウィンドウ.表示する(this.場面情報.選択肢情報リスト);
this.場面情報 = this.場面情報管理[id];
if (!this.場面情報) {
throw new Error(`場面情報が見つかりません。id: ${id}`);
}
}
このクラスは、プログラム内でキャラクターのステータス(名前、レベル、各種能力値、装備、体力や魔力のバーなど)を表示するためのウィンドウを管理するために作りました。 ユーザーがステータスウィンドウを表示させ、表示中にウィンドウが徐々に出現し、画面をクリックするとフェードアウトして消えるという演出を実現しています。
constructor(スクリーンセレクタ, ステータスウィンドウセレクタ, Com, 現滅時間 = 0.5) {
this.Com = Com;
this.現滅時間 = 現滅時間;
this.スクリーン要素 = Com.DOM要素を取得する(document, スクリーンセレクタ);
this.ステータスウィンドウ要素 = Com.DOM要素を取得する(this.スクリーン要素, ステータスウィンドウセレクタ);
this.キャラクター画像要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".キャラクター画像");
this.名前要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".名前エリア .名前");
this.レベル要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".レベルエリア .レベル");
this.経験値要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".経験値エリア .経験値");
this.技力要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".基本パラメータ1 .技力");
this.ちから要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".基本パラメータ1 .ちから");
this.素早さ要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".基本パラメータ2 .素早さ");
this.強靭さ要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".基本パラメータ2 .強靭さ");
this.攻撃力要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".攻撃力エリア .攻撃力");
this.守備力要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".守備力エリア .守備力");
this.武器要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".装備エリア .武器");
this.鎧要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".装備エリア .鎧");
this.盾要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".装備エリア .盾");
this.生命力バー要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".バーエリア .生命力バー");
this.魔力バー要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".バーエリア .魔力バー");
this.生命力要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".バーエリア .生命力");
this.最大生命力要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".バーエリア .最大生命力");
this.魔力要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".バーエリア .魔力");
this.最大魔力要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".バーエリア .最大魔力");
this.所持金要素 = Com.DOM要素を取得する(this.ステータスウィンドウ要素, ".所持金エリア .所持金");
}
解説:
async 表示する(キャラクター情報) {
if (typeof キャラクター情報 !== "object" || キャラクター情報 === null) {
throw new Error(`キャラクター情報がありません。キャラクター情報: ${キャラクター情報}`);
}
this.ステータスを表示する(キャラクター情報);
await this.ウィンドウを表示する();
return this.画面をクリックしたら終了する();
}
解説:
ステータスを表示する(キャラクター情報) {
this.キャラクター画像要素.src = キャラクター情報.画像要素.src;
this.名前要素.textContent = キャラクター情報.名前;
this.レベル要素.textContent = キャラクター情報.レベル;
this.経験値要素.textContent = キャラクター情報.経験値;
this.技力要素.textContent = キャラクター情報.技力;
this.ちから要素.textContent = キャラクター情報.ちから;
this.素早さ要素.textContent = キャラクター情報.素早さ;
this.強靭さ要素.textContent = キャラクター情報.強靭さ;
this.攻撃力要素.textContent = キャラクター情報.攻撃力;
this.守備力要素.textContent = キャラクター情報.守備力;
if (キャラクター情報.装備武器) this.武器要素.textContent = キャラクター情報.装備武器.名前;
else this.武器要素.textContent = "なし";
if (キャラクター情報.装備鎧) this.鎧要素.textContent = キャラクター情報.装備鎧.名前;
else this.鎧要素.textContent = "なし";
if (キャラクター情報.装備盾) this.盾要素.textContent = キャラクター情報.装備盾.名前;
else this.盾要素.textContent = "なし";
this.生命力要素.textContent = キャラクター情報.生命力;
this.最大生命力要素.textContent = キャラクター情報.最大生命力;
this.生命力バー要素.value = キャラクター情報.生命力 / キャラクター情報.最大生命力 * 100;
this.魔力要素.textContent = キャラクター情報.魔力;
this.最大魔力要素.textContent = キャラクター情報.最大魔力;
this.魔力バー要素.value = キャラクター情報.魔力 / キャラクター情報.最大魔力 * 100;
this.所持金要素.textContent = キャラクター情報.所持金;
}
解説:
async ウィンドウを表示する() {
this.ステータスウィンドウ要素.classList.remove('消去');
await this.Com.一時停止(this.現滅時間); // 少しずつ出現する時間
this.ステータスウィンドウ要素.classList.remove('透明化');
}
解説:
画面をクリックしたら終了する() {
return new Promise(resolve => {
this.以前のクリックハンドラ = this.スクリーン要素.onclick;
this.スクリーン要素.onclick = async () => {
this.スクリーン要素.onclick = this.以前のクリックハンドラ;
this.ステータスウィンドウ要素.classList.add('透明化');
await this.Com.一時停止(this.現滅時間); // 少しずつ消える時間
this.ステータスウィンドウ要素.classList.add('消去');
resolve(0);
};
});
}
解説: