toio.js × Unityでプロジェクションマッピングなゲームをつくってみた

本記事は、[toio Advent Calendar 2020](https://qiita.com/advent-calendar/2020/toiotomo)の22日目の記事です。

# はじめに
<img width="600" alt="DioramaShooting_ADAA.png" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127320/4faff43c-1b84-0c43-451d-8eef992da11b.png>
toioとUnityを使った「Diorama Shooting」というゲームをつくりました。
これまで[MakerFaireTokyo2020](https://makezine.jp/event/mft2020/)で出展したり、[ヒーローズリーグ](https://heroes-league.net/)に応募したりしています。

本記事では本作品の開発経緯や使った手法をざっくり解説していきます。
（[ProtoPedia](https://protopedia.net/prototype/1956)にもいくらか情報を載せております。）

# 使用したもの
- Windows10
- [Bluethoothアダプタ](https://www.amazon.co.jp/dp/B07TJHK1RW/ref=cm_sw_r_tw_dp_KLB4FbVPB8K5Z?_x_encoding=UTF8&psc=1)
- プロジェクタ（Acer H6517ST）
- 3Dプリンタ（Qidi X-Maker）
- Unity 2019.4.5
- [toio.js](https://github.com/toio/toio.js/)
- Touch Designer
- Qidi Print
- Fusion 360

# 開発の背景と考え方
toioを用いてなにか面白いものをつくれないか、と考えた際、
大きな特徴としてセンサとしての側面、モータとしての側面を持ち合わせたデバイスであることを感じました。

例えば、大まかに分けると
1. 「[ピコトンズ](https://www.youtube.com/watch?v=RhKu5RO1U18&t=28s)」ではシートの絵柄を読み取って操作を行うセンサの用途
2. 「[クラフトファイター](https://www.youtube.com/watch?v=bpbxHcxvh0Q)」ではレゴを載せたロボットをぶつけ合うようなモータの用途
3. 「[ゲズンロイド](https://www.youtube.com/watch?v=kLll0hWn5sA)」ではお互いの位置を理解しあった生き物同士が、不思議な動きを行うセンサ/モータ両方の用途
があると言えます。（クラフトファイターも座標を使っているなど、厳密なものではありません）

できれば、3.のような両方を用いた使い方でtoio独自の表現をつくってみたい...！と。（ゲズンロイド超良いですよね）

一方、toioの視覚的な出力には物足りなさを感じました。
ラジコンとして動き回る見た目は面白いですが、表示はコンソールの小さな画面か、紙媒体の絵柄が中心です。
せっかく充実したセンサとモータがあるので、映像との連動を強固にするべくプロジェクタを用いることにしました。

最初は本当に置いた場所にエフェクトを出すだけの、追従させる挙動を実験するところから。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">キラキラ〜な文字を描けるくらいになったよ <a href="https://twitter.com/hashtag/toio?src=hash&amp;ref_src=twsrc%5Etfw">#toio</a> <a href="https://t.co/0l5J4eh4p4">pic.twitter.com/0l5J4eh4p4</a></p>&mdash; ぽし / 赤星俊平 (@xstartwi) <a href="https://twitter.com/xstartwi/status/1213787907393118209?ref_src=twsrc%5Etfw">January 5, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

次に、toioの動きと映像を連動する実験。他にも複数試していて、キリがなくなってきたのでゲームとして形にしました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">なんでもいいからとりあえずゲームっぽく試作 <a href="https://twitter.com/hashtag/toio?src=hash&amp;ref_src=twsrc%5Etfw">#toio</a> <a href="https://t.co/NduJaIBrV8">pic.twitter.com/NduJaIBrV8</a></p>&mdash; ぽし / 赤星俊平 (@xstartwi) <a href="https://twitter.com/xstartwi/status/1231950735421526017?ref_src=twsrc%5Etfw">February 24, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

映像に立体感というか、厚みがほしいと感じたので立体の投影対象を配置してみる実験。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">まだ全然プロトタイプなのに新作の動画つくった。<br> <a href="https://twitter.com/hashtag/toio?src=hash&amp;ref_src=twsrc%5Etfw">#toio</a> <a href="https://twitter.com/hashtag/Unity?src=hash&amp;ref_src=twsrc%5Etfw">#Unity</a> <a href="https://twitter.com/hashtag/gamedev?src=hash&amp;ref_src=twsrc%5Etfw">#gamedev</a> <a href="https://t.co/jkZWzEkKjf">pic.twitter.com/jkZWzEkKjf</a></p>&mdash; ぽし / 赤星俊平 (@xstartwi) <a href="https://twitter.com/xstartwi/status/1268887295052050432?ref_src=twsrc%5Etfw">June 5, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

最後に、連動するtoioの台数を増やして、映像と動きを両方制御ができる状態をつくって完成。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">10月3日(土)・4日(日)のMakerFaireTokyo、『Diorama Shooting』出展しするのでよろしくお願いします👍🎮<br> <a href="https://twitter.com/hashtag/MFTokyo2020?src=hash&amp;ref_src=twsrc%5Etfw">#MFTokyo2020</a> <a href="https://t.co/NDmBnEigm3">pic.twitter.com/NDmBnEigm3</a></p>&mdash; ぽし / 赤星俊平 (@xstartwi) <a href="https://twitter.com/xstartwi/status/1312020356090093569?ref_src=twsrc%5Etfw">October 2, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

toioは技術情報が公開されており、JavaScriptでほとんどの機能が扱える[toio.js](https://github.com/toio/toio.js/)が用意されています。
JSはあまり得意ではなかったことや、画づくりのやりやすさを考え、
Unity内でゲームをつくり、toio.jsの仲介でtoioを制御する手法をとりました。

# Unityとtoio.jsの同期
WebSocketを用いてJS(toio.js)に座標を送って連動しています。
「[toio for Unity](https://toio.io/blog/toio-sdk-for-unity/)」はまだリリースされていなかったことや、Editorからのtoio接続ができないため使用していません。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Unity座標をtoioに反映できたー！ toio→Unityと違って物理移動だからやや粗いけど <a href="https://twitter.com/hashtag/toio?src=hash&amp;ref_src=twsrc%5Etfw">#toio</a> <a href="https://t.co/OAZONhdYdl">pic.twitter.com/OAZONhdYdl</a></p>&mdash; ぽし / 赤星俊平 (@xstartwi) <a href="https://twitter.com/xstartwi/status/1216739792035074053?ref_src=twsrc%5Etfw">January 13, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

詳しくは[ito_ur_rightさんの記事](https://qiita.com/ito_ur_right/items/85062db988cfce14c4d7)にUnityとtoioのインストールから連動までがまとまっています。
こちらの記事ではtoio→Unityのセンサ読み取りですが、
Unity→toioのコントロールでモータを動かしたかったため少し改変しています。

以下、Unity側の同期部分のスクリプトです。最小構成に削ったもので、本来は5台分動くように書いています。
JS側はWebSocketで受け取った値に向けてMoveToで動かしています。
（プログラムは割となあなあでやっているので、変なところがあれば教えてください）

```C#:toioSync.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using WebSocketSharp;
using UniRx;

public class toioSync : MonoBehaviour
{
    [SerializeField] private string _serverAddress;
    [SerializeField] private int _port;
    [SerializeField] private SyncPhase _nowPhase;

    public GameObject toioUnityCube; //ここに格納したオブジェクトの位置・角度にtoioが向かう
    public Vector3 unityCubePos;
    public Vector3 unityCubeRot;
    public Vector3 tmpcube;

    private WebSocket wssent;
    public string message = "";
    string messageA = "";
    public string[] toJsMessage;

    //toioMatの大きさを格納 座標値は
    //https://toio.io/blog/detail/20200423-1.html
    //を参照
    //toio開発者用マットを横2×縦3で使用
    public int xMin = 34;
    public int xMax = 644;
    public int yMin = 35;
    public int yMax = 682;
    public float xCenter;
    public float zCenter;

    //Unity座標とtoio座標を変換するための数値
    //toio for Unityを参考に計算
    public float adjust = 411 / 0.560f;

    public enum SyncPhase //配列
    {
        Idling,
        Syncing
    }

    private void Awake()
    {
        _nowPhase = SyncPhase.Idling;
        xCenter = (xMin + xMax) / 2;
        zCenter = (yMin + yMax) / 2;
    }

    void Update()
    {
        toioSynctoJs();

        //Unityの各オブジェクトの座標、角度をtoio.js向けに変換し（100分の1した後、adjustを掛ける）、一時的なCubeに格納
        tmpcube = new Vector3(toioUnityCube.transform.position.x / 100, 0.0f, -toioUnityCube.transform.position.z / 100);
        unityCubePos = new Vector3(tmpcube.x * adjust + xCenter, 0.0f, tmpcube.z * adjust + zCenter);
        unityCubeRot = new Vector3(0.0f, toioUnityCube.transform.localEulerAngles.y, 0.0f);

        toJsMessage[0] = unityCubePos.x.ToString();
        toJsMessage[1] = unityCubePos.z.ToString();
        toJsMessage[2] = unityCubeRot.y.ToString();
    }

    //Unityとtoio,jsの同期処理
    public void toioSynctoJs()
    {
        var wssentadport = "ws://" + _serverAddress + ":" + _port.ToString();
        Debug.Log("Connect to " + wssentadport);

        wssent = new WebSocket(wssentadport);
        wssent.Connect();

        _nowPhase = SyncPhase.Syncing;
        wssent.Send(toJsMessage[0] + "," + toJsMessage[1] + "," + toJsMessage[2]);
    }
}
```

Unity→toio座標変換のためにかけているadjustの値は「[toio for Unity](https://toio.io/blog/toio-sdk-for-unity/)」のスクリプトを参照しています。
プロジェクションマッピングは座標合わせがシビアなので苦労していたのですが、良いリファレンスが登場して幸いでした。

ちなみに持ち合わせてるマシンスペックの都合でWindows10を使用していますが、Macの方がtoioとの相性がよさそうです。
そもそものtoio環境構築もMacより少しややこしいのですが、[KatsuShun89さんの記事](https://qiita.com/KatsuShun89/items/5f86a9811565267f6b30)が詳しいです。


# ゲームの作成
<img width="600" alt="Diorama_UnityMonitor.png" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127320/371b04ed-cc4e-3885-7edc-f0040d75a0f0.png>
本作ではゲームやほとんどの内容はUnity上を作成しています。今回は完成を目指すためシンプルなゲームシステムにしており、
動きを横方向に制限した簡単なシューティングゲームに仕上げました。
toioが自由に動けると敵やブロックの配置、動かし方などのデザインやプログラムが大変になってきます。
アーケードのガンシューティングや、縁日の射的なんかでユーザの位置を制限してしまう考えに近いと思います。
あと正面から見ることのみに想定しておけば、プロジェクションマッピングの画も破綻しにくいです。

普通のゲームづくりと違う点といえば、実空間で展開するため、サイズ感と物量が重要になってきます。
後々3Dプリンタ出力やプロジェクションを行うため、フィールドやオブジェクトが大きすぎるor多すぎると後が大変...！
実空間の都合に引っ張られてゲームがこじんまりすることは避けたいですが、調整しやすい方を寄せていくのも重要です。

<img width="600" alt="Diorama_UnityMonitor.png" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127320/24874358-da40-c509-a259-9278adbac6c4.png>
さらにプロジェクションマッピングとして成り立たせるために、フィールドとカメラ位置の関係を
実際にプロジェクタを置く位置にします。このあたりは細かいテクニックやコツが要るのですが、
[keijiroさんの記事](https://github.com/keijiro/unity-pm-tutorial/blob/master/README.md)に詳しく解説されており、大部分はこちらを参考に実装しています。


余談ですが、投影内容をつくってから投影対象をつくるというのは、建物などに合わせて画をつくるような
通常のプロジェクションマッピングとは逆(？)の発想なので、変則的な感じが何度か驚かれました。
これもデジタルなものを手軽にアナログなのものとして出せる時代ならではなので、更に探ってみたい部分です。

# 投影対象の出力
ゲーム内で使用したオブジェクトを3Dプリンタで出力します。
大抵はUnity上で使用している3Dモデルをそのままスライサー（Qidi Printer）に投げていますが、
fbxなど対応していない形式のものはstlに変換するなどしています。

<img width="600" alt="image.png" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127320/ddb52997-f3e0-b90b-03b5-197b5710b188.png>

Unity内で置いている大きさに合わせて、モデルの出力サイズをスケールで指定しています。
Unity内のサイズが何cmかわからない！という場合はCubeで無理やりざっくり測ったりしていました。。
0.55サイズのCubeと同じ高さだから5.5cm...など。
（多少ミスっても出力してしまった場合は、Unity側のスケールを大小して解決してます）

縦横比を変に変えるなどしない限り、スケールを合わせればゲーム内と現実の物体がピッタリ合います。

# 映像の投影
映像投影のため、Touch Designerを使います。
Syphon/Spoutで投げた映像を「kantannmapper」で変換しています。
<img width="600" alt="image.png" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127320/4e31c683-4daa-8a9e-20a5-e20d6c6683cf.png>
Syphon/Spoutについては [@_kiryo さんの記事](https://qiita.com/_kiryo/items/36fe7a7b2c50705fb914
)を、
kantanmapperについては [@aokey さんの記事](https://qiita.com/aokey/items/d6852c11fea25888c206)をご参照ください。

こうしたプロジェクションマッピングのための映像変換にはMadMapperなどの専門ソフトを用いる場合が多いですが、
簡単な変換であればこれで十分かと思います。なにより無料です！
（このあたりのTipsは意外と浸透していないため、詳しい手順も記載した別記事を立てたいところ...）

また、Unity内で変換して完結させる手法も考えられ、[凹みさんの記事](http://tips.hecomi.com/entry/2017/09/30/145734)で実装されています。
今回はexeで毎回ビルドしてフルスクリーンにするとか、変換後の状態を保存しておくとかが面倒なので、
Gameビューをそのまま投げちゃって調整できるようTouchを使用しています。（少し遅延の原因になっているかも...？）

# 今後の展望
現状、いろいろと拡張したいことはあるのですが、toioの多台数接続が目下の課題です。
当初の構想としてはtoioを大量に接続してキャラクタだけでなくゲーム全体を舞台装置のように動かす予定だったので、
実は結構縮小してゲームにまとめているのです...。（toioを10台くらい買ってから気づいてしまった...）
ちなみにWindows+ドングルだと僕の環境では5台が限界でした。（PCの性能などにもよるかと思われます）

[モリカトロンさんの記事](https://tech.morikatron.ai/entry/2020/04/23/150000)や[伊藤さんの記事](https://qiita.com/warapuri/items/feddfb17d54be1031937)で試行錯誤されている結果を見るに
iPhoneの方が相性が良さそうなので、なんとかiPadでできないかとか、複数台を使ってどうにかなど、
協力してくれているプログラマと相談しながら実験中なので、進捗があればまた改良していきます！

# おわりに
実世界の座標を正確にセンシングし移動することができるtoioはアナログ/デジタルを繋ぐ技術としてなかなか強力です。
Vive TrackerやKinectのようなセンサ類の選択肢の一つとして、まだまだ可能性がありそうです。

今回は構成と使ったものをざっと流す感じで細かいことは書いておりませんが、
詳しく知りたいことや実現したい応用があれば気軽にDMかなにかでご質問ください。