[測域センサとUnity] インタラクティブコンテンツ(ゲーム)の作り方！

## 前書き
この記事はゲームの最先端とも言えるインタラクティブコンテンツの作り方となります。
本記事では測域センサとUnityの繋ぎ方、検出した位置にオブジェクトを生成するまでの方法を説明していきます。


## どのようなコンテンツを制作したか
実際に足を使ってホッケーができるインタラクティブコンテンツを制作しました。
システムの開発は私を含めた4人の学生で行いました。

![無題の動画-‐-Clipchampで作成-_3_.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553823/2bcb70d5-5dfb-c789-b983-a027bf084476.gif)


## 使用した機材
- 北陽 測域センサ UST-20LX
- 超短焦点プロジェクター IP-AW400W
- PC
- ヒートシンク
- 六法全書(センサを床から離すのに使用)


## １．GithubからサンプルのUnityを入手

https://github.com/curiosity-inc/urg-unity

まずはコード一式をダウンロードして解凍してあげましょう。
展開先は適当で大丈夫です。

![スクリーンショット 2023-12-21 181322.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553823/76a73cf7-763d-30d8-81bf-a6786d2d550f.png)


## ２．example.unityを開く
以下のパスに沿ってフォルダを開いてください

パス：**urg-unity-master\Assets\Example**

中に「**example.unity**」というファイルがあるのでダブルクリックで起動してください。
開くunityはなるべく元のものに近いバージョンのものを選択してあげてください。

![スクリーンショット 2023-12-30 231835.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553823/9b4a018e-c452-5eb8-8981-d98d65c926de.png)

このように表示されても、続行で問題ありません。
起動が完了すると下記の様になります。

![スクリーンショット 2023-12-30 232425.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553823/2b11930a-faec-c028-7e30-3e5b69e37cf3.png)

エラーなく起動できていれば問題ありません。
ここでエラーが発生する場合、バージョン差による問題が発生している可能性があるのでバージョンを変えて開くことをお勧めします。

## ３．実行
**PCと測域センサを事前に接続しておいてください。**

※初めて接続する場合、URG Benriというソフトを用いて正常に接続できるか確認をお願いします。ここでIPアドレスなどがデフォルトではない場合、unityでの接続にはIPアドレスを変更させる必要も出てきます。

https://www.hokuyo-aut.co.jp/products/data.php?id=7

PCとセンサの接続が完了次第、実行ボタンを押下しましょう。

![スクリーンショット 2023-12-30 233505.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553823/f8d100fe-b766-18be-95d7-44d393e1e1e8.png)

実行し、シーンを見ると**青い波形とキューブのオブジェクト**が生成されるはずです。
青い波形が生成されていれば、キューブが生成されていなくても問題はありません。

これでひとまず、Unityと測域センサの接続は完了しました！お疲れ様でした！

## ４．センサーの位置を特定する
まずはセンサーの位置を特定します。
仕様上、「UST-10LX」というオブジェクトの位置がセンサーの位置という訳ではありませんでした。
青い波形の中心なのに？とお思いでしょうが、違う様です。私も謎です。。
こればかりは人力で探すしかないようですね。以下、私が実際に行った手法です。


動画の様に手をセンサーに近づけたり、離したりしてみてください。
![bandicam-2023-12-31-00-01-14-305.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553823/e1b3b3d7-79dc-5863-673f-bfbb31a25c8f.gif)

手の動きと同じ動きをしているオブジェクトを見つけ、測域センサの位置を特定します。
※センサーに近づきすぎると消えてしまうので、適度な距離で確認してみてください。

![無題の動画-‐-Clipchampで作成.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553823/5f25cc43-f979-db12-2cb8-30ba655eb52a.gif)

下記の様におおよその位置に分かりやすいようオブジェクト(赤色)を設置しました。
このオブジェをprefab化させてゲームを終了させても記録が残るようにしてみます。

![スクリーンショット 2023-12-31 001026.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553823/5e2a7dac-c2dd-20eb-25d8-3899a7696bcc.png)

## ５．検出範囲の設定
DebugRendererオブジェクトにアタッチされている**DebugRenderer.cs**を開いてください。

```c#:DebugRenderer.cs 38行目～42行目
var sensorCorners = new Vector2[4];
sensorCorners[0] = new Vector2(1.5f, 1f);
sensorCorners[1] = new Vector2(1.5f, -1f);
sensorCorners[2] = new Vector2(0.2f, -1f);
sensorCorners[3] = new Vector2(0.2f, 1f);
```

