#今回使う技術の要素
##クライアントサイド
###phina.js
ver0.2.0
今回の主役
http://phinajs.com/
日本産のjavascriptでのゲームライブラリ
これを用いて多人数参加型ゲームをどう作るかという例
##通信
###websocket
リアルタイム通信を行う方法はいくつもあるが、今回用いるのはこれ
通信プロトコルの一種でブラウザが古いものでは動作しない点に注意。
細かい話は省略
今回はphina.jsと連携させる実装例を紹介します。
##サーバーサイド
###Spring-framework
ver4.3.2
Javaの「Spring」を扱うためのフレームワーク
websocketを扱えるということで今回サーバーサイドに使用
#目標
多人数参加型ゲームを作るにあたって今回は要素として必要な通知機能を作成する。
##詳細
画面上に「ベル」ボタンがあり、押下すると同じページを開いている全員に音が鳴る
#製造
他のページで説明されていることはどんどん省略していく
##サーバーサイド
http://acro-engineer.hatenablog.com/entry/2014/01/17/170607
を参考にTextWebSocketHandlerを継承し特定のメッセージ(「a」一文字とかでよい)が来たら音を鳴らすメッセージ(「b」一文字とかでよい)をブロードキャストするハンドラーを作成。
web.xmlなどを適宜設定
以下設定の必要な部分抜粋
<servlet>
<servlet-name>bell</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>bellWebSocket</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>bell</servlet-name>
<url-pattern>/bell</url-pattern>
<url-pattern>/bell.jsp</url-pattern>
<url-pattern>/bell.html</url-pattern>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>bellWebSocket</servlet-name>
<url-pattern>/ws</url-pattern>
</servlet-mapping>
<context:component-scan base-package="hoge.bell.controller" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<websocket:handlers>
<websocket:mapping path="/ws" handler="bellHandler" />
</websocket:handlers>
<bean id="bellHandler" class="hoge.bell.handler.BellHandler" />
@Controller
public class Bell {
@RequestMapping("/bell")
public ModelAndView index() {
return new ModelAndView("bell");
}
}
public class BellHandler extends TextWebSocketHandler {
/** セッション一覧 */
private Map<String, WebSocketSession> sessionsMap = new ConcurrentHashMap<String, WebSocketSession>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
this.sessionsMap.put(session.getId(), session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
this.sessionsMap.remove(session.getId());
}
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String mes = message.getPayload();
if (mes.equals("a")) {
TextMessage broadcastMessage = new TextMessage("b");
for (Entry<String, SessionInfo> entry : this.sessionsMap.entrySet()) {
entry.getValue().sendMessage(broadcastMessage);
}
}
}
}
などとしてwebsocketのアクセスに/wsを、ページの表示に/bellを割り当てる
##クライアントサイド
こちらが今回のメイン
websocketを生成しonloadでGameAppをrunする。
onmessageの処理とphina.jsを連携させる。
<html>
<head>
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://cdn.rawgit.com/phi-jp/phina.js/v0.2.0/build/phina.js"></script>
<script src="./js/bell.js"></script>
</head>
<body>
</body>
</html>
phina.globalize();
var SOUNDS = {
sound: {
'bell': './se/bell.mp3',
},
};
phina.define('WebsocketGameApp', {
superClass: 'GameApp',
init: function(params) {
this.superInit(params);
if (params.ws) {
this.ws = params.ws;
}
if (params.mesHandler) {
for (header in params.mesHandler) {
this.addMesHandler(header, params.mesHandler[header]);
}
}
},
send: function(mes) {
if (this.ws) {
this.ws.send(mes);
}
},
addMesHandler: function(header, handler) {
this.on('mes' + header, handler);
},
_accessor: {
ws: {
get: function() { return this._ws; },
set: function(ws) {
this._ws = ws;
var that = this;
this._ws.onmessage = function(mes) {
that.flare('mes' + mes, {});
};
},
},
},
});
phina.define('MainScene', {
superClass: 'DisplayScene',
init: function(params) {
this.superInit(params);
var that = this;
this.bell = Button({
text: 'ベル',
height: this.gridY.span(2),
width: this.gridX.span(5),
fontSize: 16,
})
.addChildTo(this)
.setPosition(this.gridX.center(), this.gridY.center());
this.bell.onpush = function(){
that.app.send('a');
};
},
});
phina.main(function() {
var ws = new WebSocket("ws://yourdomain/contextPath/ws");
// アプリケーション生成
var app = WebsocketGameApp({
startLabel: 'main', // メインシーンから開始する
assets: $.extend(true, {}, SOUNDS),
ws: ws,
mesHandler: {
b: function() {
AssetManager.get("sound", "bell").play();
},
},
});
ws.onopen = function() {
// アプリケーション実行
app.run();
};
ws.onclose = function() {};
ws.onerror = function(e) {
alert("エラー");
};
});
今回の実装では新たにWebsocketGameAppを定義した。
mesHandlerにオブジェクトとして渡してやり、キーのメッセージが来た際に行う処理をバリューに渡す。
これはメッセージによる処理の判定を外部から注入できるようにするためで、
GameApp内部で規定してしまっても問題はない。
メッセージとしてくる内容がヘッダーとボディーに分かれてボディーを引数として渡してやりたい場合はwsのセッターを
set: function(ws) {
this._ws = ws;
var that = this;
this._ws.onmessage = function(mes) {
var header = mes.split(SEPARATOR)[0];
var body = mes.split(SEPARATOR)[1];
that.flare('mes' + header, {
body: body,
});
};
},
などと変えてやりhandlerの引数で以下のように受け取る
mesHandler: {
sound: function(arg) {
// ここでは引数に音の種類を表す文字列が入ってくるものとする。
// 上記ベルと同じ処理ならばSEPARATOR = ","の場合「sound,bell」のメッセージが渡されるということである。
AssetManager.get("sound", arg.body).play();
},
},
今回オブジェクトのbodyとして渡しているが、ボディをそのまま渡してもよい。