TypeScriptでゲームプログラム3「STARTボタンを追加」

START処理

ここまで作ってきたプログラムではブラウザで開いた瞬間に球が動き出してしまいます。しかし一般的なゲームはオープニング(タイトルの表示やデモプレイなど)やゲームモードの選択があり、START処理を行うことでゲームが始まります。このプログラムにもそのようなSTART処理を追加することにします。

といっても、たいそうなものは作りません。「STARTボタンを押すとゲームを開始する」というささやかな処理を追加します。

そのために、まずは画面にSTARTボタンが出るようにします。
CANVASにSTARTボタンを描画して、それをクリックするようにしてもよいのですが、描画のためのコードを作る手間が省けて容易なため、今回はHTMLにボタンを追加することにしました。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Game</title>
    <script src="game.js"></script>
    <style>
        #container {
            width: 320px;
        }

        #button1 {
            display: block;
            margin: 10px auto;
            font-size: large;
        }

        #message {
            min-height: 50px;
            border: 2px gray solid;
            border-radius: 8px;
            padding: 8px;
        }
    </style>
</head>

<body>
    <div id="container">
        <canvas id="game" width="320" height="320"></canvas>
        <button id="button1">スタート</button>
        <div id="message">ボタンを押すとゲームを開始します。</div>
    </div>
</body>

</html>

ハイライト部分が変更点です。

<button id="button1">がSTARTボタンです。そして、ゲーム中のメッセージを表示するためのdiv要素も追加しました。また、それらの見た目を飾るためにCSSも追加しています。

ゲーム状況を変数化する

「STARTボタンを押すとゲームを開始する」を言い換えると、

  • 最初はゲーム停止状態
  • ボタンをクリックすることでゲーム実行状態に変化

ということになります。つまり、ゲームは停止状態と実行状態の2つの状況が存在します。

それをコード化した例を以下に示します。

/**
 * ゲームの状況を表すtype
 * stop - ゲーム停止中
 * game - ゲーム実行中
 */
type GameStatus_t = 'stop' | 'game';
/**
 * ゲームの状況
 */
let GameStatus: GameStatus_t = "stop";

ゲームの状況を表すunion型GameStatus_tを定義し、その型の変数GameStatusを宣言します。

GameStatusの初期値は"stop"です。最初はゲーム停止になっています。

ボタンクリックすると、GameStatusは"game"に変化します。

ここまで「TypeScriptでゲームプログラム」というタイトルでいろいろなことを書いてきましたが、このコードがこれまでで最もTypeScriptらしいコードかもしれません。そう思ったのは、stopやgame以外の間違った値をGameStatusに代入したとき、TypeScriptならトランスパイル時にエラーになって、そういった不具合を抑えることができるからです。

ボタンクリックイベントを追加する

以下にbutton1のクリック時のコードを示します。

document.addEventListener("DOMContentLoaded", () => {
    let button1 = document.getElementById("button1");
    if (button1) {
        button1.addEventListener("click", () => {
            switch (GameStatus) {
                case "game":
                    GameStatus = "stop";
                    ChangeButton1Text("スタート");
                    ChangeMessage("ゲームポーズ中\nボタンを押すと再開します。");
                    break;
                case "stop":
                    GameStatus = "game";
                    ChangeButton1Text("ストップ");
                    ChangeMessage("");
                    break;
            }
        })
    }
});

ゲームが停止中にボタンをクリックするとゲーム実行中に変化し、ゲーム実行中にクリックするとゲーム停止に変化します。
また、ゲーム状態の変更に合わせbutton1やmessageに表示する文字を変更しています。

ChangeButton1Text()のコードは以下の通りです。ChangeMessage()も同様のコードなので省略します。

/**
 * id名にマッチするHTML要素のinnerTextを変更する。
 * @param id id名
 * @param text innerTextにセットする文字列
 */
function ChangeText(id: string, text: string) {
    let element = document.getElementById(id);
    if (element) {
        element.innerText = text;
    }
}

/**
 * button1のテキストを変更する。
 * @param text 変更する文字列
 */
function ChangeButton1Text(text: string) {
    ChangeText("button1", text);
}

最後の仕上げ

ボタンクリックによって、GameStatusが変化するようになりました。しかし、まだSTART処理はできていません。球の移動処理を更新していないので、球はまだボタンクリックを無視して移動していませう。

START処理を実現するために、GameStatusが"game"になっていない間は球が移動しないようにしなければなりません。

そのために、UpdateGameSituation()を更新します。

/**
 * ゲームの状況を更新する
 */
function UpdateGameSituation() {
    UpdateRacket();
    UpdateBall();
}

/**
 * 球の状態更新
 */
function UpdateBall() {
    if (GameStatus == "game") {
      // 球の座標や速度を変化させる処理がここに入る。詳細は省略している。
    }
}

球やラケットの移動処理はこれまでUpdateGameSituation()に記載していましたが、コードが増えてきたので、整理して2つの関数に分けました。そして球の移動処理はGameStatusがgameのときのみ行うようにしました。

これで、ボタンが押されるまでstopであるため球は移動しなくなり、ボタンを押してgameになると球が移動し始めます。そしてさらにボタンを押すと再びstopになって、球は移動しなくなります。

これにより、「ゲーム停止→その間にラケットを動かす→ゲーム再開」というズル(タイム連打)ができるようになりました。ズルを禁止するなら、ラケットの移動処理も同様の方法で止めればよいでしょう。

なお、球の移動処理は以下で記載したときと同じなのでそちらを参照してください。

同様に、ラケットの移動処理は以下で記載したときと同じであるため、詳細は省略しています。

実際の例

ボタンを押すとゲームを開始します。

コメント