ここでセンサーが検知する最大の範囲を設定しているようです。
解説が難しいので、コメントを振ってみました。
```c#:DebugRenderer.cs 38行目～42行目
//  【センサーが読み込む位置の指定】
// 　足の位置が合わない場合、ここの値をいじるとなぜか上手くいく :)
/*
            
    ---------------------------------
    |2                          　 1|
    |                               |
    |                               |
    |                               |
    |                               |
    |                               |
    |                               |
    |3                             4|
    ---------------------------------
                    ■
           　　 　センサー
*/
var sensorCorners = new Vector2[4];
// 1. 右上の座標　　  センサーから見てまっすぐ2.5f、2.5f行ったところから2.3f横の位置
sensorCorners[0] = new Vector2(2.5f, 2.3f);
// 2. 左上の座標　　　センサーから見てまっすぐ2.5f、2.5f行ったところから-2.3f横の位置
sensorCorners[1] = new Vector2(2.5f, -2.3f);
// 3. 左下の座標　　　センサーから見てまっすぐ0.2f、0.2f行ったところから-2.3f横の位置
sensorCorners[2] = new Vector2(0.2f, -2.3f);
// 4. 右下の座標　　　センサーから見てまっすぐ0.2f、0.2f行ったところから2.3f横の位置
sensorCorners[3] = new Vector2(0.2f, 2.3f);
```

コメントにある通り、センサーを中心としてそこから端の位置を決めているようです。
Vector2の各値についてはプロジェクターを実際に投影しながら、微調整を行うしかありませんので頑張ってください。

現実で検出された位置とUnity上で生成されるオブジェクトの位置がずれる場合、上記の各Vectorの値を調整することで解決します

地道に調整するしかありません。こればかりは大変ですが、本当に頑張ってください。
私は2時間以上かかりました。


## ６．検出の精度を調整する



```c#:DebugRenderer.cs 94行目～97行目
 if (clusterIndices[i].Count < 20)
{
    continue;
}
```

このif文では下記のような処理の内容となっています。かなり重要です。

まず前提として測域センサーを中心に赤外線が多方面に照射されます。
この赤外線は細い線が、細かな角度ごとにいくつもあるという認識で、物に赤外線があたると線の数が分かるようになっているようです。

「clusterIndices[i].Count」には物の大きさを表す線の数が含まれており、if文の条件の数値「20」を大きくすると大きな物にしか反応しなくなり、数値を小さくすると小さい物から大きい物まで検出するようになります。

「それなら「1」くらいでいいんじゃないか」と思うでしょうが、誤検出が発生しやすくなるので極端に値を小さくするのはオススメしません。

私的にこの値は「3」に設定すると丁度良くなりました。
センサーから離れれば離れるほど小さな物の検出が難しくなるようです。
遠くに離れると線の数が少なくなってしまうので要調整が必要です。


## ７．オブジェクトを生成する数を調整する



```c#:DebugRenderer.cs 52行目～58行目
for (var i = 0; i < 100; i++)
{
    var obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
    obj.transform.parent = transform;
    obj.transform.localScale = 0.3f * Vector3.one;
    debugObjects.Add(obj);
}
```

ここで注目する値はfor文内の「100」という数値です。
この値はオブジェクトを生成する最大の数で「100」と設定されている場合、100個の物まで検知できるといった認識です。基本的にプロジェクトでは1対1のゲームを想定していたため足(物)4本分があれば問題ありません。
なのでfor文内の数値を「4」と設定しても良いのですが、誤検出が起きた際に誤検出側に生成したオブジェクトを占有されてしまい、足の位置にオブジェクトが表示されないことがありました。
そのため、必要な数にプラス10～20くらいの値を指定すれば問題ないかと思います。



## ８．生成するオブジェクトを変更する

```c#:DebugRenderer.cs 52行目～58行目
for (var i = 0; i < 100; i++)
{
    var obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
    obj.transform.parent = transform;
    obj.transform.localScale = 0.3f * Vector3.one;
    debugObjects.Add(obj);
}
```


生成数の調整と同様に52～58行目です。
各コードの処理内容が分かりやすいようコメントを振りました。


```c#:
for (var i = 0; i < 100; i++)
{
    // 検知した場所に生成するオブジェクト（キューブ）
    var obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
    obj.transform.parent = transform;
    // 生成するオブジェクトのサイズを指定
    obj.transform.localScale = new Vector3(1f, 1f, 1f);
    // 生成するオブジェクトを非表示に設定
    //obj.GetComponent<MeshRenderer>().enabled = false;
    // 追加
    debugObjects.Add(obj);
}
```

下記のコードを追加しました。
コメントアウトを外すと、生成されるオブジェクトが非表示になります。
ビルド時には非表示にする必要があるので、コメントアウトを解除してあげましょう。
```c#:
// 生成するオブジェクトを非表示に設定
//obj.GetComponent<MeshRenderer>().enabled = false;
```
### オブジェクトを変更
それでは生成するオブジェクトを変更する方法ですが下記のコードを編集します。

```c#:
// 検知した場所に生成するオブジェクト
var obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
```
例えばキューブから円筒に変更したい場合は以下の様にします。
```c#:
// 検知した場所に生成するオブジェクト
var obj = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
```
### オブジェクトのサイズを変更
生成するオブジェクトのサイズを変更する場合、下記の値を変更すると変わります。
```c#:
// 生成するオブジェクトのサイズを指定
obj.transform.localScale = new Vector3(1f, 1f, 1f);
```


