めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

パソコン風のテキストゲームを簡単に作れる React のゲームエンジン

何の話かと言うと

昔懐かしいパソコン風のテキストゲームを簡単に作れるゲームエンジンを React で作りました。こんな感じのブラウザ上で遊べるゲームが作成できます。

ここから遊べます。)

利用手順

まず、こちらの記事に従って、React の開発環境を準備します。

enakai00.hatenablog.com

作業用ディレクトリーにゲームエンジンをダウンロードして、必要なパッケージをインストールします。

% cd ~/Documents
% git clone https://github.com/enakai00/react_textgame
% cd react_textgame
% yarn install

冒頭の「あるけあるけゲーム」がデフォルトで入っており、下記のコマンドで実行できます。

% yarn start
---- Output ----
Compiled successfully!

You can now view react_textgame in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://192.168.1.56:3000

Note that the development build is not optimized.
To create a production build, use npm run build.

webpack compiled successfully

ゲーム本体はファイル「src/Main.js」に記載されており、このファイルを書き換えるだけでオリジナルのゲームが作成できます。

Main.js の書き方

使用するキーの指定

110行目に下記のような記述があります。ここには、ゲームで使用するキーを並べて指定します。これらのキーの On/Off を検知するハンドラーが用意されて、変数 KeyPress からキーの状態が読み出せるようになります。

   109    // Define keys used in the game.
   110    const keys = ["s", "i", "j", "l", "m"];

たとえば、"s" キーを押していれば、KeyPress["s"] === ture、押していなければ、KeyPress["s"] === false となります。

ゲーム本体の記述

ゲーム本体は、7行目から始まる関数「game」の中に書きます。

     6  // Your code here!
     7  const game = async (screen, refresh, keyPress, exit) => {
...
   105  }

大雑把な構造は次の通りです。

はじめに、ゲーム全体で使用するグローバル変数を定義します。

     8    // Global game variables.
     9    const bike = {x: 0, y: 0, direction: 0, score: 0}
    10    const bikeChar = ["┻", "┣", "┳", "┫"]
    11    const dirVector = [[0, -1], [1, 0], [0, 1], [-1, 0]] // [dx, dy]

その後は、ゲーム内で使用するサブルーチンを用意します。この例では、次のような関数を定義しています。

  • ゲームの初期化(initGame)
  • ゲームオーバー画面(gameover)
  • 自機の移動(moveBike)
  • 障害物の表示(putBlock)

これらはすべて、async キーワードをつけて非同期関数として定義します。関数内では、次の同期処理が利用できます。

  • await refresh() :画面を再描画して、描画が終わるまで待つ。
  • await sleep() :指定時間(ミリ秒単位)何もせずに待つ。
    13    const initGame = async () => {
    14      // Initialize bike status.
    15      bike.x = 20;
...
    47    }
    48
    49    const gameover = async () => {
    50      print(screen, 15, 10, " GAME OVER ", "black", "white");
    51      await refresh();
    52      await sleep(5000);
    53    }
    54
    55    const moveBike = async () => {
    56      if (keyPress["i"]) {
    57        bike.direction = 0;
    58      }
...
    80    }
    81
    82    const putBlock = async () => {
    83      const x = randInt(1, 39);
    84      const y = randInt(2, 22);
    85      if (randInt(0, 2) === 0) {
    86        print(screen, x, y, " ", "yellow", "yellow");
    87      }
    88    }

最後にこれらのサブルーチンを順に呼び出すループをメインループとして用意します。

    91    // main loop
    92    var finished;
    93    while (true) {
    94      finished = false;
    95      await initGame();
    96      while (!finished) {
    97        if (exit.current) return;
    98        await moveBike();
    99        await putBlock();
   100        await refresh();
   101        await sleep(100);
   102      }
   103      await gameover();
   104    }
   105  }

変数 finished でゲームオーバーかどうかの判定を行っており、サブルーチン内部で「finshed = true」に設定するとゲームオーバーになります。個々のサブルーチンは、await を付けて、同期的に順番に実行していきましょう。(昔のパソコンのプログラムは、すべて同期実行でしたから。)

また、画面の左下に「Reset」ボタンの機能が用意されていますが、これを押すと「exit.current=true」がセットされるので、これを検知して関数「game」を終了(return)させる必要があります。97行目はそのための処理になります。同様に、ゲーム開始時に「S」キーが押されるのを待つループの中でも、これを検知して return するようにしてあります。

    34      while (true) {
    35        if (exit.current) return;
    36        if (keyPress["s"]) {
    37          break;
    38        }
    39        await sleep(100);
    40      }


その他の部分は、ボイラープレートなのでそのままにしておいてください。

その他の補助変数・補助関数

その他には、画面描画に関連する、次の補助変数、補助関数が利用できます。

  • 2次元配列 screen

screen[y][x] には、座標 (x, y) に表示する「キャラクター、文字色、背景色」の情報が下記の連想配列として保存されています。(画面サイズはお約束の 40 × 24 です。)

screen[y][x] = {
  char: "", color: "white", bgColor: "black"
};

これを書き換えて、await refresh() を実行すると画面が書き換えられます。逆に、screen[y][x] を読み出せば現在表示されている「キャラクター、文字色、背景色」がわかります。(パソコンで言うところの VRAM みたいなものです。)

  • 関数 print

文字列を表示する際は、screen を個別に書き換えるのは面倒です。print で指定位置から文字列を書き込むことができます。

print(screen, x, y, str, "white", "black");

第1引数は、書き換える配列として screen を指定します。座標 (x, y) から文字列 str を書き込みます。その後ろは文字色と背景色で、省略時は、"white", "black" になります。半角文字は、自動で全角文字に変換されます。

ちなみに、背景色を付けて " "(スペース)を表示すると、冒頭のゲームで使っているような塗りつぶしたブロックが表現できます。

  • 関数 clearScreen

配列 screen を渡すと初期化して画面をクリアします。

clearScreen(screen);

初期状態では、screen[y][x].char === ""(スペースではなく空文字列)になっていますので、" "(スペース)を表示した場所は、初期状態とは別物になります。

  • 関数 randInt

画面表示とは関係ありませんが、ゲームでは必須の整数の乱数が得られます。

randInt(min, max);

min 以上、max 未満の整数値が得られます。

ゲームの公開方法

開発したゲームは、GitHub Pages で公開することができます。下記の手順を参考にしてください。

enakai00.hatenablog.com