milkcocoaはUnityと連携できるか！？

[milkcocoa Advent Calendar 2014](http://qiita.com/advent-calendar/2014/mlkcca) 12月18日の内容です。
## 概要
手軽にリアルタイム通信が出来る [milkcocoa](https://mlkcca.com/) 、使い勝手が最高にクールです。
今のところHTML, node.js のライブラリが公式対応されています。

さらにIOS版、Android 版が現在開発中、2015年の5月に正式リリース予定ということです。

もし、隙間を埋めるべく、Unityでmilkcocoaが動かせると、
プラットフォームを容易に飛び越えることが出来るリアルタイム通信に、
リッチなユーザー体験をマッシュアップでき、可能性が大きく広がりそうです。

そこで、今回は、原理確認的な実装で、milkcocoaのUnity対応が可能かどうか検証し、
その知見と今後の課題を共有します。

---

### nilkcocoaの通信手法 socket.io について
Unityはnode.jsや生のJavaScriptを組み込むことが出来ないので<a title="UnityScript≠JavaScript ですし、この場合WebGLで展開するのは本末転倒ですので">※</a>、
milkcocoa のライブラリがしてくれていた通信処理を自力で実装しなければいけません。
~~node.jsライブラリの解読や、パケット解析により~~調査した結果、
milecocoa の通信はsocket.ioのバージョン1.0を用いて行われています。
ここで、socket.ioのバージョン毎の互換性の問題に注目しましょう。

**0.9→1.0** プロトコルに大きな変更がなされ、互換性はない
**1.0→1.1** UTF-8文字列の扱いに変更があり、バージョンが混ざると文字化けを起こす

> 執筆時点(2014年12月)では、 milecocoa は socket.io 1.0 のプロトコルで通信しています。

Unuty で socket.io のライブラリは、 [UnitySocketIO](https://github.com/NetEase/UnitySocketIO) や [websocket-sharp](https://github.com/sta/websocket-sharp) が有名ですが[[1]](#ref1)、対応している socket.io のバージョンが 0.9 以降更新が止まってしまっています。

そこで sockt.io 1.x 対応のUnityのライブラリを探してたのですが、ありました。

<blockquote class="twitter-tweet" lang="ja"><p>Socket.IO for Unity3D. Incredible work by <a href="https://twitter.com/fpanettieri">@fpanettieri</a> &#10;<a href="https://t.co/uITwAMCwY8">https://t.co/uITwAMCwY8</a>&#10;<a href="https://t.co/poHSX6a8GP">https://t.co/poHSX6a8GP</a>&#10;<a href="https://t.co/cOUBBMxzDa">https://t.co/cOUBBMxzDa</a></p>&mdash; Socket IO (@SocketIO) <a href="https://twitter.com/SocketIO/status/514928218537615360">2014, 9月 25</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

[Socket.IO for Unity3D](https://github.com/fpanettieri/unity-socket.io)を取っ掛かりにして、 milkcocoa のサーバと対話してみることにしました。

---

### milkcocoaサーバへの接続

JavaScriptの場合、ライブラリ内での接続先は、以下のように記述しますが、

```JavaScript
var milkcocoa = new MilkCocoa("https://io-YOUR_APP_ID.mlkcca.com/");
```

Socket.IO for Unity3D の場合、空のオブジェクトに SocketIO のプレハブを適用し、
InspectorウィンドウのオプションからURLを指定します。
ただし、そのまま ``https://io-YOUR_APP_ID.mlkcca.com/`` の記述ではサーバとwebsocketの通信を開始することが出来ません。
プロトコルに `wss`(WebSocket on SSLだと思う) を指定し、transport に websocketを指定しましょう。

```JavaScript
wss://io-YOUR_APP_ID.mlkcca.com/socket.io/?EIO=2&transport=websocket
```

![socketio_send_001.png](https://qiita-image-store.s3.amazonaws.com/0/35009/2cb0431d-2f9b-9826-5acb-d6115f122571.png)
---

次に、socket.ioで接続する部分のコードを作成します。
同じく空のオブジェクトを作成し、さらに、同じ名前のC#のスクリプトを作成します。

```c#
using System.Collections;
using UnityEngine;
using SocketIO;

public class milkcocoa : MonoBehaviour
{
    private SocketIOComponent socket;

    public void Start()
    {
        GameObject go = GameObject.Find("SocketIO");
        socket = go.GetComponent<SocketIOComponent>();

        socket.On("open", OnSocketOpen);
        socket.On("error", OnSocketError);
        socket.On("close", OnSocketClose);
    }

    public void OnSocketOpen(SocketIOEvent e)
    {
        Debug.Log("[SocketIO] Open received: " + e.name + " " + e.data);
    }

    public void OnSocketError(SocketIOEvent e)
    {
        Debug.Log("[SocketIO] Error received: " + e.name + " " + e.data);
    }

    public void OnSocketClose(SocketIOEvent e)
    {
        Debug.Log("[SocketIO] Close received: " + e.name + " " + e.data);
    }
}
```

以上で、milkcocoaのサーバに接続するだけのコードが出来ます。
websocketを開くことが出来れば、ログに表示されます。

![socketio_send_002.png](https://qiita-image-store.s3.amazonaws.com/0/35009/53af15c0-358c-0e1f-9cbf-b75735660e27.png)

---

### milkcocoaからの受信

次に、milkcocoaサーバからイベントを受け取るコードを書く必要があります。
今回は、原理確認でとどめますので、メッセージを console に吐き出すだけにしています。
手順は以下です。

1. 各種イベントのcallback関数を定義します。
2. milkcocoa の DataStore の path に対して、受信の意思を示すために、
  milkcocoaサーバに `on` を Emit します。

```C#
    public void Start()
    {
        GameObject go = GameObject.Find("SocketIO");
        socket = go.GetComponent<SocketIOComponent>();

        socket.On("open", OnSocketOpen);
        socket.On("error", OnSocketError);
        socket.On("close", OnSocketClose);
        socket.On("a", OnMcCallback);
        socket.On("b", OnMcPush);
        socket.On("c", OnMcSet);
        socket.On("d", OnMcRemove);
        socket.On("e", OnMcSend);

        StartCoroutine("AddEventEmitter");
    }

/*
 ～略～
*/

    public void OnMcCallback(SocketIOEvent e)
    {
        Debug.Log("[SocketIO] McCallback received: " + e.name + " " + e.data);
    }
    public void OnMcPush(SocketIOEvent e)
    {
        Debug.Log("[SocketIO] McPush received: " + e.name + " " + e.data);
    }
    public void OnMcSet(SocketIOEvent e)
    {
        Debug.Log("[SocketIO] McSet received: " + e.name + " " + e.data);
    }
    public void OnMcRemove(SocketIOEvent e)
    {
        Debug.Log("[SocketIO] McRemove received: " + e.name + " " + e.data);
    }
    public void OnMcSend(SocketIOEvent e)
    {
        Debug.Log("[SocketIO] McSend received: " + e.name + " " + e.data);
    }

    // milkcocoa イベント受信設定
    private IEnumerator AddEventEmitter()
    {
        // wait 1 second and continue
        yield return new WaitForSeconds(1);
        string path = "unitytest";
        AddDataStoreEvent("push", path);
        AddDataStoreEvent("set", path);
        AddDataStoreEvent("remove", path);
        AddDataStoreEvent("send", path);
    }

    private void AddDataStoreEvent(string eventname, string path)
    {
        // add listener [send] event
        JSONObject jsonobj = new JSONObject(JSONObject.Type.OBJECT);
        jsonobj.AddField("event", eventname);
        jsonobj.AddField("path", path);
        socket.Emit("on", jsonobj);
    }
```

尚、実際のプログラムで、特定のフィールドの値を取り出す場合は、
コールバック関数内で以下のように書きます。
例) `data.vakue.content` が取得したい場合

```C#
string content = e.data.GetField ("value").GetField ("content").Print ();
```

### イベントの送信

イベントをちゃんと受信できるか確認するために、
node.js からイベントを送信してみます。

```JavaScript
var MilkCocoa = require("./index.js");

var milkcocoa = new MilkCocoa("https://io-YOUR_APP_ID.mlkcca.com");
var ds = milkcocoa.dataStore("nodejs");
ds.on("send", function(r) {
	console.log(r);
});
ds.send({ text : "Hello!"});
```

実行すると、受信できていることが確認できました。

![miklcocoa_send_001.png](https://qiita-image-store.s3.amazonaws.com/0/35009/43392055-6f40-c128-0bd7-665739a8ce6c.png)

> 今回使用したコードを手直しして、[GitHubに公開しています](https://github.com/m2wasabi/unity-milkcocoa-primitive)。
milkcocoaアカウントを取得し、 APP_ID を設定すれば、動作可能です。

---

### まとめ

Unity でも自力実装すれば milkcocoa でリアルタイム通信可能であることが分かった

---

### 課題(既知の未解決事項)
+ マルチバイト(日本語)文字を送った時に文字化けする問題
+ Macで未検証である
+ ログインアカウント管理API対応
+ ライブラリ化して汎用性を持たせる

---

#### 参考  
<a name="ref1"></a>1) Unity から Node.js を裏でこっそり立ち上げてアレコレ出来るアセットをつくってみた - 凹みTips
http://tips.hecomi.com/entry/2014/04/21/001606