[Unity]WebSocketでリアルタイム処理を作る為の備忘録

Unityで画面処理やキャラクター操作ができるようになってくると、他のユーザーとリアルタイムに連携したバトルとかが作りたくなる。
これを実現するためにリアルタイム通信の入門ステップとしてWebSocketを利用してブラウザからコマンドを入力してWebSocketサーバに接続しているクライアントのゲームキャラクターを動かすだけのシステムを作成してみた。


**サーバサイド**
今回はMicrosoft.WebSocketを利用して、.Net MVCでサーバ側アプリケーションを組みました。

Viewはこんな感じに作って「Connect」ボタンでサーバとの接続を確立後、「Up」「Down」「Left」「Right」でキャラクターを操作できるようにする。
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/108650/67356eca-d1fb-336a-d649-048382a852e4.png)
Viewのソース

```cshtml:Index.cshtml

<button id="connectBtn">Connect</button><br />
<button id="upBtn">Up</button><button id="downBtn">Down</button><button id="leftBtn">Left</button><button id="rightBtn">Right</button><br/>
Log: <pre id="log" style="width:100%; height:260px;"></pre>
<button id="CloseBtn">Close</button><br />


@section scripts {
    <script type="text/javascript">
        var socket;

        $(function () {
            $('#connectBtn').click(function () {
                if (socket == null) {
                    try {
                        socket = new WebSocket('ws://' + location.hostname + ':' + location.port + '/wsserver');
                        socket.onopen = function (event) { log('接続'); };
                        socket.onclose = function onClose(event) { log('切断'); };
                        socket.onerror = function onError(event) { log('エラーです'); };
                        socket.onmessage = function onMessage(event) { log(event.data); };
                    } catch (e) { alert(e.message); }
                }
            });

            $('#sendBtn').click(function () {
                var obj = new Object();
                obj.message = $('#message').val();

                var jsonString = JSON.stringify(obj);
                socket.send(jsonString);
                log('send: ' + jsonString);
                $('#message').val('');
                return false;
            });

            $('#CloseBtn').click(function () {
                socket.close();
                socket.onclose();
                socket = null;
            });

            $('#upBtn').click(function () {
                var obj = new Object();
                obj.direction = 'up';
                var jsonString = JSON.stringify(obj);
                socket.send(jsonString);
            });
            $('#downBtn').click(function () {
                var obj = new Object();
                obj.direction = 'down';
                var jsonString = JSON.stringify(obj);
                socket.send(jsonString);
            });
            $('#leftBtn').click(function () {
                var obj = new Object();
                obj.direction = 'left';
                var jsonString = JSON.stringify(obj);
                socket.send(jsonString);
            });
            $('#rightBtn').click(function () {
                var obj = new Object();
                obj.direction = 'right';
                var jsonString = JSON.stringify(obj);
                socket.send(jsonString);
            });
        });

        function log(msg) {
            var txt = $('#log').text();
            txt = msg + '\n' + txt;
            $('#log').text(txt);
        }
    </script>
```
サーバアプリケーション側のソースはWebSocketでググればでてくるので省略。
参考サイト：[ASP.NET MVC で WebSocket サーバを作る ](http://architect-wat.hatenablog.jp/entry/20131113/1384328577)


**クライアントサイド**
Unity5.3を利用しています。

ブラウザから入力した情報のJson受け取り用のModel。Unity5.3だと.Net3.5までしか対応していないので、Newtonsoft.Jsonとか使えない。UnityからJsonUtilityが提供されているのでこれを使う。

```csharp:OthersInputModel.cs
[Serializable]
    public class OthersInputModel
    {
        public string direction;
    }
```

適当なGameObjectを追加し、GameObjectにScriptを追加。今回はCharacterManager.csとします。
WebSocketに必要なライブラリは[こちら](https://github.com/sta/websocket-sharp)から入手し、websocket-sharpをビルドしてAssets/Pluginにインポートしました。

```csharp:CharacterManager.cs
public class CharacterManager : MonoBehaviour {

    private WebSocket Socket;
    private Queue<string> MessageQueue;
    void Start () {
        Connect();
	}
	void Awake()
    {
        MessageQueue = new Queue<string>();
        Socket = new WebSocket("ws://localhost:61018/wsserver");
    }

	// Update is called once per frame
	void Update () {
        if (MessageQueue.Count > 0)
        {
            string direction = MessageQueue.Dequeue();

            if (direction == "up")
            {
                Up();
            }
            else if (direction == "down")
            {
                Down();
            }
            else if (direction == "right")
            {
                Right();
            }
            else if(direction == "left")
            {
                Left();
            }
        }
    }

    public void Connect()
    {
        Socket.OnMessage += (s, e) =>
        {
            var data = JsonUtility.FromJson<OthersInputModel>(e.Data);

            MessageQueue.Enqueue(data.direction);
                        
        };
        Socket.Connect();
    }

    public void Up()
    {
        var pos = this.gameObject.transform.position;
        pos.x = this.gameObject.transform.position.x;
        pos.y = this.gameObject.transform.position.y + 0.2f;
        this.gameObject.transform.position = pos;
    }

    public void Down()
    {
        var pos = this.gameObject.transform.position;
        pos.x = this.gameObject.transform.position.x;
        pos.y = this.gameObject.transform.position.y - 0.2f;
        this.gameObject.transform.position = pos;
    }

    public void Left()
    {
        var pos = this.gameObject.transform.position;
        pos.x = this.gameObject.transform.position.x - 0.2f;
        pos.y = this.gameObject.transform.position.y;
        this.gameObject.transform.position = pos;
    }

    public void Right()
    {
        var pos = this.gameObject.transform.position;
        pos.x = this.gameObject.transform.position.x + 0.2f;
        pos.y = this.gameObject.transform.position.y;
        this.gameObject.transform.position = pos;
    }
}
```


Socket.OnMessageで直接Up()とか呼びたかったのですが、OnMessageでの処理はMainスレッドでの処理ではないため、Queueに入力された情報を突っ込み、UpdateでQueueの情報を引っ張りだしてキャラクターを操作するようにしました。


**結果**
これらを実装した状態でブラウザから方向ボタンを押すとUnity側のキャラクターが押したボタンの方向に動きました。

**感想**
今回はブラウザからの操作でしたが、操作がゲームに無事反映されてくれたので、今後はルーム設定やどのキャラクターが操作されたのかをまとめることでよりゲーム性の高い処理を実装できたらいいなと思いました。

以上。