enchant.js と WebSocket を組み合わせた通信ゲームサンプル
tankの拡張
enchant.js応用編のページに記載したtankゲームを,WebSocketによる通信も加えた形でブラウザ対戦型ゲームに拡張してみる.
ソースコード一式はGitLabに置いた.
サーバを起動するまで(情報コースの端末室のPCを使っていると想定)
$ cd ~/csd $ git clone https://gitlab.cis.iwate-u.ac.jp/kimura/tank_nodejs.git $ cd tank_nodejs $ npm install $ node main.js
これで(ポート番号6004で待ち受ける)サーバが立ち上がる.
注意
もし,nodeコマンドでエラーが出るようなら,main.js内のポート番号を(6004ではなく)適当に変更してから再度,実行してみると良い.
遊び方
chromium-browserで(Firefoxだと上手く動作しない模様)
アクセスURI
http://サーバを起動したPCのIPアドレス:6004/
に接続する.名前を聞かれるので,適当に入力するとゲームスタート.
- 誰かが接続してくるとその機体が現れる
- 複数人対応.4〜5人でわちゃわちゃもできる(はず)
- カーソルキーの左右で向き
- カーソルキーの上下で前進・後退
- スペースキーで弾発射
- (自分以外の)誰かの機体に弾が当たれば+1点,逆に撃たれたら-1点
- 終了処理とかは特に組み込んでいないので注意.ブラウザを閉じれば,その人の機体は画面から消える
ごく簡単な解説
この手のプログラミングでまず考えなければならないことは,
- 「どんなデータを,どんなタイミングで通信するか」
- 「サーバ側に行わせる処理をどうするか」
である.この通信型tankゲーム(の動き)を簡単に分析してみると,
- クライアントは,いつどのタイミングで接続してくるか分からない
- 同様に,いつ切断するかも分からない
- 接続されたクライアント達は,各々が勝手に動き回り,適当な向きで弾を発射するはず(プレーヤーの操作は完全に非同期)
- どのクライアントの弾がどのクライアントの機体に当たったかを判定して,スコア更新する必要がある
といったところであろうか.ここで,何も考えずに「とにかくクライアント(プレイヤー)ごとに見えているゲーム画面を同期」させようとして「何でもかんでもサーバ側で処理」しようとすると,通信を圧迫して輻輳が発生し,パケット渋滞を引き起こしてしまうことになりかねない.enchant.jsは,デフォルトでは1秒に30回の書き換えを行う訳だから,もし,各クライアントが書き換え処理を行う度にいちいちサーバと通信していたら大変なことになる.(と,予測がつくようになって欲しい)
そこで,完全な同期は諦め,サーバでは
- 誰か(1つのクライアント)の機体が動いたら,その座標と向きだけを他の人全て(他クライアント達)に送る,(main.jsの65-75行目)
- 誰か(1つのクライアント)が弾を発射したら,その発射位置と弾の向きだけを他の人全て(他クライアント達)に送る,(main.jsの77-85行目)
- 誰かがサーバに接続してきた,あるいは誰かが切断したら,その情報を他の人全て(他クライアント達)に知らせる,(main.jsの41-50,52-62,87-97行目)
といった処理だけを(socket.broadcast.emit
(イベントを送信してきた以外のクライアント全部に対してデータを送信)で)行うようにし,画面表示や衝突判定,スコア更新は各クライアントが自分自身で行うようにすると通信コストが減らせる.各クライアント達は
- サーバから「誰か接続したよ」とメッセージが来たら,その位置情報などを元に(敵としての)機体を発生させる,(tank_nodejs.jsの207-214行目あたり)
- サーバから「この人が動いたよ」とメッセージが来たら,その位置情報などから敵機の表示位置を更新する,(tank_nodejs.jsの217-222行目)
- サーバから「この人が弾撃ったよ」とメッセージが来たら,その位置情報などから敵機の弾を発生させ(て,それ以降の弾の動きはクライアントで処理す)る,(tank_nodejs.jsの224-228行目)
- 自機の動きは基本的に自身で処理する.ただし,「弾を撃つ度に」サーバへその旨を知らせる,(tank_nodejsの68-73行目)
- 定期的にサーバーに自機の更新データを送る,(tank_nodejs.jsの240-249行目)
- 自機や敵機の弾表示や衝突判定,スコア更新まで全て自身で処理する,
- サーバから「この人が切断したよ」とメッセージが来たら,その機体を削除する,(tank_nodejs.jsの231-234行目)
という形でプログラミングすれば良い.つまり,厳密にはAというプレイヤーとBというプレイヤーが見ているゲーム画面が微妙に食い違っているかもしれないが,そこには目を瞑ろう,ということである.これは,通信対戦ゲームに必要なある種の「割り切り」であり,「全体的に見たらここの食い違いは許容されていい」といったような落としどころをうまく見つけることが重要となる.
なお,WebSocket-Chatの解説ページに記載した通り,サーバからのメッセージを待つ部分はsocket.on
で登録すれば良いし,サーバに情報を送る際にはsocket.emit
を実行すれば良い.
演習では主に,閉じたLAN環境内での利用になるだろうから,通信速度や遅延の問題はそこまで重要ではないかもしれないが,通信を取り扱う予定の班は,このあたりにも注意しておいて欲しい.