2012年8月25日土曜日

websocket + HTML5(canvas)でのゲーム開発(ボンバーマン風)

このエントリーをはてなブックマークに追加

はじめに

エンジニアの@ryooo321です。
よろしくお願いします。

Happy Elements株式会社では勉強会が活発に行われており、
その中の1つに「1.5時間で○○を作る」エンジニア向けワークショップがあります。(毎週開催@京都)
※ ○○は毎週かわり、設計/実装方法などは自由です。

今回はワークショップ2回(計3時間)で作成したボンバーマン風ゲームの紹介を通して、
他人とリアルタイムで遊べるゲームの可能性を感じていただければと思います。
※ 技術的にはwebsocket、canvasを利用
※ ライブラリ/ツールとしてNode.js、CreateJS、socket.io、coffeescriptを利用
※ 急いで作ったのでほとんどリファクタリングされていませんmm

また、おまけとして
サーバーサイドでのcanvas描画とwebsocketでのバイナリメッセージについて
試してみた結果を共有します。

ボンバーマン風

紹介

基本的にはcanvasで表示する形のゲームですが、
websocketを使って異なる端末・ブラウザで描画情報を同期しています。
下記程度のアウトプットを、338行程度のコーディングで可能でした。

※ わかりづらいですが、2つのブラウザで2キャラが操作できており、表示が同期しています。
※ 動画内consoleに流れているのがwebsocketの通信データです。

ソース

説明

1. サーバーサイド(server.coffee)
マップや爆弾や敵、キャラや炎などあらゆるオブジェクトはNodeサーバー上に持っています。
Nodeサーバーは10回/秒のペースで、canvas描画情報をブラウザにpushしています。(描画差分のみ)

サーバー起動時にマップを作成するので、
起動後にアクセスしたユーザーは同じマップで遊ぶことになります。

2. クライアントサイド(public/javascripts/client.coffee)
ブラウザはCreateJSを使ってcanvasに描画するだけです。
画面全体の描画をさけるために静的要素のcanvasと頻繁に書き変わる要素のcanvasを分けました。
静的要素(芝/石)のcanvasは初回のみ描画するようにすることで付加軽減を図っています。
※ キャラ、敵、炎、爆弾の変更情報のみ通信しています。

所感

[CreateJSすごい]
CreateJS(Easel.js)を使うとcanvas操作が驚くほど楽になりました。
(enchant.js以来の衝撃でした++)

[安定のcoffeescript]
coffeescriptを使うことでクラス化やリファクタリングが楽になり、
単純にタイピング量も減りました。

[websocketはリアルタイムで有利に感じました]
websocketを使うとhttpで同じ回数通信するより通信量が少なくなるので、
大規模にやるならwebsocketは有利だと思います。
しかし、この程度のjsonデータ量であれば規模によっては
ブラウザから同じ回数のAPIリクエストを飛ばしてもよいかもしれません。
500qpsさばける構成なら50人が同時接続しても問題ないかもしれません。
(1クライアントあたり10qpsとして)(簡単なjsonを取ってくるだけなので今のつくりでは重くはありません)


おまけ - サーバーサイドでのcanvas描画

概要

サーバーサイドがjavascriptなので、サーバーでcanvas描画しbitmapをブラウザに転送してみました。
ついでにバイナリでのwebsocket通信もやってみました。
ボンバーマン風で使ったwebsocketライブラリ(socket.io)は現時点でバイナリメッセージに対応していないので、代わりにnode-websocketを利用しました。

ソース

説明

下記でcanvasが使えるようになります。
brew install cairo
brew link cairo
npm install canvas
node.js内では
下記でcontextがとれ、描画できます。
Canvas = require('canvas')
canvas = new Canvas(400, 400)
ctx = canvas.getContext("2d")
描画したcontextは、下記いずれかの方法でブラウザで利用できます。(きっと他にもあります)
1. base64エンコードしてwebsocketでpush
socket.sendUTF(canvas.toDataURL())
2. bitmapの色情報のbyte情報をwebsocket(ArrayBuffer)でpush
imagedata = c.getImageData(0, 0, 400, 400)
bin = imagedata.data
len = bin.length
buff = new Buffer(len)
for i in [0..(len-1)]
  buff[i] = bin[i]
socket.sendBytes(buff)
3. 画像ファイルとしてサーバーに保存して、ブラウザからhttpリクエスト
# 実装例は割愛

所感

[微妙]
サーバーサイドcanvasについては、あまり有用とは感じませんでした。
素直にブラウザでやればよいと思います。
canvasで描画した画像をブラウザに送る形にすることで、
ブラウザ側の処理を軽減できるケースがあるとは思います。
SmartPhoneなどのパワーのない端末で巨大なcanvasを使いたい場合などは、
サーバーで大きなcanvasを作成して一部分だけcontext.getImageData()して転送したりもできそう。

[もっといろいろできる]
バイナリメッセージを使えば、音楽・動画も他人と簡単に共有できるようです。
WebSocketのバイナリメッセージを試したら、ウェブの未来が垣間見えた

[ライブラリ利用]
サーバーサイドでCreateJSを使うにはwindowオブジェクトやdocumentオブジェクトを
Node.jsに組み込まなければなりません。
しかし、jsdomを入れるすることでCreateJSやjQueryも利用できるはずです。
※ jsdomは、nodeの環境にW3C準拠のdomオブジェクト群を定義してくれるライブラリです。

[データサイズ]
bitmapは1ピクセルに4byte使うので、縦横400pxでは640,000byteほどのbitmap配列になります。
これをwebsocketで転送してブラウザで表示するには1push/sが限度でしたorz
一方、今回の例では透明領域の大きい画像だったこともあり、
base64にエンコードする方法だと2,000byte程度でした。
これだと、100push/sでもさくさく表示できました。
※ localhostのサーバーです。感覚値で恐縮です。

[Blobでの通信]
ArrayBufferでなくBlobで通信するバイナリメッセージもあり、
canvasでない場合はBlobで通信した方が早いでしょうね。

画像ファイルをBlob転送してみましたが大きくてもざっと問題なさそうでした。



関連情報


・Node.js
http://nodejs.org/

・CreateJS
http://createjs.com/#!/CreateJS

・coffeescript
http://coffeescript.org/

・websocket
http://www.html5.jp/trans/w3c_websockets.html
http://tools.ietf.org/html/rfc6455

・socket.io
http://socket.io/

・jsdom
https://github.com/tmpvar/jsdom

一緒に働きたい方、絶賛 募集中
京都で開発してみたいというエンジニアの皆さん、ご応募お待ちしています!
技術力を伸ばしたい学生さん、アルバイトも可能なのでご応募お待ちしています!
大阪、滋賀、神戸から通勤実績あり


以上、長文にお付き合い下さいましてありがとうございました。

1 件のコメント: