話題のnode.jsで リアルタイム PvP(プレイヤー同士バトル)アプリ作成 実装解説 node.js + socket.io + Express
2014.03.31
この記事は最終更新日から1年以上が経過しています。
最近、何かとnode.jsを扱っております。
いやぁ。面白い。
WebSocketをより簡単に扱うためのライブラリ「socket.io」や
webフレームワークの「Express」等を使うことによって更により簡単にリアルタイムのコンテンツを作る事が出来ます。
今回は「socket.io」や「Express」の実装方法などを記述していきたいのですが、
web上や書籍等でよく、「チャットシステム」等の作成方法等がピックアップされていますので、
それとは違った リアルタイムゲームを作ってみたいと思います。というか、作りました。
リアルタイムPvP (Player VS Player)の実装ということで、
世界的にもっとも有名なPvPバトルとして知られる
「じゃんけん」をnode.jsで実装してみたいと思います!
何はともあれ、youtube とデモをUPしましたので、見て扱って頂ければと思います。
こんな感じです。
フローチャート
簡単ではありますがフローチャートを作りました。
フローチャートを作る際は WEBドローツールである「Cacoo」等、ワイヤーフレーム作成Webサービスを利用するといいでしょう。
cacoo
今回はCacooを使ってフローチャートを作成。
デザイン
TOP画面
じゃんけんバトルのtop画面となります。
バトルスタートボタンを押してバトルメイン画面に遷移し、バトルを開始します。
対戦相手待ち
バトルメイン画面に誰も存在しない場合は、上記の待機状態となります。
この際、誰かがアクセスするとバトルが開始します。
じゃんけん開始(メイン画面)
バトルメイン画面となります。
下の3つの手から出す手を選択します。
画面上は選択した手を表示するエリアです。
相手の手は選択したか、未だかのみが分かります。
結果画面
2人手を選択するか、タイムアウトすると結果画面へと遷移します。
「TOPへ戻る」ボタンを押すことによってTOP画面へと戻ります。
socket.io
では早速、scoket.ioをインストール。
したいのですが、そもそもnode.jsってなんだよ?って方はこちら等を参照して頂ければと。
サーバーサイドJavascript 『node.js』 macにインストール・使用方法
npmを利用して様々なモジュールをインストールをしていくのですが、
ローカルディレクトリとグローバル領域にインストールする方法があります。
ローカルインストール
npm install モジュール名
上記の npmコマンドでローカルインストールになります.
現在のカレントディレクトリの node_modules フォルダの中にインストールします。
この際、
./node_modules, ../node_modules, ../../node_modules という順番に探して、最初に見つけた場所に格納されます。
遡ってnode_modulesが見つからなければカレントディレクトリにnode_modulesを作って格納されます。
グローバルインストール
グローバルインストールは全てのプロジェクト共通で使いたいモジュールをインストールする際に使います。 ローカルインストールの方法に -g オプションを付与することによってグローバルインストールとなります。 また、グローバルインストールした場合、そのディレクトリにパスが通り、 coffeescript や mocha を直接コマンドとして実行出来るようになります。
npm install -g モジュール名
それでは、socket.ioに関してなんですが、公式サイトやGithubリポジトリは以下となっております。
socket.io 公式サイト
Github
https://github.com/learnboost/socket.io
インストール
npm install socket.io
npm コマンドでサクっとインストールすることが出来ます。
んで、sockets.ioとはなんぞや?
てところですが、公式サイトにも記載されているように、
「Socket.IO は複数の通信メカニズムを抽象化することで、 全てのブラウザ、デバイスでリアルタイムアプリを実装可能にするために開発されました。 とても手軽にリアルタイムなアプリを 100% JavaScript で作成可能です。」
そう。気軽にリアルタイムなアプリを100%Javascriptで作成が可能なのです!
簡単に説明させていただきますと、
通信の方法として、定期的にリクエストを送信する「ポーリング」、更にタイムアウト時間を伸ばし長時間に渡って断続的にレスポンスを送信する「ロングポーリング」がありますが、これらの方法では双方向かつ非同期での完璧な通信は実現できませんでした。
そこで2011年に提案されたのが、双方向通信プロトコル「WebSocket」です。
「WebSocket」では独自のプロトコルを利用することにより、非同期かつ双方向の通信を可能としております。
が、「WebSocket」は比較的新しいWebブラウザのみのサポートとなり、IE9以前やスマホ、タブレット等に搭載されている一部のwebブラウザはサポートされていません。
それ以外の方法として、Adobe Flash Playerにもソケット(Socket API)の機能があり、クライアントサーバー間で双方向の非同期通信が可能となっています。
これもまた、お互い webブラウザでFlashプラグインが利用できる環境ではないといけない。といった制限があります。
こういった制限のある双方向通信プロトコル「Websocket」ですが、「Socket.IO」というモジュールを利用することによって、開発者も利用者も接続プロトコルを考慮せず、容易に双方向通信が行える様になる。
といったところでしょうか。
Express
Expressをインストールしていきます。
詳細はこちらを参照して頂ければと思います。
サーバーサイドJavascript node.js expressやらnpmやら
npm install -g express
express app
expressコマンドでappという名前のアセットを生成していくのですが、
デフォルトで生成するとテンプレートエンジンはjadeとなってしまうのでオプションを付与して作成します。
今回はectを使って作成するのですが、ectのオプションは未だ対応していないようなので、
ejsと使用方法が近いため、一旦ejsで生成を行います。
express app --ejs
テンプレートのviewsが index.ejsで生成されているのが確認できるかと思います。
ECT Javascript テンプレートエンジン インストール
さて、ectを使って作成しますので、テンプレートエンジンであるectをインストールしましょう。
まず、package.jsonを修正。
“ejs”の部分を “ect”: “*”に変更。
{ "name": "application-name", "version": "0.0.1", "private": true, "scripts": { "start": "node app.js" }, "dependencies": { "express": "3.5.0", "ect": "*", "socket.io": "*" } }
npmコマンドでインストール出来ます。
npm install
を行うと、「node_modules」内にectがインストールされます。
また、個別の指定を行いインストールも可能です。
npm install ect
ECTに関しての詳細は
node.js テンプレートエンジン ECTが爆速!! らしい件(ectの使い方)
を参照して頂ければと思います。
修正を行っていきます。
まず、app.js を修正していきます。
ectを使っていきますので requireでectを読み込み、ectの設定を追加。
var ECT = require('ect'); app.engine('ect', ECT({ watch: true, root: __dirname + '/views', ext: '.ect' }).render); app.set('view engine', 'ect');
詳細は
node.js テンプレートエンジン ECTが爆速!! らしい件(ectの使い方)
にも記載しております。
index.ejsをindex.ectに変更し
index.ejsを修正。
パラメータは@付与なので以下の様に修正。
<%= title %>
↓
<%= @title %>
node app.js
できちんと起動できているか確認しましょう。
ディレクトリ構造
node_modules (インストールした nodeモジュール)
|- .bin
|- ect
|- express
|- socket.io
public (expressで生成されたディレクトリ以下のファイルを格納)
|- css
|- img
|- js
|- scss
routes (ルーティングを行うjsファイルを格納)
index.js
sockets(サーバー側 js)
server.js
views (ECTテンプレートファイル)
|- battle.ect (バトルメイン画面)
|- index.ect (top画面)
|- layout.ect (共通レイアウト)
|- result.ect (バトル結果画面)
Github
ソースはGithubのsample レポジトリ内に公開しております。
ご自由にお使いください。
Github
https://github.com/webcyou/sample/tree/master/node/pvp/app
routes/index.js ・ server.js・ battle.js
views以下(html,css,img)の説明は省略させて頂きます。
exports.index = function(req, res) { res.render('index', { title: 'Node.js PvP じゃんけん' });};
top画面である、exports.index〜 では、indexを叩いた時に呼び出す処理を行っております。
上記の場合ですと title: ‘Node.js PvP じゃんけん’ をレンダリング時に受渡しております。
その下に記述しているバトルメインの画面になる、exports.battle 〜では、roomName、yourNameを生成しております。
yourNameはランダムの数字を付与して生成。
var roomName = 'battleRoom', yourName = 'user' + Math.floor(Math.random() * 100)
結果画面である、exports.result 〜 では、
getで受け取ったrequestをレンダリング時にbattleResultのパラメータとして受渡しております。
var battleResult = req.query.result
server.js
サーバー側の処理にあたる、server.jsを見ていきます。
var battleCount = 0, roomCount = 1;
battleCountはユーザーが何人存在しているか、roomCountはバトルの部屋の数となってます。
exports.onConnection = function (socket) {
はソケットが接続された際に実行される処理となっております。
socket.emit();
socket.on();
等のメソッドがありますが、これらは以下の様になります。
addListener(event, listener) | 指定したイベントにイベントハンドラーを登録 |
on(event, listener) | 指定したイベントにイベントハンドラーを登録(addListenerメソッドの別名) |
once(event, listener) | 指定したイベントに一度だけ実行されるイベントハンドラーを登録する |
removeListener(event, listener) | 指定したイベントを削除する |
removeAllListener([event]) | 指定したイベントを全て削除する |
setMaxListeners(n) | 登録できるイベントハンドラー数の上限を変更する。n引数には整数で上限数を指定する |
listeners(event) | 指定したイベントに登録されているイベントハンドラーを取得する |
emit(event,[arg1],[arg2],…) | 指定したイベントを発生させる |
となります。
onで登録し、emitで呼び出しています。
server.jsで使用している各イベントは以下の通りとなります。
check credential | 認証情報の確認 |
disconnect | クライアント側から接続が切断された際に発生 |
setHand | クライアント側から選択された手をサーバー側で保存 |
getEnemyData | クライアント側から選択された相手の手の情報を取得、自分の方に情報を送信 |
battleTimeout | タイムアウト時に手が選択されているか確認 |
battleReset | あいこの場合、再戦となるためバトルデータをリセット |
battle.js
public/js/ 内に格納されている battle.jsが、クライアント側のJavascriptとなります。
クラスである「battleControler」がユーザー側のイベント等によって処理されるscriptとなります。
targetElemはdomのオブジェクト、battleDataはデータを管理しているオブジェクトとなります。
socketに登録されているイベントは以下となります。
connected | サーバーへの接続が完了した際に発生 |
credential ok | ユーザー名に問題ない場合に発生 |
userName exists | 同ユーザー名を生成した際に発生 |
battle wait | 相手がいない場合の待機状態の際に発生 |
battle start | 条件が揃いバトル開始出来る際に発生 |
battle judge | バトルのジャッジを行う際に発生 |
battle timeout | タイムアウト時に発生 |
update members | 退出、入室時に発生 |
getStatus | 相手の情報が届いた際に発生 |
battleControlerの処理
init | 初期化 |
battleStart | バトル開始処理 |
battleReset | バトルデータリセット |
battleReStart | バトル再開 |
timeCount | タイムカウント処理 |
commandSet | 手を選択処理 |
enemySet | 相手の情報セット |
judge | バトル判定 |
battleWait | 待機状態処理 |
battleResult | バトル結果処理 |
となっております。
作成データデプロイ
今回はさくらVPSを利用してデプロイを行いました。
通常の起動コマンド
node app.js
ではコンソールを閉じてしまうとアプリケーションが終了してしまうため、永続化する必要があります。
nohupコマンドを使うことによってNode.jsアプリケーションを永続化することができます。
nohup コマンド 引数
例:
nohup node app.js &
この際、Node.jsから出力はnohup.outファイルに書きこまれます。
cat nohup.out
nohupコマンドで実行しているNode.jsアプリケーションを終了させるには、psコマンドでプロセス番号を調べ、killコマンドで対応するnodeプロセスを終了させると良いです。
ps ux | grep node
kill 55778
forever
上記の方法だと、プログラムに問題が発生した際サービスが停止してしまいます。
それを避けるために、プログラムを監視し、意図しない終了が検出されたら再度プログラムを起動させるようにNode.jsのforeverパッケージを使います。
forever
foreverはnodejitsu社がオープンソースで開発・公開を行っております。
nodejitsu 公式
詳細
https://blog.nodejitsu.com/keep-a-nodejs-server-up-with-forever/
Github
https://github.com/nodejitsu/forever
npmコマンドでインストールできます。
npm install -g forever
foreverの使用方法
forever [options] [action] SCRIPT [script-options]
プログラム実行
forever start app.js
実行プロセス確認
forever list
実行プロセス停止
(listで実行プロセス番号を確認し、停止したいプロセス番号を入力)
forever stop 0
このように、foreverで実行したプログラムはデーモンとして実行され、ターミナルの終了やログアウトを行ってもそのまま実行を継続されます。
また、意図しない終了が検出されたら再度プログラムを起動が行われます。
と、PvPリアルじゃんけんバトルの実装詳細でした。
ではでは