忍者ブログ

JavaScriptゲーム

JavaScriptでゲームを作る!

メッセージウィンドウを作成する方法

以下のようなメッセージウィンドウを作成する方法を記します。

  • 一定時間ごとに1文字づつ表示します。
  • メッセージを表示中に画面内をクリックすると一括で表示します。
  • メッセージ内に以下のような制御を入れることが出来ます。
    1. 改行:メッセージを途中で改行します。
    2. クリア:メッセージを好きな箇所でクリアします。
    3. クリック待ち:メッセージ表示をクリックされるか指定した時間まで停止します。
    4. 変数:変数を埋め込むことで、メッセージを使いまわすことができます。サンプルでは時刻の表示に変数を使っています。

ファイル構成

ファイルとそれを入れるフォルダーを以下のような構成します。
※前回と同じファイル名でも内容が変わっている場合があります。
※背景と人物の画像は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" aria-label="グラフィックコンテンツ"></canvas>
        <article class="メッセージウィンドウ 消去 透明化">
            <p class="メッセージエリア">
                <span class="メッセージ"></span>
                <span class="カーソル 点滅 不可視化">■</span>
            </p>
        </article>
    </main>
</body>
</html>

1. <head> セクション

<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>
  • <meta charset="UTF-8"> セクション:日本語を含む多言語を正しく表示するためにUTF-8を使用しています。
  • <meta name="viewport"> セクション:モバイル端末での表示を最適化するため、幅を660pxに固定し、ユーザーがズームできないよう設定しています。
  • <title> セクション:ブラウザのタブに表示されるページタイトルです。
  • <link href="css/sample.css" rel="stylesheet"> セクション:別ファイル(css/sample.css)からスタイル情報を読み込む設定です。

2. <body> セクション

<script type="module" src="js/sample.js"></script>
  • type="module":モジュール化されたJavaScriptファイルを読み込みます。
  • src="js/sample.js":読み込む外部JavaScriptファイルを設定しています。このスクリプトにはメッセージウィンドウの動作等が定義されています。

3. 画面の更新情報

<aside class="画面更新情報">
    <p>
        リフレッシュレート:<span class="リフレッシュレート">0</span>Hz
        /フレームレート:<span class="フレームレート">0</span>fps
    </p>
</aside>
  • 役割:ここでは、画面の更新状況(リフレッシュレートとフレームレート)を表示しています。
  • クラス名の設定:
    • "画面更新情報":全体のコンテナのスタイリング(例:中央揃えやフォントサイズなど)に対応。
    • "リフレッシュレート" や "フレームレート":動的に数値が書き換えられる部分となります。

4. スクリーンエリア

<main class="スクリーン">
    <canvas class="レイヤー1" width="640" height="640" aria-label="グラフィックコンテンツ"></canvas>
    <article class="メッセージウィンドウ 消去 透明化">
        <p class="メッセージエリア">
            <span class="メッセージ"></span>
            <span class="カーソル 点滅 不可視化">■</span>
        </p>
    </article>
</main>
  1. Canvas要素:
    <canvas class="レイヤー1" width="640" height="640" aria-label="グラフィックコンテンツ"></canvas>
    
    • Canvasタグ:描画エリアを提供します。このコードでは画像やスプライトを表示する「レイヤー」として使われます。
    • 属性:
      • class="レイヤー1":CSSやJavaScriptで制御するためのクラス名。
      • width="640" height="640":Canvasの横幅と高さを指定。
      • aria-label="グラフィックコンテンツ":アクセシビリティのためにコンテンツの説明を付けています。
  2. メッセージウィンドウ:
    <article class="メッセージウィンドウ 消去 透明化">
        <p class="メッセージエリア">
            <span class="メッセージ"></span>
            <span class="カーソル 点滅 不可視化">■</span>
        </p>
    </article>
    
    • <article class="メッセージウィンドウ 消去 透明化">:
      • 「メッセージウィンドウ」クラス:このクラスは、吹き出しやダイアログのように画面上にメッセージを表示するためのウィンドウのスタイルを定義しています。
      • 「消去」クラス:CSS では display: none; として定義され、初期状態ではウィンドウが存在しているものの画面には表示されません。
      • 「透明化」クラス:CSS では opacity: 0; の設定になっており、見た目を透明にして隠しています。 ※「消去」と「透明化」を組み合わせて、ウィンドウを非表示状態にセットしています。
    • <p class="メッセージエリア">:メッセージテキストが表示される領域です。CSS によりスクロールなどの設定がされています。
    • <span class="メッセージ">:実際のメッセージテキストを 1 文字ずつ追加して表示していく部分です。
    • <span class="カーソル 点滅 不可視化">:
      • 「カーソル」クラス:メッセージ表示時に、入力中を示すためのアイコン(ここでは■)の表示に使います。
      • 「点滅」クラス:CSS の @keyframes 点滅 や animation 設定を使って、「■」が点滅するアニメーションが適用されます。
      • 「不可視化」クラス:CSS では visibility: hidden; として定義され、初期状態でカーソル自体が見えないようにしています。

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: 640px;
    margin: 20px auto;
    border: 2px solid #ccc;
    position: relative;
    background-color: black;
}