## ９．再度実行
期待通りに現実の位置とUnity上のオブジェクトの位置が合わせることができましたか？
こちらはメイン開発を担当した2人が実際に動き回り、測域センサが足の位置を取得しUnityにオブジェクトを生成してる様子です。リアリティを追求するには本当に微調整が大切と学びました。
ここまで一緒に乗り越えた友人に感謝です。

![ezgif-4-a4e9b66af1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553823/d96706eb-4fb9-d989-81ea-57f6890c7e08.gif)


## トラブルシューティング
### transform.position assign attempt for 'Cube' is not valid. Input position is { NaN, NaN, NaN }.

**解決策：メインカメラの角度をx:90に設定する**

このエラーが出力される場合、カメラの角度が関係しています。
元のコードを辿るとカメラが映している範囲を取得していました。
カメラの角度をデフォルトの状態からずらしてしまうと、エラーが発生します。
関係しているのは「X」のみで「Y」「Z」は値を変更しても問題ありませんでした。

### URG Benriにすら繋がらない！

**解決策：下記のリンク先に対策をまとめたので確認してみてください**

https://qiita.com/MOSO1409/items/bfe4d3baecffde94a172


### センサーの検出位置とオブジェクトの生成位置が異なる

**解決策：DebugRenderer.csを編集する**

本記事の「[５.検出範囲の設定](#５検出範囲の設定)」内で説明しているVector2の値を調整する必要があります。
ここを減らしたり増やしたりすることで、必ず合います!

また測域センサは正しい方向や設置位置にありますか？
センサがずれているとコードを調整したところで確実にズレが生じます。、しっかりとセンサを固定してあげましょう。


### 測域センサに設定しているIPアドレス、ポート番号がデフォルトと異なる場合

![スクリーンショット 2023-12-31 112419.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553823/b098c74f-dbaa-b43f-0674-fbe060751960.png)

**解決策：UST-10LXオブジェクトにアタッチされているEthernet TransportのIP Addressとポートの値を変更してあげてください**


### センサーが熱を持ってしまう

**解決策：センサーにヒートシンクを貼る**

https://www.amazon.co.jp/%E3%83%92%E3%83%BC%E3%83%88%E3%82%B7%E3%83%B3%E3%82%AF-%E7%86%B1%E4%BC%9D%E5%B0%8E%E6%80%A7%E4%B8%A1%E9%9D%A2%E3%83%86%E3%83%BC%E3%83%97%E4%BB%98%E3%81%8D-%E3%82%A2%E3%83%AB%E3%83%9F%E3%83%8B%E3%82%A6%E3%83%A0%E8%A3%BD-M2-SSD-%E3%83%8F%E3%82%A4%E3%83%91%E3%83%AF%E3%83%BCLED%E3%82%A2%E3%83%B3%E3%83%97%E3%81%AB%E9%81%A9%E7%94%A8/dp/B07HFRBY9K/ref=sr_1_43?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=1ZHJFVAQIRFWL&keywords=%E3%83%92%E3%83%BC%E3%83%88%E3%82%B7%E3%83%B3%E3%82%AF&qid=1703948224&sprefix=%E3%83%92%E3%83%BC%E3%83%88%E3%82%B7%E3%83%B3%E3%82%AF%2Caps%2C209&sr=8-43

私は上記のリンク先のヒートシンクを購入しました。
センサーの側面にヒートシンクを貼ることで、かなり放熱されました。

気温が熱い環境や近くにプロジェクターがあり熱風を浴びる環境などでは、ファンでヒートシンクを冷やす必要もあります。

### 誤検出が多い！

**解決策：明るい場所を避ける・センサーを壁、床などから少し離す**

まず蛍光灯をつけると誤検出が増える傾向にありました。
私の場合、センサーを床に設置していましたがセンサを床から離す(高くする)と誤検出が減りました。
丁度良い高さは六法全書くらいの高さでした。多分10～15cmくらいです。

### 検出範囲とゲームのステージの範囲が合わない
例えばゲームのステージを既に作成していて、検出によって生成されたオブジェクトの移動量や移動範囲と合わない。しかもそのUST-10LXオブジェクトを編集しても移動量や移動範囲は変わらず困っているのではないでしょうか。

**解決策：ゲームのステージ等の全てのオブジェクトのサイズを「検出によって生成されるオブジェクトのサイズ感」に合わせるしかない**

私も頑張って解決策を探しましたが、上記の解決策しか方法はありませんでした。
他にあれば教えてください。



## まとめ
本当に未知の領域で分からないことばかりだったので、もしかすると間違えている点もあるかもしれません。
もし誤りがあればコメントで指摘をいただけますと大変助かります。
これからインタラクティブコンテンツを作成する皆様の力になれたようであれば、記事を書いた甲斐があります。
もしよろしければコメントやいいねなど反応を頂けると嬉しいです。

また分からないことがあればコメントください。