忍者ブログ

JavaScriptゲーム

JavaScriptでゲームを作る!

選択肢を表示する方法

選択肢を表示する方法を記します。
以下の例では、選択肢をクリックすると該当するメッセージを表示します。

ファイル構成

ファイルとフォルダーを以下のような構成します。
※前回と同じファイル名でも内容が変わっている場合があります。
※背景と人物の画像はBing Image Creatorで生成しています(AI生成)。

index.html - HTML

<!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>

1. 全体の構造

このHTMLは前回の「メッセージウィンドウを表示する方法」に「コマンドウィンドウ」クラスの要素を追加しており、それ以外はほぼ同じです。

2. コマンドウィンドウ

<article class="コマンドウィンドウ 消去">
    <button class="通常ボタン" value="0"><span></span></button>
    <!-- 以下省略 -->
</article>
  • 役割:選択肢を表示するウィンドウ。
  • クラス設定:
    • コマンドウィンドウ:ウィンドウのスタイルを定義。
    • 消去:初期状態では非表示(CSSでdisplay: none)。
  • 動的要素:
    • <button>:各ボタンが選択肢を表し、value 属性でボタンの番号を指定。
    • <span>:ボタン上に表示するテキストを動的に更新。

sample.css - CSS

: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;
    }

1. 全体の構造

このCSSは前回の「メッセージウィンドウを表示する方法」に「コマンドウィンドウ」クラスと「通常ボタン」クラスを追加しており、それ以外はほぼ同じです。

2. コマンドウィンドウ

.コマンドウィンドウ {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -35%);
    text-align: center;
    ...
}
  • 中央配置:top: 50%; left: 50%; transform: translate(-50%, -35%); によって画面中央に配置。
  • 最大高さ:600px以内に設定し、オーバーフロー時はスクロール。

2. ボタン(.通常ボタン)

.通常ボタン {
    font-size: var(--小文字サイズ);
    background-color: rgba(250, 250, 250, 0.1);
    border-radius: 5px;
    ...
}
  • ボタンデザイン。
    • 背景:半透明の白と斜めのグラデーション。
    • 角丸:5pxの角を丸く。
    • ホバー効果:ボタンにマウスを載せると黄色に変化。

commandWindow.js - コマンドウィンドウクラス

このクラスの目的:

  • コマンド(ボタンを介して実行される選択肢)を表示するウィンドウを作成。
  • 選択肢をクリックすると適切な処理が実行される仕組みを提供。
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);
    }
}

1. constructor() コンストラクタ

この部分は、クラスのインスタンスが作られるときに最初に呼び出されます。ウィンドウやボタンなどのHTML要素を取得し、操作できるようにします。

  • スクリーンセレクタ:コマンドウィンドウが配置される「画面」を示すセレクタ(CSSセレクタの指定)。
  • コマンドウィンドウセレクタ:コマンドを表示する「ウィンドウ」のセレクタ。

処理の流れ:

  1. this.スクリーン要素 に「画面」要素を取得。
  2. this.コマンドウィンドウ要素 に「コマンドウィンドウ」を取得。
  3. ウィンドウ内のボタンタグ(<button>)や名前を表示するスパンタグ(<span>)をリストとして取得。
  4. 各ボタンにクリックイベントを設定しておく(「this.ボタンをクリックする」を登録)。

ポイント:各ステップで、要素が見つからない場合はエラーとして処理を終了します(例:ボタンがない場合)

2. 表示する(コマンドリスト) メソッド

このメソッドは、コマンドウィンドウにコマンドを表示し、選択肢をユーザーに提示します。

処理の流れ:

  1. コマンドリストを反映:
    • 入力されたコマンドリストに基づいて、ボタンと名前を表示。
    • リストの長さが不足している場合、そのボタンを隠す。
  2. ウィンドウを表示:
    • コマンドウィンドウのCSSクラス消去(非表示状態)を削除。
  3. クリックハンドラの変更:
    • ウィンドウ外(背景など)をクリックすると閉じる処理を登録。
  4. Promise:
    • 選択が完了したら、その結果(id)を返す仕組みになっています。

