01.ゲームの骨格を作る
オンライン対戦のタンクゲームを作っていきます。まず、オンライン対戦ゲームの骨格を作ります。
デモ
デモ(Google App Engine):https://online-battle-tanks.an.r.appspot.com/index01.html
デモ(Heroku):https://online-battle-tanks-22-cf6589d25849.herokuapp.com/index01.html
Webアプリの骨格
Nodeを用いて、Webアプリを作成するために、最低限すべきことは以下です。
- サーバー側で動作するプログラムを作成します。( server.js )
- クライアント側で表示させるウェブページを作成します。( public/index.html )
- クライアント側で動作するプログラムを作成します。( public/client.js )
オンライン対戦ゲームの骨格
オンライン対戦ゲームの骨格の作成として行うことは以下です。
サーバー側
- 一定時間間隔で処理を行う周期的処理を行います。
- 周期的処理では、「ゲーム内に存在しているもの」をすべて更新します。(周期的処理の前回時から経過した時間分、「ゲーム内に存在しているもの」をすべて更新します。)
- 「ゲーム内に存在しているもの」をすべて更新したら、「ゲーム内に存在しているもの」の最新状況を、クライアントに送信します。
- クライアントから送信されるデータを受信し、「クライアントが操作しているもの」の状態を変更します。
クライアント側
- サーバーから送信される「ゲーム内に存在しているものの最新状況」を画面に表示します。
- プレーヤーのキー入力等の操作を受けて、「クライアントが操作しているもの」の状態変更に関するデータを、サーバーに送信します。
オンライン対戦ゲームのクラス設計
「オンライン対戦ゲームの骨格」を踏まえて、オンライン対戦ゲームのクラスを、以下のように設計しました。
(サーバー側)ゲームクラス
- ゲームクラスは、ゲーム全体を管理するクラスです。
- ワールドクラスのインスタンスを保持します。
- サーバー側の通信イベントを処理します。通信イベント処理とは、クライアントから送信されるデータを受信した時の処理です。「クライアントとサーバーの接続時の処理」「クライアントとサーバーの切断時の処理」「クライアントが操作しているものの状態変更のデータ受信時の処理」等です。
- 周期的処理をします。周期的処理では、ワールドの「更新処理」を呼び出し、更新処理後、「ワールドが保持するゲーム内に存在しているもの」の最新状況を、クライアントへ送信します。
(サーバー側)ワールドクラス
- ワールドクラスは、ゲームワールドを管理するクラスです。
- ゲーム内に存在しているものを保持します(タンクや壁や弾丸のインスタンスを保持します)。
- ゲームから保持されます。
- ゲームワールドを更新します。
(クライアント側)スクリーンクラス
- スクリーンクラスは、ゲームワールドを描画するクラスです。
- クライアント側の通信イベントを処理します。通信イベント処理とは、サーバーから送信されるデータを受信した時の処理です。「クライアントとサーバーの接続時の処理」「ゲーム内に存在しているものの最新状況のデータ受信時の処理」等です。
- ブラウザ画面にゲームワールドを描画します。
その他のクラス
サーバー側、クライアント側には、上記クラス以外にも、ゲーム設定に関するクラスや、画像ファイルに関するクラス、ゲーム内に存在しているものの種類ごとのクラス、便利関数のクラス、などを作ります。
Node.jsのインストール
Node.js をインストールしていない場合は、まず、Node.js をインストールします。
Node.js がインストール済みかは、「node --version」コマンドで確認できます。
Raspbian の場合の例。
Windows の場合の例。
オンライン対戦ゲーム用のフォルダの作成
オンライン対戦ゲーム用のフォルダとして、「OnlineBattleTanks」というフォルダを作成します。
Node.jsのパッケージ管理の初期化
Node.jsのパッケージのインストールに先立ち、パッケージ管理の初期化をします。
「OnlineBattleTanks」フォルダに移動し、下記コマンドを実行します。
package name : 何も入力せずエンターキーを押します ( onlinebattletanksとなる )
version : 何も入力せずエンターキーを押します ( 1.0.0となる )
description : 何も入力せずエンターキーを押します (空となる)
entry point : server.js と入力しエンターキーを押します
test command : 何も入力せずエンターキーを押します (既定のtest コマンドが登録される)
git repository : 何も入力せずエンターキーを押します (空となる)
keywords : 何も入力せずエンターキーを押します (空となる)
author : 何も入力せずエンターキーを押します (空となる)
license : 何も入力せずエンターキーを押します (ISCとなる)
Is this OK? : y を入力しエンターキーを押します。
Windows の場合の例。
「OnlineBattleTanks」フォルダに、「package.json」ファイルが生成されます。
「package.json」ファイルの内容は以下のような内容になっています。
Node.jsのパッケージのインストール
「OnlineBattleTanks」フォルダに、Node.jsのパッケージ 「Express」と「Socket.io」 をインストールします。
オンライン対戦ゲーム用のフォルダに移動し、下記コマンドを実行します。
Windows の場合の例(「Express」のインストール)
Windows の場合の例(「socket.io」のインストール)
「node_modules」
「OnlineBattleTanks」フォルダに、「node_modules」フォルダが生成され、フォルダ内に、Node.jsのパッケージ 「Express」と「Socket.io」および関連パッケージがインストールされます。
「package.json」ファイルの内容は以下のような内容になっています。
(dependenciesセクションが追加され、expressとsocket.ioに関する記述が追加されます。)
サーバー起動処理
サーバー起動処理ファイルを追加します。
「OnlineBattleTanks」フォルダに、「server.js」というファイルを作成し、内容を以下のようにします。
コード解説
- server.jsは、サーバーを起動する際に指定するファイルです。
- 処理の大部分は、Gameクラスのインスタンス等が行いますので、server.js の実装は多くはありません。
- Gameクラスのインスタンスを作成します。( const game = new Game(); )
- Gameクラスのインスタンスの処理を開始します。( game.start( io ); )
- ブラウザからアクセスする際の公開フォルダとして、「/public」を指定します。( app.use( express.static( __dirname + '/public' ) ); )
- サーバーを起動します。( server.listen( ・・・略・・・ ); )
サーバー側スクリプトのフォルダ作成とファイル追加
サーバー側スクリプト用のフォルダとして、「OnlineBattleTanks」フォルダに、「libs」というフォルダを作成します。
ゲームクラスの追加
サーバー側スクリプトフォルダにゲームクラス定義ファイルを追加します。
「libs」フォルダに、「Game.js」というスクリプトファイルを追加し、内容を以下のようにします。
コード解説
- ゲームクラスは、ゲーム全体を管理するクラスです。
- ワールドクラスのインスタンスを保持します。( コンストラクタの const world = new World( io ); )
- サーバー側の通信イベントを処理します。通信イベント処理とは、クライアントから送信されるデータを受信した時の処理です。「クライアントとサーバーの接続時の処理」「クライアントとサーバーの切断時の処理」「クライアントが操作しているものの状態変更のデータ受信時の処理」等です。この章の実装としては、まだ「クライアントが操作しているものの状態変更のデータ受信時の処理」の実装はありません。( io.on( ・・・略・・・ ); )
- 周期的処理をします。周期的処理では、ワールドの「更新処理」を呼び出し、更新処理後、「ワールドが保持するゲーム内に存在しているもの」の最新状況を、クライアントへ送信します。この章の実装としては、まだゲームワールドには何もないので、ゲームワールドの更新の処理時間のみをクライアントへ送信します。( setInterva( ・・・略・・・ ); )
ワールドクラスの追加
サーバー側スクリプトフォルダにワールドクラス定義ファイルを追加します。
「libs」フォルダに、「World.js」というスクリプトファイルを追加し、内容を以下のようにします。
コード解説
- ワールドクラスは、ゲームワールドを管理するクラスです。
- この章の実装としては、まだゲームワールドには何もないので、関数の定義はありますが、処理はありません。。
- ゲームワールドを更新します。( update関数 )
- 更新処理では、オブジェクトの座標値の更新( updateObjects関数 )、衝突チェック( checkCollisions関数 )、新たな行動( doNewActions関数 )を行います。
ゲーム設定クラスの追加
サーバー側スクリプトフォルダにゲーム設定クラス定義ファイルを追加します。
「libs」フォルダに、「GameSettings.js」というスクリプトファイルを追加し、内容を以下のようにします。
コード解説
- サーバー側スクリプトでのみ使用する設定を、ゲーム設定クラス( GameSettingsクラス )にまとめておきます。
- (クライアント側スクリプトでのみ使用する設定は、後述の描画設定クラス( RenderingSettingsクラス )にまとめておきます。)
- (サーバー側スクリプトとクライアント側スクリプトの両方で使用する設定は、後述の共有設定クラス( SharedSettingsクラス )にまとめておきます。)
公開フォルダ・ファイルの作成
公開フォルダとして、「OnlineBattleTanks」フォルダに、「public」というフォルダを作成します。
「public」フォルダに、「index.html」というウェブページファイルを作成し、内容を以下のようにします。
コード解説
- ゲーム画面となるキャンバスを用意します。( <canvas id="canvas-2d"></canvas> )
- socket.ioライブラリを読み込みます( <scrpit src="/socket.io/socket.io.js"></script> )( 「/socket.io/socket.io.js」ファイルはsocket.ioサーバーによって自動生成されます。参照:https://github.com/socketio/socket.io-client )
- jQueryライブラリを読み込みます( <scrpit src="https://ajax.googleapis.com/ajax/libs/jquery/・・・略・・・"></script> )
- 自作のjsファイルを読み込みます。( <script src="./js/・・・略・・・></script> )
画像ファイル用のフォルダの作成と画像ファイルの配置
画像ファイル用のフォルダとして、「public」フォルダに、「images」というフォルダを作成します。
「public/images」フォルダに、背景画像ファイルを配置します。
背景画像ファイルは、フリーゲームやオープンソースゲーム用のメディア倉庫である「OpanGameArt.org」から、「athile」さんが公開している「Seamless Grass Textures (20 pack)」より、「grass01.png」を借用しました。
クライアント側スクリプトのフォルダ作成とファイル追加
クライアント側スクリプト用のフォルダとして、「public」フォルダ下に、「js」というフォルダを作成します。
クライアント側開始処理
クライアント側スクリプトフォルダにクライアント側開始処理ファイルを追加します。
「public/js」フォルダに、「client.js」というスクリプトファイルを追加し、内容を以下のようにします。
コード解説
- client.jsは、クライアント側の最初に呼び出される処理です。
- 処理の大部分はScreenクラスのインスタンス等が行いますので、client.js の実装は多くはありません。
- Screenクラスのインスタンスを作成します。( const screen = new Screen( socket, canvas ); )
- Screenクラスのインスタンスの描画処理を開始します。( screen.animate( 0 ); )
スクリーンクラスの追加
クライアント側スクリプトフォルダにスクリーンクラス定義ファイルを追加します。
「public/js」フォルダに、「Screen.js」というスクリプトファイルを追加し、内容を以下のようにします。
コード解説
- スクリーンクラスは、ゲームワールドを描画するクラスです。
- クライアント側の通信イベントを処理します。通信イベント処理とは、サーバーから送信されるデータを受信した時の処理です。「クライアントとサーバーの接続時の処理」「ゲーム内に存在しているものの最新状況のデータ受信時の処理」等です。( initSocket関数 )
- ブラウザ画面にゲームワールドを描画します。( animate関数、render関数、renderField関数 )
アセットクラスの追加
クライアント側スクリプトフォルダにアセットクラス定義ファイルを追加します。
「public/js」フォルダに、「Assets.js」というスクリプトファイルを追加し、内容を以下のようにします。
コード解説
- アセットクラスは、画像ファイルに関するクラスです。
- 背景画像を読み込みます。( this.imageField.src = '../images/grass01.png' )
- 画像における背景画像として使用する領域を指定ます。( this.rectFieldInFieldImage = { sx: 0, sy: 0, sw: 512, sh: 512 }; )
描画設定クラスの追加
クライアント側スクリプトフォルダに描画設定クラス定義ファイルを追加します。
「public/js」フォルダに、「RenderingSettings.js」というスクリプトファイルを追加し、内容を以下のようにします。
コード解説
- クライアント側スクリプトでのみ使用する設定は、描画設定クラス( RenderingSettingsクラス )にまとめておきます。
- (サーバー側スクリプトでのみ使用する設定を、前述のゲーム設定クラス( GameSettingsクラス )にまとめておきます。)
- (サーバー側スクリプトとクライアント側スクリプトの両方で使用する設定は、後述の共有設定クラス( SharedSettingsクラス )にまとめておきます。)
共有設定クラスの追加
クライアント側スクリプトフォルダに共有設定クラス定義ファイルを追加します。
「public/js」フォルダに、「SharedSettings.js」というスクリプトファイルを追加し、内容を以下のようにします。
コード解説
- サーバー側スクリプトとクライアント側スクリプトの両方で使用する設定は、共有設定クラス( SharedSettingsクラス )にまとめておきます。
- (サーバー側スクリプトでのみ使用する設定を、前述のゲーム設定クラス( GameSettingsクラス )にまとめておきます。)
- (クライアント側スクリプトでのみ使用する設定は、前述の描画設定クラス( RenderingSettingsクラス )にまとめておきます。)
動作を確認
サーバーを起動します。
「OnlineBattleTanks」フォルダで、以下のコマンドを実行します。
「Server on port 1337」と表示されます。
ブラウザから、「localhost:1337」にアクセスします。
背景画像が表示されます。
画面右上に「サーバーでのゲームワールドの更新処理時間」が表示されます。
起動したサーバーは、「Ctrl + C」で終了します。
ダウンロード
デモ
デモ(Google App Engine):https://online-battle-tanks.an.r.appspot.com/index01.html
デモ(Heroku):https://online-battle-tanks-22-cf6589d25849.herokuapp.com/index01.html