canvas {
    position: absolute;
}

.不可視化 {
    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);
    }

1. 変数(:rootセクター)

: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;
}
  • 目的:ここでは CSS 変数を定義しています。たとえば、文章の基本サイズや行の高さ、テキストに付ける影、アニメーションの速さなど、後で何度も使う数値を 1か所で管理できるようにしています。
  • 例:
    • --通常文字サイズ は 26px に設定されており、通常のテキストサイズとして使用。
    • --黒い影 は、文字を際立たせるために複数の影を短いオフセットとともに定義しています。
    • --トランジションスピード はアニメーションや状態変化の際の速度(ここでは 0.8秒)を指定しています。

2. 全体の設定(bodyセレクター)

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;
}
  • 目的:ページ全体の基本となるスタイルを設定しています。
  • 全体のデザイン:
    • font-family:フォントの種類を指定。Arialがメイン。
    • margin と padding:0 にすることで、ブラウザの初期余白をリセット。
    • background-color:薄いグレー(#f4f4f4)で全体の背景色に指定。
  • 特殊な設定:
    • -webkit-tap-highlight-color:スマホでタップしたときのハイライトを無効化。
    • overscroll-behavior-y:スクロールの挙動を制御することで、特にスマートフォンなどでの意図しない操作を防いでいます。

3. 更新情報のスタイル(.画面更新情報)

.画面更新情報 {
    text-align: center;
    font-size: var(--小文字サイズ);
    line-height: var(--小文字行高さ);
    color: #333;
}
  • 更新情報(リフレッシュレートやフレームレート)の文字を中央揃えにし、小さなフォントサイズで表示しています。

4. スクリーンエリアの設定(.スクリーン)

.スクリーン {
    width: 640px;
    height: 640px;
    margin: 20px auto;
    border: 2px solid #ccc;
    position: relative;
    background-color: black;
}
  • 目的:画面のメイン表示部分となるエリアのスタイル設定です。
  • ポイント:
    • width & height:幅と高さを640pxずつ固定。
    • margin: 20px auto:上下に20pxの余白を作り、中央に配置。
    • border:灰色(#ccc)の2px枠線を追加。
    • background-color:背景を黒に設定。
    • position: relative:内部に配置する要素(たとえば canvas やメッセージウィンドウ)の絶対配置の基準となります。

5. Canvas(キャンバス)の設定

canvas {
    position: absolute;
}
  • 目的:描画領域として利用する canvas 要素を絶対配置にしています。
  • ポイント:これにより、canvas は親要素(ここでは .スクリーン)の中で自由に配置できるようになります。

6. 表示や非表示を制御するクラス

.不可視化 {
    visibility: hidden;
}

.透明化 {
    opacity: 0;
}

.消去 {
    display: none;
}

.表示 {
    display: block;
}
  • 目的:HTML 要素の表示状態を切り替えるための便利なクラスを定義しています。
  • 違い:
    • .不可視化:visibility: hidden; は、その領域はレイアウトに残るが見えなくなる。
    • .透明化:opacity: 0; は、要素が透明になり見えなくなるが、クリックなどのイベントは受ける場合もある。
    • .消去:display: none; は、要素自体を完全に除去してレイアウトに影響を与えない。
    • .表示:display: block; は、要素を改めてブロック表示させます。

7. 点滅アニメーションの定義

7.1 キーフレームの定義

@keyframes 点滅 {
    0% {
        opacity: 1;
    }
    50% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}
  • 目的:点滅のアニメーションです。
  • ポイント:
    • 0%(開始時)と 100%(終了時)の時点で透明度 opacity が 1 =完全に表示
    • 50% の時点で透明度が 0 =完全に消える
    • これにより、要素が周期的に現れたり消えたりします

7.2 点滅クラスの適用

.点滅 {
    animation: 点滅 1s ease-out infinite;
}
  • 目的:このクラスを要素につけると、先ほど定義した点滅のアニメーションが適用されます。
  • ポイント:
    • animation:点滅 1s ease-out infinite;
      • アニメーション名は「点滅」
      • アニメーション期間は 1 秒
      • ease-out により、終了に向かってゆっくり変化する効果
      • infinite で繰り返し無限に実行

8. メッセージウィンドウの設定

.メッセージウィンドウ {
    position: absolute;
    background-color: rgba(0, 0, 0, 0.7);
    border: solid 3px white;
    padding: 12px 17px;
    margin: 5px;
    transition: opacity var(--トランジションスピード) ease-out;
}
  • メッセージウィンドウ全体のデザイン:
    • 背景:半透明の黒(rgba(0, 0, 0, 0.7))。
    • 枠線:白色の3px枠線。
    • アニメーション:transition により透明度(opacity)を滑らかに変化させる。

8.1 メッセージエリア

.メッセージウィンドウ .メッセージエリア {
    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;
}
  • 文字デザイン:
    • font-size と line-height は設定済みの変数を使用。
    • 白色の文字(color: white)に影(text-shadow)を追加。
  • スクロールバー非表示:
    • overflow: scroll; により、テキストがあふれる場合はスクロールできるように設定。ただし、IE と Firefox のスクロールバーは none で非表示にしています。
    • また、WebKit系ブラウザではスクロールバーを完全に非表示にする設定も追加。

8.2 カーソル(■)の設定

.メッセージウィンドウ .カーソル {
    transition: opacity var(--トランジションスピード) ease-in;
    font-size: var(--通常文字サイズ);
    display: inline-block;
    transform: translateY(-2px);
}
  • 目的:メッセージウィンドウ内の「カーソル」要素(■)をスタイリングします。
  • ポイント:
    • transition により、表示・非表示の切り替えが滑らかに変化。
    • transform: translateY(-2px); は、カーソルがテキストの高さに合わせて少し位置調整されるための設定です。
    • 先ほどの「点滅」クラスを付けると、点滅するカーソルとして動作させることができます。

9. 初心者向けのまとめ

このCSSファイルでは、メッセージウィンドウや画面の見た目を細かく調整しています。特徴的なポイントは以下の通り:

  • 変数で統一感を持たせる:文字サイズや色、影の設定などを変数化することで、全体の統一感を保ちつつ簡単に修正可能。
  • 基本リセットと全体デザイン:body では基本的な余白のリセット、フォント、背景色の設定やスマートフォンでの操作感の向上を図っています。
  • レイアウトの基盤:.スクリーン とその中の canvas で、描画エリアを絶対配置し、見た目の枠線や背景色を整えています。
  • 表示状態の切り替えクラス:.不可視化、.透明化、.消去、.表示 で、要素を一時的に隠す、または完全に取り除くといった要素の設定を簡単に切替えられるようにしいています。
  • 点滅アニメーション:@keyframes 点滅 と .点滅 クラスを使い、要素が周期的に現れたり消えたりするアニメーションを実現しています。カーソルにこのクラスをつけることで、点滅する効果を付与できます。

com.js - 共通処理クラス

Com クラス は共通で使用できる処理をまとめています。
一時停止と時間フォーマットの2つがあります。

export class Com {
    static 一時停止(時間) {
        return new Promise(resolve => setTimeout(resolve, 時間 * 1000));
    }

    static シリアル値を時分秒に変換する(シリアル値) {
        // シリアル値をDateオブジェクトに変換
        const date = new Date(シリアル値);

        // 時間、分、秒を取得
        const hours = date.getHours();
        const minutes = date.getMinutes();
        const seconds = date.getSeconds();

        // フォーマットして返す
        return `${hours}時${minutes}分${seconds}秒`;
    }
}

1. 一時停止(時間) メソッド

役割:指定した時間だけプログラムを停止する機能を提供します。例えば、ゲームやタイマー機能で使われることがあります。

仕組み:

  • setTimeout という JavaScriptの関数を利用して、一時停止を実現します。
  • setTimeout は、指定した時間が経過した後に何かを実行する関数です。
  • Promise を使って、停止が終わった後に次の処理を続けられるようにしています。

コードの流れ:

  1. setTimeout を使って指定した時間(秒×1000ミリ秒)だけ待ちます。
  2. 待ち時間が終わったら resolve で「再開していいよ」と合図します。

2. シリアル値を時分秒に変換する(シリアル値) メソッド

役割:シリアル値(数値形式の時刻)を、「〇〇時〇〇分〇〇秒」という形に変換して表示します。

仕組み:

  • JavaScript の Date オブジェクトを利用してシリアル値を時間に変換します。
  • 変換された日時から「時」「分」「秒」を取り出します。
  • 最後にフォーマットして使いやすい形(日本語の時刻表記)にします。

コードの流れ:

  1. new Date(シリアル値) でシリアル値を日時オブジェクトに変換します。
  2. getHours、getMinutes、getSeconds でそれぞれ「時」「分」「秒」を取得します。
  3. フォーマットして、「〇〇時〇〇分〇〇秒」の形で返します。

data.js - データ管理クラス

この Data クラスは、文字列に埋め込まれた <var>変数名</var> の形式で記述された変数を、事前に登録されたデータ(オブジェクト管理)から値を引き出して置き換えます。
イメージとしては、テンプレートに変数名を埋め込んでおき、実行時にそれを動的に値へ変える仕組みです。

export class Data {
    static オブジェクト管理 = {};

    static 文字列内の変数を値に変換する(文字列) {
        let 変換結果 = 文字列;
        const varList = 変換結果.match(/<var>[^<]*<\/var>/g);
        if (!varList) return 変換結果; // 変数が無い場合は何もせずにそのまま返す

        varList.forEach(varTag => {
            const 変数名 = varTag.replace(/<var>([^<]*)<\/var>/, "$1");
            const 値 = Data.変数の値を取得する(変数名) || ""; // 値がない場合は空文字を使用
            変換結果 = 変換結果.replace(varTag, 値); // <var>・・・</var>を値に置き換える
        });
        return 変換結果;
    }

    static 変数の値を取得する(変数名) {
        const 名称リスト = 変数名.split(".");
        let 値 = Data.オブジェクト管理;

        for (const 名称 of 名称リスト) {
            値 = 値?.[名称];
            if (値 === undefined || 値 === null) return ""; // 値が存在しない場合
        }
        return 値;
    }
}

1. オブジェクト管理:静的プロパティ

static オブジェクト管理 = {};
  • このプロパティは「変数名と値」を管理するための大元のデータ構造です。
  • 登録されたデータを基に、変数名の値を検索します。

例: もし オブジェクト管理 = { "user": { "name": "プレイヤー", "level": 25 } } となっている場合、user.name の値は "プレイヤー" となります。

2. 文字列内の変数を値に変換する:メソッド

役割:

  • 入力:変換対象の文字列(例: "こんにちは、<var>user.name</var>さん!")
  • 出力:変換結果の文字列(例: "こんにちは、プレイヤーさん!")

コードの流れ:

  1. 変数タグを探す:
    const varList = 変換結果.match(/<var>[^<]*<\/var>/g);
    
    この正規表現を使って <var>変数名</var> の形を探し出します。例えば、<var>user.name</var> が該当します。
  2. 変数名を取り出す:
    const 変数名 = varTag.replace(/<var>([^<]*)<\/var>/, "$1");
    
    この処理で <var> と </var> を取り除き、純粋な変数名(例: user.name)を取り出します。
  3. 値に置き換える:
    const 値 = Data.変数の値を取得する(変数名) || "";
    変換結果 = 変換結果.replace(varTag, 値);
    
    • Data.変数の値を取得する メソッドで値を検索し、該当する場合は置き換えます。
    • 値が無い場合は空文字 "" で置き換えます。

3. 変数の値を取得する:メソッド

このメソッドは、「変数名の構造」を辿りながら オブジェクト管理 の中から適切な値を見つけ出します。

コードの流れ:

  1. 変数名を分割:
    const 名称リスト = 変数名.split(".");
    
    例えば、user.name なら ["user", "name"] に分解されます。
  2. 値を辿る:
    値 = 値?.[名称];
    
    名前リストを順番に参照しながらネストされた構造を探索します。

    例:

    • オブジェクト管理 = { "user": { "name": "プレイヤー" } }
    • 変数名 = "user.name"
    • 処理結果: "プレイヤー"
  3. 値が存在しなければ空文字を返す:
    if (値 === undefined || 値 === null) return "";
    

4. 全体の動作イメージ

例えば、以下のように使われます:

Data.オブジェクト管理 = {
    user: { name: "プレイヤー", level: 25 },
    system: { time: "12:00" }
};

const result = Data.文字列内の変数を値に変換する(
    こんにちは、<var>user.name</var>さん。現在の時刻は<var>system.time</var>です。
);

console.log(result);
// 結果: "こんにちは、プレイヤーさん。現在の時刻は12:00です。"

messageWindow.js - メッセージウィンドウクラス

import { Data } from './data.js'; // クラスをインポート
import { ループ } from './loop.js'; // クラスをインポート
import { Com } from './com.js'; // クラスをインポート

class 制御情報クラス {
    constructor(メッセージ, メッセージエリア要素) {
        this.メッセージ = メッセージ;
        this.表示位置 = 0;
        this.表示メッセージ = "";
        this.クリック待ちフラグ = false;
        this.表示フレームカウンタ = 0;
        this.クリック待ちフレームカウンタ = 0;
        this.スクロール高さ = メッセージエリア要素.scrollHeight;
        this.一括表示フラグ = false;
    }
}

export class メッセージウィンドウクラス {
    constructor(スクリーンセレクタ, メッセージウィンドウセレクタ, 出現時間 = 1.0
        , 表示フレーム間隔 = 2, クリック待ち時間 = 30) {
        this.スクリーン要素 = document.querySelector(スクリーンセレクタ);
        this.メッセージウィンドウ要素 = this.スクリーン要素.querySelector(メッセージウィンドウセレクタ);
        this.メッセージエリア要素 = this.メッセージウィンドウ要素.querySelector(".メッセージエリア");
        this.メッセージ要素 = this.メッセージエリア要素.querySelector(".メッセージ");
        this.カーソル要素 = this.メッセージエリア要素.querySelector(".カーソル");
        this.出現時間 = 出現時間;
        this.表示フレーム間隔 = 表示フレーム間隔;
        this.クリック待ちフレーム数 = this.基本クリック待ちフレーム数 = クリック待ち時間 > 0 ? ループ.フレームレート * クリック待ち時間 : 0;
    }

    メッセージを表示する(メッセージ) {
        const 制御情報 = new 制御情報クラス(Data.文字列内の変数を値に変換する(メッセージ), this.メッセージエリア要素);
        this.スクリーン要素.onclick = this.メッセージ表示時にクリックする.bind(this, 制御情報);
        return new Promise(resolve => {
            ループ.処理を追加する(this, () => {
                if (this.クリック待ち時の処理を行う(制御情報)) return;

                // 表示フレーム間隔でメッセージを表示する
                if (0 < 制御情報.表示フレームカウンタ--) return;
                制御情報.表示フレームカウンタ = this.表示フレーム間隔;

                // 指定されたメッセージを全て表示したら処理を終了する
                if (制御情報.表示位置 >= 制御情報.メッセージ.length) {
                    ループ.処理を削除する(this);
                    this.スクリーン要素.onclick = null;
                    resolve();
                    return;
                }

                if (this.メッセージを一括で表示する(制御情報)) return;
                if (this.制御タグを取得する(制御情報)) return;
                this.指定した文字数のメッセージを表示する(制御情報, 1);
            });
        });
    }

    クリック待ち時の処理を行う(制御情報) {
        if (!制御情報.クリック待ちフラグ) return false;

        // クリック待ち時間(クリック待ちフレーム数)が0の場合は無限にクリックを待つ
        if (!this.クリック待ちフレーム数) return true;

        // クリック待ち時間までクリックを待つ
        if (this.クリック待ちフレーム数 > 制御情報.クリック待ちフレームカウンタ++) return true;

        制御情報.クリック待ちフレームカウンタ = 0;
        this.クリック待ち時にクリックする(制御情報);
        return true;
    }

    メッセージを一括で表示する(制御情報) {
        if (!制御情報.一括表示フラグ) return false;

        // 制御タグまでの文字を一括で表示する
        let 表示文字数 = 制御情報.メッセージ.indexOf('<', 制御情報.表示位置) - 制御情報.表示位置;
        if (表示文字数 == 0) return false; // 次の文字が制御タグだった場合
        if (表示文字数 < 0) {
            // 制御タグが無い場合は最後まで表示する
            表示文字数 = 制御情報.メッセージ.length - 制御情報.表示位置;
        }
        this.指定した文字数のメッセージを表示する(制御情報, 表示文字数);
        return true;
    }

    指定した文字数のメッセージを表示する(制御情報, 文字数) {
        制御情報.表示メッセージ += 制御情報.メッセージ.substring(制御情報.表示位置, 制御情報.表示位置 + 文字数);
        制御情報.表示位置 += 文字数;
        this.メッセージ要素.innerHTML = 制御情報.表示メッセージ;
        if (制御情報.スクロール高さ == this.メッセージエリア要素.scrollHeight) return;
        this.メッセージエリア要素.scrollTop = 制御情報.スクロール高さ = this.メッセージエリア要素.scrollHeight;
        return;
    }

    制御タグを取得する(制御情報) {
        if (制御情報.メッセージ.charAt(制御情報.表示位置) != '<') return false;
        if (this.制御タグの処理を行う(制御情報, '<br>', () => 制御情報.表示メッセージ += '<br>')) return true; // 改行する
        if (this.制御タグの処理を行う(制御情報, '<c>', () => 制御情報.表示メッセージ = '')) return true; // クリアする
        if (this.クリック待ちにする(制御情報)) return true;
        return false;
    }

    制御タグの処理を行う(制御情報, タグ名, 処理) {
        if (!制御情報.メッセージ.startsWith(タグ名, 制御情報.表示位置)) return false;
        制御情報.表示位置 += タグ名.length;
        処理();
        this.メッセージ要素.innerHTML = 制御情報.表示メッセージ;
        return true;
    }

    クリック待ちにする(制御情報) {
        const タグ名 = '<w>';
        if (!制御情報.メッセージ.startsWith(タグ名, 制御情報.表示位置)) return false;
        const 取得メッセージ = 制御情報.メッセージ.substring(制御情報.表示位置);
        const wList = 取得メッセージ.match(/^<w>\d*<\/w>/);
        if (wList) {
            const 待ち時間 = Number(wList[0].replace(/<w>(\d*)<\/w>/, "$1"));
            制御情報.表示位置 += wList[0].length;
            this.クリック待ちフレーム数 = 待ち時間 > 0 ? ループ.フレームレート * 待ち時間 : 0;
        } else {
            制御情報.表示位置 += タグ名.length;
        }
        制御情報.クリック待ちフラグ = true;
        制御情報.一括表示フラグ = false;
        this.スクリーン要素.onclick = this.クリック待ち時にクリックする.bind(this, 制御情報);
        this.カーソル要素.classList.toggle('不可視化', false);
        return true;
    }

    クリック待ち時にクリックする(制御情報) {
        制御情報.クリック待ちフラグ = false;
        this.クリック待ちフレーム数 = this.基本クリック待ちフレーム数;
        this.カーソル要素.classList.toggle('不可視化' , true);
        this.スクリーン要素.onclick = this.メッセージ表示時にクリックする.bind(this, 制御情報);
    }

    メッセージ表示時にクリックする(制御情報) {
        制御情報.一括表示フラグ = true;
    }

    async 表示する() {
        this.メッセージウィンドウ要素.classList.replace('消去', '表示');
        await Com.一時停止(this.出現時間); // 少しずつ出現する時間
        this.メッセージウィンドウ要素.classList.toggle('透明化', false);
        await Com.一時停止(this.出現時間); // 出現した後に少し待つ
    }

    async 消去する() {
        this.メッセージウィンドウ要素.classList.toggle('透明化', true);
        this.メッセージウィンドウ要素.classList.replace('表示', '消去');
        this.メッセージ要素.innerHTML = "";
    }
}

1. 全体の流れ

このコードは、以下の3つの主要なクラスを利用しています:

  • Dataクラス:文字列内に変数を埋め込んで値を変換します。
  • ループクラス:アニメーションや継続的な処理を管理します。
  • Comクラス:一時停止や時刻表示などの補助的な処理を提供します。

さらに、以下の2つのクラスを新たに定義しています:

  • 制御情報クラス:メッセージを管理する内部データ構造。
  • メッセージウィンドウクラス:メッセージウィンドウの動作を管理します。

2. 制御情報クラス

このクラスは、文字表示の進捗状況を管理します。

プロパティの内容:

  • メッセージ:「表示する全文字列」。 ※ここには、変数の値に変換された後のテキストが格納されます。
  • 表示位置:現在、どこまで文字列を表示済みかのインデックスです。
  • 表示メッセージ:実際に画面上に表示中の文字列。
  • クリック待ちフラグ:制御タグ(例えば <w>)でクリック待ち状態になったときに true となります。
  • 表示フレームカウンタ:1文字表示するまでのフレーム間隔を管理するためのカウンタ。
  • クリック待ちフレームカウンタ:クリック待ちの時間(フレーム数)をカウントするためのもの。
  • スクロール高さ:メッセージエリアのスクロール位置を更新する必要があるかどうかを判断するため、初期のスクロール高さを記録します。
  • 一括表示フラグ:ユーザーが画面(スクリーン)をクリックすると、通常の1文字ずつの表示ではなく「一括表示」に切り替えるためのフラグです。

3. メッセージウィンドウクラス

■ コンストラクタ

このクラスのコンストラクタでは、引数としてセレクタ(例えば、スクリーンおよびメッセージウィンドウの要素を指定する文字列)が渡されます。また、出現時間、1文字ずつ表示する際のフレーム間隔、クリック待ち時間(フレーム換算)を受け取ります。 処理の流れは以下のとおり:

  1. DOM要素の取得:
    • 画面全体を示す「スクリーン要素」
    • その中の「メッセージウィンドウ要素」
    • ウィンドウ内の「メッセージエリア」および「メッセージ」要素
    • 最後に、カーソル(点滅させる対象)の要素を取得
  2. 各種パラメータの保持:出現時間や表示フレーム間隔、そしてクリック待ちフレーム数(ループのフレームレート×待ち時間)を設定しています。

4. メッセージの表示処理

■ メッセージを表示する:メソッド

  1. 初期設定:
    • 渡されたメッセージ(変数の置換済みのテキスト)とメッセージエリア要素の情報をもとに、新しい 制御情報クラス のインスタンスを作成します。
  2. クリックイベントの設定:
    • 画面(スクリーン要素)をクリックすると、メッセージ表示時にクリックする メソッド(後述)で処理が走るようにします。
  3. ループ処理の追加:
    • 外部のループ管理(requestAnimationFrame を利用している ループ クラス)に対して、このメッセージ表示処理関数を登録します。
  4. 各フレームでの動作:
    • クリック待ち状態の処理をチェックし、待機中なら何もしません。
    • それ以外の場合、設定されたフレーム間隔ごとに表示する文字数を更新します。
    • もし全メッセージが表示されていれば、ループ処理を削除して Promise を解決(終了)します。
  5. 一括表示の確認:
    • ユーザーのクリックによって一括表示フラグが true になっている場合は、通常の1文字ずつの表示ではなく、次の制御タグ(< で始まる部分)までのテキストを一気に表示します。
  6. 制御タグの処理:
    • 現在の位置が < なら、制御タグを取得する でタグ内容を処理します(たとえば <br> なら改行、<c> ならメッセージクリア、<w> ならクリック待ち)。
  7. 最後は1文字ずつ表示:
    • 上記いずれにも該当しなければ、指定した文字数のメッセージを表示する メソッドで1文字だけ追加して表示します。

5. 制御タグの処理

■ 制御タグの例

  • <br>:改行を挿入するタグ。
    • 処理関数により、表示中のメッセージに <br> を追加します。
  • <c>:表示中のメッセージをクリアするタグ。
    • このタグを検出すると、表示文字列を空文字にリセットします。
  • <w>数字</w>:クリック待ちタグ。
    • ここではタグ内の数字を読み取り、その秒数分(ループのフレームレート×秒数)のクリック待ち状態にします。
    • 同時に「クリック待ちフラグ」が true になり、カーソルの表示も切り替え(不可視から表示へ)られます。

6. ユーザークリックでの一括表示

■ メッセージ表示時にクリックする:メソッド

  • ユーザーが画面(スクリーン)をクリックすると、通常は1文字ずつ表示されるところを、一括表示フラグを true に設定します。
  • 次のループ処理時に、メッセージを一括で表示する が呼ばれ、次の制御タグまでのテキストをまとめて表示します。

7. 表示・消去の演出

■ 表示する:メソッド

  • メッセージウィンドウを出現させる際に、CSS クラスを切り替えます。
    1. 初めは非表示状態(たとえば、消去 クラスが付いている)から、表示 クラスに切り替えます。
    2. その後、指定した出現時間だけ一時停止(Com.一時停止 を使用)し、さらに透明状態を解除(透明化 クラスの切替)して、ふわっと表示されるようにします。

■ 消去する:メソッド

  • 表示されたメッセージウィンドウを非表示にするため、透明度を下げ、表示 クラスを 消去 に変更し、メッセージ内容をクリアします。

sample.js - サンプルクラス

このプログラムは次のような処理を行います:

  1. 必要なクラスをインポートします。
  2. メイン処理 (main) を実行します。
  3. ループ処理や画面描画を初期化します。
  4. 画像を読み込み、それを基にスプライトを作成します。
  5. 作成したスプライトやメッセージウィンドウを表示します。
import { 画像管理クラス } from './imageManager.js'; // クラスをインポート
import { スプライトクラス } from './sprite.js'; // クラスをインポート
import { レイヤークラス } from './layer.js'; // クラスをインポート
import { ループ } from './loop.js'; // クラスをインポート
import { メッセージウィンドウクラス } from './messageWindow.js'; // クラスをインポート
import { Data } from './data.js'; // クラスをインポート
import { Com } from './com.js'; // クラスをインポート

class サンプル {
    static async main() {
        サンプル.ループ処理を作成する();
        サンプル.メッセージウィンドウを作成する();
        サンプル.レイヤーを作成する();
        if (!await サンプル.使用する画像を読み込む()) return;
        サンプル.読み込んだ画像でスプライトを作成する();
        サンプル.作成した物を使って画面に表示する();
    }

    static ループ処理を作成する() {
        ループ.初期化する();
        ループ.開始する();
    }

    static メッセージウィンドウを作成する() {
        サンプル.メッセージウィンドウ = new メッセージウィンドウクラス(".スクリーン", ".メッセージウィンドウ");
        Data.オブジェクト管理["サンプル"] = サンプル;
    }

    static レイヤーを作成する() {
        サンプル.レイヤー = new レイヤークラス('canvas.レイヤー1');
        ループ.処理を追加する(サンプル.レイヤー, サンプル.レイヤー.描画する.bind(サンプル.レイヤー));
    }

    static async 使用する画像を読み込む() {
        const 画像情報リスト = [
            { id: '町01', src: 'img/bg02.jpg' },
            { id: '人物01', src: 'img/chara01.png' }
        ];
        サンプル.画像管理 = new 画像管理クラス();
        return await サンプル.画像管理.画像を読み込む(画像情報リスト);
    }

    static 読み込んだ画像でスプライトを作成する() {
        サンプル.背景01 = new スプライトクラス(サンプル.レイヤー, サンプル.画像管理.取得する('町01'), 0, 0, 640, 640);
        サンプル.レイヤー.処理を追加する(サンプル.背景01, サンプル.背景01.描画する.bind(サンプル.背景01));
        サンプル.人物01 = new スプライトクラス(サンプル.レイヤー, サンプル.画像管理.取得する('人物01'), 0, 150, 640, 640);
        サンプル.人物01正位置 = { x: サンプル.人物01.x, y: サンプル.人物01.y };
        サンプル.人物01.座標を設定する(640, 150, 1280, 640); //初期表示位置を設定する
    }

    static async 作成した物を使って画面に表示する() {
        サンプル.主人公名 = "プレイヤー";
        サンプル.現時刻 = Com.シリアル値を時分秒に変換する(Date.now());

        await サンプル.人物01.移動してフェードインする(-10, 0, サンプル.人物01正位置.x, 0, 1);
        ループ.処理を削除する(サンプル.レイヤー);

        await サンプル.メッセージウィンドウ.表示する();
        await サンプル.メッセージウィンドウ.メッセージを表示する(
    "<var>サンプル.主人公名</var>は町を訪れた<w>5</w><br><c><var>サンプル.主人公名</var>はとりあえず町の人に今何時か聞いてみた<w><br>すると「<var>サンプル.現時刻</var>」だと教えてくれた<w>"
        );
        await サンプル.メッセージウィンドウ.消去する();

        while (1) {
            サンプル.現時刻 = Com.シリアル値を時分秒に変換する(Date.now());

            await サンプル.メッセージウィンドウ.表示する();
            await サンプル.メッセージウィンドウ.メッセージを表示する(
    "暫くして、<var>サンプル.主人公名</var>はまた何時なのか気になった<w>2</w><br>辺りの人に聞いてみると「<var>サンプル.現時刻</var>」だと教えてくれた<w>"
            );
            await サンプル.メッセージウィンドウ.消去する();
        }
    }
}

addEventListener('load', サンプル.main);

1. クラスをインポート

import { 画像管理クラス } from './imageManager.js'; // 画像管理に関する機能を提供
import { スプライトクラス } from './sprite.js'; // スプライト(画像)管理に関する機能
import { レイヤークラス } from './layer.js'; // レイヤー(描画画面)管理
import { ループ } from './loop.js'; // 繰り返し処理管理
import { メッセージウィンドウクラス } from './messageWindow.js'; // メッセージ表示管理
import { Data } from './data.js'; // データ管理
import { Com } from './com.js'; // 共通処理管理

これらのインポートにより、各機能を効率的に使うことができます。例えば、画像管理やスプライト表示、ループ処理などのモジュールが含まれています。

2. メイン処理

サンプル.main() は全体の流れを管理するメイン処理です。これにより以下の手順が実行されます:

  1. ループ処理を設定。
  2. メッセージウィンドウを作成。
  3. 画像を読み込んでスプライトを作成。
  4. 画面にスプライトやメッセージを表示。

3. 画像を使用する流れ

  • 画像読み込み処理:
    const 画像情報リスト = [
        { id: '町01', src: 'img/bg02.jpg' }, // 背景画像
        { id: '人物01', src: 'img/chara01.png' } // キャラクター画像
    ];
    サンプル.画像管理 = new 画像管理クラス();
    
    画像情報リストを定義し、それらを読み込みます。
  • スプライト作成処理:
    サンプル.背景01 = new スプライトクラス(サンプル.レイヤー, サンプル.画像管理.取得する('町01'), 0, 0, 640, 640);
    サンプル.人物01 = new スプライトクラス(サンプル.レイヤー, サンプル.画像管理.取得する('人物01'), 0, 150, 640, 640);
    
    背景やキャラクターなどをスプライトとして設定し、画面上に表示できるようにします。

4. メッセージウィンドウの流れ

  • メッセージウィンドウを使ってテキストを表示します。例えば:
    await サンプル.メッセージウィンドウ.メッセージを表示する("プレイヤーは町を訪れた...");
    
    このコードで、メッセージウィンドウにテキストを表示できます。

5. 繰り返し処理

while (1) {
    サンプル.現時刻 = Com.シリアル値を時分秒に変換する(Date.now());
    await サンプル.メッセージウィンドウ.メッセージを表示する("現在時刻は" + サンプル.現時刻);
}

ここでは永遠ループを使って、時間の変化を表示しています。

拍手[1回]

PR

コメント

P R

プロフィール

HN:
No Name Ninja
性別:
非公開