ポイント:Promise は、非同期処理が完了するまで待つ仕組みを提供します

3. ウィンドウ外をクリックする() メソッド

ウィンドウ外の画面部分がクリックされたとき、コマンドウィンドウを非表示にします。 (例:ユーザーが選択せずに閉じたいときの処理)

4. ボタンをクリックする(event) メソッド

ボタンがクリックされたときの処理を担当します。

処理の流れ:

  1. ウィンドウを非表示に変更。
  2. イベントを伝播しないようにevent.stopPropagation()を呼び出し。
  3. 前のクリックハンドラ(画面のクリックイベントなど)を復元。
  4. 選択されたボタンの値(id)をPromiseで返す。

sample.js - サンプルクラス

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);

1. プログラムの全体像

このコードの目的は次の通り:

  • 必要な画像を読み込み、それを画面に表示する。
  • ユーザーが選択肢(コマンドウィンドウ)を選んで進行できる仕組みを提供する。
  • メッセージウィンドウを使って物語や状況を表示する。

2. 必要なクラスのインポート

  • この部分で外部ファイル(モジュール)からクラスを読み込みます。
    import { 画像管理クラス } from './imageManager.js';
    import { レイヤークラス } from './layer.js';
    
    読み込まれたクラスは、このプログラムの中で使用されます。

3. メイン処理 (main メソッド)

  • このプログラムのスタート地点です。
  • 次の手順でゲームの準備を整えます:
    1. 必要な画像をロードする。
    2. ゲームループ(繰り返し処理)を開始する。
    3. メッセージウィンドウやコマンドウィンドウを作成する。
    4. 読み込んだ画像を使ってスプライト(キャラクターや背景)を画面に描画する。
    5. 描画された画面に基づいてメッセージと選択肢を表示する。

4. 画像読み込み (使用する画像を読み込む メソッド)

  • 使用する画像のリスト(例:「町」や「プレイヤー」)を準備し、それを読み込みます。
  • 読み込まれた画像は 画像管理クラス に保存され、後から使うことができます。

5. レイヤーの作成 (レイヤーを作成する メソッド)

  • レイヤーはゲーム画面の「背景」と「前景」を分けるために使います。
  • たとえば:
    • 背景(レイヤー1)は町の画像を表示。
    • 前景(レイヤー2)はキャラクターや移動する物を表示。

6. スプライトの作成 (読み込んだ画像でスプライトを作成する メソッド)

  • スプライトは画面に表示される画像(例:背景やキャラクター)のこと。
  • このメソッドでは以下を設定します:
    • 背景スプライト:町の画像を表示。
    • キャラクタースプライト:「プレイヤー」の初期位置を設定。

7. メッセージウィンドウの作成

  • メッセージを表示するためのウィンドウ(吹き出しのようなもの)を作ります。
  • たとえば「プレイヤーは町の広場に来た」という物語を表示します。

8. コマンドウィンドウの作成

  • 画面に「話す」「持物を見る」「移動する」などの選択肢を表示するウィンドウを用意します。
  • プレイヤーが選択肢をクリックすると、それに対応する処理が実行されます。

9. プログラムの流れ

  1. 背景とキャラクターを表示:
    • 画面に背景とキャラクター画像を描画します。
    • キャラクターはフェードイン(徐々に表示)します。
  2. メッセージを表示:
    • メッセージウィンドウに「プレイヤーは町の広場に来た」などの文章を表示します。
  3. コマンドの選択:
    • プレイヤーが選択肢をクリックし、次の行動を決めます。
    • たとえば、「話す」を選ぶと「話が聞けそうな人は誰もいない」と表示されます。
  4. 繰り返し処理:
    • どの選択肢を選んでも、また次の選択肢が表示されます。

拍手[0回]

PR

コメント

P R

プロフィール

HN:
No Name Ninja
性別:
非公開