利用手順
まず、こちらの記事に従って、React の開発環境を準備します。
作業用ディレクトリーにゲームエンジンをダウンロードして、必要なパッケージをインストールします。
% 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 未満の整数値が得られます。