mBaaSを使って無料でチャットゲームを作る(後編)

チャットゲームを作る記事の後編です。前回Pusherとcocos製ゲームを繋いだので、今回はアプリサーバとゲームを繋ぎます。

## Parse.comの紹介
![スクリーンショット 2014-12-11 20.50.25.png](https://qiita-image-store.s3.amazonaws.com/0/2490/3265969c-d372-a250-a73c-c97c5c6741b3.png)
Parse.comはmBaaSのひとつで、以前Facebookに買収されたことで大きな話題を呼びました。似たようなmBaaSが多い中で

* ドキュメントが充実している
* クライアント用SDKの機能が豊富
* 障害が起きてもすぐ分かる(http://status.parse.com/)
* DashBoardのUIが綺麗

など、提供されている機能はもちろんのこと細かい部分まで丁寧に作られている印象があります。

## データを保存してみる
cocosで作ったゲームからデータを保存してみます。Parse.comでのデータストアは明示されていませんが、障害時のエラーメッセージから恐らく実体はMongoDBなので、例えばチャットゲームの場合このように表現できます。
![スクリーンショット 2014-12-12 20.09.31.png](https://qiita-image-store.s3.amazonaws.com/0/2490/6d85e52b-8645-f0c3-06cc-b72c10431341.png)
データの読み書きにはREST APIが提供されているので、それらを使って通信します。

```lua
local xhr = cc.XMLHttpRequest:new()
xhr.responseType = cc.XMLHTTPREQUEST_RESPONSE_JSON
xhr:setRequestHeader("X-Parse-Application-Id", "YOUR_APP_ID")
xhr:setRequestHeader("X-Parse-REST-API-Key", "YOUR_API_KEY")
xhr:setRequestHeader("Content-Type", "application/json")
xhr:open("POST", "https://api.parse.com/1/classes/Message)
xhr:registerScriptHandler(function()
    if xhr.readyState == 4 and (xhr.status >= 200 and xhr.status < 207) then
        print(xhr.response)
    else
        print("xhr.readyState is:", xhr.readyState, "xhr.status is: ",xhr.status)
    end
end)
xhr:send(json.encode({text = "17歳です"}))
```

*※前回同様、クライアントサイドから認証なしでデータを書き込むのは危険なので、実際に稼働させる際は[ガイドライン](https://www.parse.com/docs/data#security)に従って設計すべきです。*

## CloudCode
データを保存することができたので、Pusherに送信して他の端末にメッセージを送りましょう。Parse.comにはCloudCodeという仕組みがあって、サーバーサイドのロジックやデータ保存時のバリデーション等を行えます。ここでは、メッセージが保存された後そのメッセージをPusherに送信するようにしてみます。

```js
var crypto = require('crypto');

var PUSHER_ID     = "YOUR_PUSHER_ID";
var PUSHER_KEY    = "YOUR_PUSHER_KEY";
var PUSHER_SECRET = "YOUR_PUSHER_SECRET";

var sign = function(str) {
    return crypto.createHmac('sha256', PUSHER_SECRET).update(str).digest('hex');
}

var push = function(body, success, error) {
    body = JSON.stringify(body);
    var method = "POST";
    var path = "/apps/" + PUSHER_ID + "/events";
    var query = "auth_key=" + PUSHER_KEY;
    query += "&auth_timestamp=" + parseInt(new Date().getTime() / 1000, 10);
    query += "&auth_version=1.0";
    query += "&body_md5=" + crypto.createHash('md5').update(body, 'utf8').digest('hex');
    query += "&auth_signature=" + sign([method, path, query].join('\n'));
    Parse.Cloud.httpRequest({
        method: method,
        url: "http://api.pusherapp.com" + path + "?" + query,
        headers: {
            'Content-Type': 'application/json'
        },
        body: body,
        success: success,
        error: error
    });
}

Parse.Cloud.afterSave("Message", function(req) {
    push({
        name    : "talk",
        data    : JSON.stringify({"text":req.object.get("text")}),
        channel : "talk_room"
    }, function(httpResponse) {
        console.log(httpResponse.text);
    }, function(httpResponse) {
        console.error(httpResponse.status + ': ' + httpResponse.text);
    });
});
```

`push`ではPusher用の認証情報を組み立ててCloudCodeのHTTPリクエストAPIで送信しています。`afterSave`は指定したクラスのデータが保存される度に呼ばれるコードで、ここではMessageの保存時にその内容をPusherに送信しています。

## 組み合わせる
最後に、ここまで紹介した要素を組み合わせてチャットゲームを完成させましょう。UIの部分を除いた送受信の処理は以下のようになります。

```lua
local json = require("json")

function init()
    local ws = cc.WebSocket:create("ws://ws.pusherapp.com/app/{YOUR_APP_KEY}?protocol=7")
    ws:registerScriptHandler(function(msg)
        msg = json.decode(msg)
        if msg.event == "pusher:connection_established" then
            ws:sendString(json.encode({
                event = "pusher:subscribe",
                data = { channel = "teen_room" }
            }))
        elseif msg.event == "talk"
            local data = json.decode(msg.data) -- ※dataはさらにjsonエンコードされているのでデコードしています
            showMessage(data.text)
        end
    end, cc.WEBSOCKET_MESSAGE)
end

function send()
    local xhr = cc.XMLHttpRequest:new()
    xhr.responseType = cc.XMLHTTPREQUEST_RESPONSE_JSON
    xhr:setRequestHeader("X-Parse-Application-Id", "YOUR_APP_ID")
    xhr:setRequestHeader("X-Parse-REST-API-Key", "YOUR_API_KEY")
    xhr:setRequestHeader("Content-Type", "application/json")
    xhr:open("POST", "https://api.parse.com/1/classes/Message)
    xhr:registerScriptHandler(function()
        if xhr.readyState == 4 and (xhr.status >= 200 and xhr.status < 207) then
            print(xhr.response)
        else
            print("xhr.readyState is:", xhr.readyState, "xhr.status is: ",xhr.status)
        end
    end)
    xhr:send(json.encode({text = getInputText()}))
end
```

![bg_jan.png](https://qiita-image-store.s3.amazonaws.com/0/2490/1c34f11c-1b68-b418-48cb-084685d4baff.png)
*[臼井の会](http://usui.moo.jp/frame2.html)さんの素材を利用させてもらっています*

## まとめ
cocosで作ったゲームにリアルタイム通信機能を実装する方法について書きました。記事の大半が各mBaaSの機能の紹介になってしまいcocosのアドベントカレンダーとしてどうなんだろう、という反省の気持ちもややありますが、クライアントサイドでのエンジニアリングに注力したいcocosだからこそサーバーサイドを手軽に実装できる選択肢を持っておくことが重要なのではと思いこのテーマで書きました。

明日はmettoboshiさんによるAppwarp / Cubism SDKの話です。割と近い話になるんじゃないかと楽しみにしています:)