JavaScriptでゲームを作る!
選択肢を表示する方法を記します。
以下の例では、選択肢をクリックすると該当するメッセージを表示します。
ファイルとフォルダーを以下のような構成します。
※前回と同じファイル名でも内容が変わっている場合があります。
※背景と人物の画像はBing Image Creatorで生成しています(AI生成)。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=660,user-scalable=no,shrink-to-fit=yes">
<title>選択肢を表示する方法</title>
<link href="css/sample.css" rel="stylesheet" />
</head>
<body>
<script type="module" src="js/sample.js"></script>
<aside class="画面更新情報">
<p>
リフレッシュレート:<span class="リフレッシュレート">0</span>Hz
/フレームレート:<span class="フレームレート">0</span>fps
</p>
</aside>
<main class="スクリーン">
<canvas class="レイヤー1" width="640" height="640"></canvas>
<canvas class="レイヤー2" width="640" height="640"></canvas>
<article class="メッセージウィンドウ 消去 透明化">
<p class="メッセージエリア">
<span class="メッセージ"></span>
<span class="カーソル 点滅 不可視化">■</span>
</p>
</article>
<article class="コマンドウィンドウ 消去">
<button class="通常ボタン" value="0">
<span></span>
</button>
<button class="通常ボタン" value="1">
<span></span>
</button>
<button class="通常ボタン" value="2">
<span></span>
</button>
<button class="通常ボタン" value="3">
<span></span>
</button>
<button class="通常ボタン" value="4">
<span></span>
</button>
<button class="通常ボタン" value="5">
<span></span>
</button>
<button class="通常ボタン" value="6">
<span></span>
</button>
<button class="通常ボタン" value="7">
<span></span>
</button>
<button class="通常ボタン" value="8">
<span></span>
</button>
<button class="通常ボタン" value="9">
<span></span>
</button>
</article>
</main>
</body>
</html>
このHTMLは前回の「メッセージウィンドウを表示する方法」に「コマンドウィンドウ」クラスの要素を追加しており、それ以外はほぼ同じです。
<article class="コマンドウィンドウ 消去">
<button class="通常ボタン" value="0"><span></span></button>
<!-- 以下省略 -->
</article>
:root {
--通常文字サイズ: 26px;
--通常文字行高さ: 38px;
--小文字サイズ: 18px;
--小文字行高さ: 24px;
--黒い影: 1px 1px 1px #000, -1px -1px 1px #000, -1px 1px 1px #000, 1px -1px 1px #000, 2px 0px 1px #000, -2px -0px 1px #000, 0px 2px 1px #000, 0px -2px 1px #000;
--トランジションスピード: 0.8s;
}
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
overscroll-behavior-y: none;
}
.画面更新情報 {
text-align: center;
font-size: var(--小文字サイズ);
line-height: var(--小文字行高さ);
color: #333;
}
.スクリーン {
width: 640px;
height: 840px;
margin: 20px auto;
border: 2px solid #ccc;
position: relative;
background-color: black;
}
canvas {
position: absolute;
top: 200px;
}
.不可視化 {
visibility: hidden;
}
.透明化 {
opacity: 0;
}
.消去 {
display: none;
}
.表示 {
display: block;
}
@keyframes 点滅 {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.点滅 {
animation: 点滅 1s ease-out infinite;
}
.メッセージウィンドウ {
position: absolute;
background-color: rgba(0, 0, 0, 0.7);
border: solid 3px white;
padding: 12px 17px;
margin: 5px;
transition: opacity var(--トランジションスピード) ease-out;
}
.メッセージウィンドウ .メッセージエリア {
padding: 3px;
margin: 0px;
font-size: var(--通常文字サイズ);
line-height: var(--通常文字行高さ);
color: white;
text-shadow: var(--黒い影);
width: 584px;
height: 154px;
overflow: scroll;
-ms-overflow-style: none;
scrollbar-width: none;
}
.メッセージウィンドウ .メッセージエリア::-webkit-scrollbar {
display: none;
}
.メッセージウィンドウ .カーソル {
transition: opacity var(--トランジションスピード) ease-in;
font-size: var(--通常文字サイズ);
display: inline-block;
transform: translateY(-2px);
}
.通常ボタン {
font-size: var(--小文字サイズ);
line-height: var(--小文字行高さ);
color: white;
text-shadow: var(--黒い影);
background-color: rgba(250, 250, 250, 0.1);
background-image: linear-gradient(to top left, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4) 30%, rgba(255, 255, 255, 0.3));
box-shadow: inset 2px 2px 3px rgba(255, 255, 255, 0.6), inset -2px -2px 3px rgba(0, 0, 0, 0.6);
border: solid 1px black;
border-radius: 5px;
padding: 10px 20px;
margin:1px 0;
}
.通常ボタン:hover {
background-color: rgb(255, 255, 45);
}
.通常ボタン:active {
box-shadow: inset -2px -2px 3px rgba(255, 255, 255, 0.6), inset 2px 2px 3px rgba(0, 0, 0, 0.6);
}
.通常ボタン span {
display: flex;
align-items: center;
}
.コマンドウィンドウ {
position: absolute;
padding: 12px 17px;
margin: 5px;
top: 50%;
left: 50%;
transform: translate(-50%, -35%);
text-align: center;
max-height: 600px;
overflow: scroll;
-ms-overflow-style: none;
scrollbar-width: none;
}
.コマンドウィンドウ::-webkit-scrollbar {
display: none;
}
.コマンドウィンドウ .通常ボタン span {
justify-content: center;
white-space: nowrap;
min-width:250px;
}
このCSSは前回の「メッセージウィンドウを表示する方法」に「コマンドウィンドウ」クラスと「通常ボタン」クラスを追加しており、それ以外はほぼ同じです。
.コマンドウィンドウ {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -35%);
text-align: center;
...
}
.通常ボタン {
font-size: var(--小文字サイズ);
background-color: rgba(250, 250, 250, 0.1);
border-radius: 5px;
...
}
このクラスの目的:
export class コマンドウィンドウクラス {
constructor(スクリーンセレクタ, コマンドウィンドウセレクタ) {
this.スクリーン要素 = document.querySelector(スクリーンセレクタ);
if (!this.スクリーン要素) throw new Error(`スクリーンセレクタ:'${スクリーンセレクタ}'が見つかりません`);
this.コマンドウィンドウ要素 = this.スクリーン要素.querySelector(コマンドウィンドウセレクタ);
if (!this.コマンドウィンドウ要素) throw new Error(`コマンドウィンドウセレクタ:'${コマンドウィンドウセレクタ}'が見つかりません`);
this.ボタン要素リスト = this.コマンドウィンドウ要素.querySelectorAll('button');
if (!this.ボタン要素リスト.length) throw new Error(`'${コマンドウィンドウセレクタ}'にbuttonタグが見つかりません`);
this.名称要素リスト = this.コマンドウィンドウ要素.querySelectorAll('span');
if (!this.名称要素リスト.length) throw new Error(`'${コマンドウィンドウセレクタ}'にspanタグが見つかりません`);
this.ボタン要素リスト.forEach(x => x.onclick = this.ボタンをクリックする.bind(this));
}
async 表示する(コマンドリスト) {
for (let i = 0; i < this.名称要素リスト.length; i++) {
if (コマンドリスト.length <= i) {
this.ボタン要素リスト[i].classList.toggle('消去', true);
continue;
}
this.ボタン要素リスト[i].classList.toggle('消去', false);
this.名称要素リスト[i].innerHTML = コマンドリスト[i].名称;
}
this.コマンドリスト = コマンドリスト;
this.コマンドウィンドウ要素.classList.toggle('消去', false);
this.以前のクリックハンドラ = this.スクリーン要素.onclick;
this.スクリーン要素.onclick = this.ウィンドウ外をクリックする.bind(this);
return new Promise(resolve => this.resolve = resolve);
}
async ウィンドウ外をクリックする() {
this.コマンドウィンドウ要素.classList.toggle('消去');
}
async ボタンをクリックする(event) {
this.コマンドウィンドウ要素.classList.toggle('消去', true);
event.stopPropagation();
this.スクリーン要素.onclick = this.以前のクリックハンドラ;
this.resolve(this.コマンドリスト[Number(event.currentTarget.value)].id);
}
}
この部分は、クラスのインスタンスが作られるときに最初に呼び出されます。ウィンドウやボタンなどのHTML要素を取得し、操作できるようにします。
処理の流れ:
ポイント:各ステップで、要素が見つからない場合はエラーとして処理を終了します(例:ボタンがない場合)
このメソッドは、コマンドウィンドウにコマンドを表示し、選択肢をユーザーに提示します。
処理の流れ:
ポイント:Promise は、非同期処理が完了するまで待つ仕組みを提供します
ウィンドウ外の画面部分がクリックされたとき、コマンドウィンドウを非表示にします。 (例:ユーザーが選択せずに閉じたいときの処理)
ボタンがクリックされたときの処理を担当します。
処理の流れ:
import { 画像管理クラス } from './imageManager.js'; // クラスをインポート
import { レイヤークラス } from './layer.js'; // クラスをインポート
import { ループ } from './loop.js'; // クラスをインポート
import { メッセージウィンドウクラス } from './messageWindow.js'; // クラスをインポート
import { コマンドウィンドウクラス } from './commandWindow.js'; // クラスをインポート
import { Data } from './data.js'; // クラスをインポート
import { スプライトクラス } from './sprite.js'; // クラスをインポート
export class サンプル {
static async main() {
await サンプル.使用する画像を読み込む();
サンプル.ループ処理を作成する();
サンプル.メッセージウィンドウを作成する();
サンプル.コマンドウィンドウを作成する();
サンプル.レイヤーを作成する();
サンプル.読み込んだ画像でスプライトを作成する();
サンプル.作成した物を使って画面に表示する();
}
static async 使用する画像を読み込む() {
const 画像情報リスト = [
{ id: '町', src: 'img/bg02.jpg' },
{ id: 'プレイヤー', src: 'img/chara01.png' }
];
サンプル.画像管理 = new 画像管理クラス();
await サンプル.画像管理.画像を読み込む(画像情報リスト);
}
static ループ処理を作成する() {
ループ.初期化する();
ループ.開始する();
}
static メッセージウィンドウを作成する() {
サンプル.メッセージウィンドウ = new メッセージウィンドウクラス(".スクリーン", ".メッセージウィンドウ");
Data.オブジェクト管理["サンプル"] = サンプル;
}
static コマンドウィンドウを作成する() {
サンプル.コマンドウィンドウ = new コマンドウィンドウクラス(".スクリーン", ".コマンドウィンドウ");
}
static レイヤーを作成する() {
サンプル.レイヤー1 = new レイヤークラス('canvas.レイヤー1'); // 背景用のレイヤ
サンプル.レイヤー2 = new レイヤークラス('canvas.レイヤー2'); // 前景用のレイヤ
サンプル.レイヤー2.描画処理をループ処理に追加する();
}
static 読み込んだ画像でスプライトを作成する() {
サンプル.背景01 = new スプライトクラス(サンプル.レイヤー1, サンプル.画像管理.取得する('町'), 0, 0, 640, 640);
サンプル.人物01 = new スプライトクラス(サンプル.レイヤー2, サンプル.画像管理.取得する('プレイヤー'), 0, 0, 640, 640);
サンプル.人物01正位置 = { x: サンプル.人物01.x, y: サンプル.人物01.y };
サンプル.人物01.座標を設定する(640, 0, 1280, 640); //初期表示位置を設定する
}
static async 作成した物を使って画面に表示する() {
サンプル.背景01.描画する();
await サンプル.人物01.移動してフェードインする(-10, 0, サンプル.人物01正位置.x, 0, 1);
ループ.処理を削除する(サンプル.レイヤー2);
await サンプル.メッセージウィンドウ.表示する();
await サンプル.メッセージウィンドウ.メッセージを表示する("プレイヤーは町の広場に来た<w>10</w>");
let id = 0;
while (1) {
await サンプル.メッセージウィンドウ.メッセージを表示する("どうする?");
id = await サンプル.コマンドウィンドウ.表示する([
{ id: 0, 名称: "話す" },
{ id: 1, 名称: "持物を見る" },
{ id: 2, 名称: "移動する" }
]);
if (id == 0) {
await サンプル.メッセージウィンドウ.メッセージを表示する("話が聞けそうな人は誰もいない<w>10</w>");
continue;
} else if (id==1) {
await サンプル.メッセージウィンドウ.メッセージを表示する("今のところ、何も持っていない<w>10</w>");
continue;
}
await サンプル.メッセージウィンドウ.メッセージを表示する("どこへ行く?");
id = await サンプル.コマンドウィンドウ.表示する([
{ id: 0, 名称: "やめる" },
{ id: 1, 名称: "武器屋へ行く" },
{ id: 2, 名称: "防具屋へ行く" },
{ id: 3, 名称: "道具屋へ行く" },
{ id: 4, 名称: "宿屋へ行く" },
{ id: 5, 名称: "町を出る" }
]);
if (id == 0) continue;
else if (id == 1) {
await サンプル.メッセージウィンドウ.メッセージを表示する("武器屋は開いていないようだ<w>10</w>");
continue;
} else if (id == 2) {
await サンプル.メッセージウィンドウ.メッセージを表示する("防具屋は開いていないようだ<w>10</w>");
continue;
} else if (id == 3) {
await サンプル.メッセージウィンドウ.メッセージを表示する("道具屋は開いていないようだ<w>10</w>");
continue;
} else if (id == 4) {
await サンプル.メッセージウィンドウ.メッセージを表示する("宿屋は開いていないようだ<w>10</w>");
continue;
}
await サンプル.メッセージウィンドウ.メッセージを表示する("まだ装備が整っていない<w>10</w>");
}
}
}
addEventListener('load', サンプル.main);
このコードの目的は次の通り:
import { 画像管理クラス } from './imageManager.js';
import { レイヤークラス } from './layer.js';
読み込まれたクラスは、このプログラムの中で使用されます。