このサイトは、只今WEB業界で活躍中のデザイナー、プログラマーの方々の情報を集めたweb統合情報サイトです。

web帳

記事詳細

2014.03.31

話題のnode.jsで リアルタイム PvP(プレイヤー同士バトル)アプリ作成 実装解説 node.js + socket.io + Express

最近、何かとnode.jsを扱っております。

いやぁ。面白い。

WebSocketをより簡単に扱うためのライブラリ「socket.io」

webフレームワークの「Express」等を使うことによって更により簡単にリアルタイムのコンテンツを作る事が出来ます。

今回は「socket.io」「Express」の実装方法などを記述していきたいのですが、

web上や書籍等でよく、「チャットシステム」等の作成方法等がピックアップされていますので、

それとは違った リアルタイムゲームを作ってみたいと思います。というか、作りました。

リアルタイムPvP (Player VS Player)の実装ということで、

世界的にもっとも有名なPvPバトルとして知られる

「じゃんけん」をnode.jsで実装してみたいと思います!

何はともあれ、youtube とデモをUPしましたので、見て扱って頂ければと思います。

こんな感じです。

リアルタイムPvPバトル 「じゃんけんバトル」デモ

フローチャート

簡単ではありますがフローチャートを作りました。

フローチャートを作る際は  WEBドローツールである「Cacoo」等、ワイヤーフレーム作成Webサービスを利用するといいでしょう。

cacoo

https://cacoo.com/

今回は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 公式サイト

http://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://www.nodejitsu.com

詳細

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リアルじゃんけんバトルの実装詳細でした。

ではでは

  • RSSを登録する

  • follow us in feedly

Graphical FrontEnd Engineer
- Daisuke Takayama

MAD CITY 北九州市で生まれ育つ。20代はバンド活動に明け暮れ、ふと「webデザイナーになりたい。」と思い、デジタルハリウッド福岡校入学。卒業後、数々の賞を受賞、web業界をざわつかせる。
現在、港区六本木で活動中。

WEBデザイナーの、WEBデザイナーによる、WEBデザイナーの為のサイト。「みんなで書こう!」と仲間を募ってみたが、結局書くのは自分だけとなってしまいました。日々のメモを綴っていきます。