toio と Unity の連携

#はじめに
これは「[toio™(ロボットトイ | toio（トイオ）) Advent Calendar 2019](https://qiita.com/advent-calendar/2019/toiotomo)」の1日目の記事になります。

この記事ではUnityとtoioの連携方法を記します。
以下の記事・サイトを参考にしました。

- [UnityからNode.jsサーバにWebSocketを使ってデータ送信 *](https://qiita.com/nmxi/items/d3a0e787ca67e27ffd17)
- [toio/toio.js - GitHub](https://github.com/toio/toio.js)

#toio.js とは
toio.js は Node.js を用いたtoio公式のライブラリです。 
パソコン側からcoreCubeを動かしたり、マット上の位置を取得したり出来ます。
今回はこれを使ってマット上のcoreCubeにUnity上のCubeを連動させたいと思います。

#環境
OS : MacOS High Sierra 10.13.6

#下準備
##Unity
###Unity Hub のインストール
Unity Hub を入れます。
[こちら](https://unity3d.com/jp/get-unity/update?_ga=2.57807842.913444004.1574745690-504212690.1547626327)の**UnityHubをダウンロード**からインストーラをダウンロードできます。

unity hubを起動してインストールで最新バージョンのUnityを入れます。
このとき、Visual Studioも一緒にインストールします。
<img width="1145" alt="スクリーンショット 2019-11-26 15.04.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145574/9b318f2a-20d9-c190-97bb-899f9a7f1994.png">


###UniRx
Unity Hub で新規プロジェクトを作成します。
<img width="1146" alt="スクリーンショット 2019-11-26 14.48.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145574/55e6ea7f-1e42-b46c-2d5e-b208fd79a5a0.png">

プロジェクトが開いたらAssetストアで **UniRx** をダウンロード、インポートします。
すると、Assetsフォルダ下にPluginsフォルダができ、その中にUniRxが入ります。
<img width="1440" alt="スクリーンショット 2019-11-26 15.31.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145574/011bbed3-9871-a3fb-4bd2-efd558c93829.png">

###C# websocketライブラリ

[こちら](https://github.com/sta/websocket-sharp)からライブラリをダウンロードして解凍します。
<img width="621" alt="スクリーンショット 2019-11-28 21.31.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145574/fb2b0cdb-3ce8-063f-6d91-6ad155311819.png">


中にある　websocket-sharp.csproj をVisualStudioで開きビルドすると bin/Debug 下に websocket-sharp.dll が出来ます。
<img width="625" alt="スクリーンショット 2019-11-28 21.30.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145574/04a4123e-e00d-9cbc-4a06-1ed28c70576d.png">


この websocket-sharp.dll を先ほど作成したプロジェクトの Assets/Plugins に入れます。
<img width="382" alt="スクリーンショット 2019-11-26 15.51.16のコピー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145574/f46b2675-a1be-1f14-4c12-70046b3f2106.png">


これでUnity側の下準備は完了です。

##toio.js
###brew install yarn

yarn に関しては公式では npm で入れていますが、

```
npm install -g yarn
```

私は以下のように brew を使いました。

```
brew install yarn 
```

権限が必要だったり入れ難かった覚えがありましたが、なんかと入れたようです。
良くないことをしたかも知れません。

###toio.js

次に toio の github から toio.js を clone します。
公式曰く、以下のコマンドでパッケージのbuildまで済みます。
yarn example:hogehoge でサンプルプログラムが動きます。

```
git clone https://github.com/toio/toio.js.git
cd toio.js
yarn install
yarn build
yarn example:keyboard-control
```

coreCubeの電源を入れて接続され方向キーで動いたらオッケーです。
僕は接続されなかったのでパソコンを再起動したら動きました。

#実装
##Unity側

SceneにCreateから3D Object/Cubeを置きます。
またCreateからUI/Buttonを置いておきます。

GameViewでみるとこんな感じになると思います。
<img width="1031" alt="スクリーンショット 2019-11-28 21.10.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145574/59f68f69-e37d-5bfa-c2a1-d228139dfd09.png">


Cube に Add Component/NewScript で**PositionSync**というスクリプトをつけます。
以下、@nmxi さんの記事*から拝借したコードを改変して書いた中身です。


```PositionSync.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using WebSocketSharp;
using UniRx;

public class PositionSync : MonoBehaviour
{

    [SerializeField] private string _serverAddress;
    [SerializeField] private int _port;

    [SerializeField] private SyncPhase _nowPhase;

    private GameObject tA;
    private WebSocket ws;
    string message = "";
    string messageA = "";

    public float span = 0.5f;

    public enum SyncPhase
    {
        Idling,
        Syncing
    }

    private void Awake()
    {
        tA = GameObject.Find("Cube");
        _nowPhase = SyncPhase.Idling;

    }

    void Update()
    {
        string[] m = message.Split(',');

        switch (m[0])
        {
            case "A":

                if (message != messageA)
                {
                    ////サイズの変更
                    //マット
                    //559mm 410points 
                    //toioコアキューブ
                    //31.8mm about 23.3point

                    tA.transform.position = new Vector3(-(float.Parse(m[2]) - 250.0f) / 10.0f, tA.transform.position.y, -(float.Parse(m[1]) - 250.0f) / 10.0f);
                    tA.transform.rotation = Quaternion.Euler(0, float.Parse(m[3]), 0);
                }
                messageA = message;
                //Debug.Log(m);
                break;
        }


    }
    /// <summary>
    /// Get Down Start Sync Button
    /// </summary>
    public void OnSyncStartButtonDown()
    {
        var ca = "ws://" + _serverAddress + ":" + _port.ToString();
        Debug.Log("Connect to " + ca);
        ws = new WebSocket(ca);

        //Add Events
        //On catch message event
        ws.OnMessage += (object sender, MessageEventArgs e) => {
            message = e.Data;

        };

        //On error event
        ws.OnError += (sender, e) => {
            Debug.Log("WebSocket Error Message: " + e.Message);
            _nowPhase = SyncPhase.Idling;
        };

        //On WebSocket close event
        ws.OnClose += (sender, e) => {
            Debug.Log("Disconnected Server");
        };

        ws.Connect();

        _nowPhase = SyncPhase.Syncing;
    }

    public void OnChangedTargetTransformValue(Vector3 pos)
    {
        if (_nowPhase == SyncPhase.Syncing)
        {
            //ws.Send(pos.ToString());
            //ws.Send("1");
        }
    }
}
```

Cube にある PositionSync (Script)の欄に
Server Address には "localhost"、Port には "8080"を入れて置きます。
<img width="408" alt="スクリーンショット 2019-11-28 21.05.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145574/5fae4755-1cde-5e46-1532-076371dcea2f.png">


先ほど置いたButtonのOn Click()に Cube/PositionSync/OnSyncStartButtonDown() を追加します。
<img width="407" alt="スクリーンショット 2019-11-28 21.12.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145574/792ccb08-1206-663f-6fe6-06ae0d3d7993.png">



##toio.js側

toio.js/projectsフォルダを作成し、その中にスクリプトを置きます。
今回はtoio公式のサンプルと@nmxi さんの記事*から拝借したコードを悪魔合体して以下のスクリプトを生成しました。

マット上におけるcoreCubeの絶対位置をUnityに送るプログラムです。

```toio_node.js

const { NearestScanner } = require('@toio/scanner')

var WebSocketServer = require('ws').Server

var wss = new WebSocketServer({
    port: 8080
});

var cube;

async function main() {
  // start a scanner to find nearest cube
  cube = await new NearestScanner().start()

  // connect to the cube
  await cube.connect()
}

main()

wss.on('connection', function(ws) {

    //position-idに変化があった時
    //移動、回転
    //toioPositionを更新する
    cube.on('id:position-id', data => {
        ws.send("A,"+data.x+","+data.y+","+data.angle)

    })
    //マットから離れた時
    cube.on('id:position-id-missed', () => {
      // ws.send('Position Miss!');
    })
});

```

#実行
1.coreCube の電源を入れます。

2.toio_node.js を実行します。(少しして coreCube から音がします。)

 ```
 cd projects
 node toio_node.js
 ```

3.Unity のプロジェクトを Run します。

4.GameView 上で Button をクリックします。

この状態でマットに置いた coreCube の位置が Unity の Cube に反映されれば成功です。

こんな感じ
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">[qiita用] マット上でのtoioCoreCubeの移動・回転がunityのSceneに反映されている <a href="https://t.co/QJloUHmCeR">pic.twitter.com/QJloUHmCeR</a></p>&mdash; ito.ur.right (@ito_ur_right) <a href="https://twitter.com/ito_ur_right/status/1200026898484477952?ref_src=twsrc%5Etfw">November 28, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>