ATOM Mate for toio サンプル for Unity

## 本記事について

この記事は [Unity Advent Calendar 2023 シリーズ2](https://qiita.com/advent-calendar/2023/unity) の20日目の記事です。

https://protopedia.net/prototype/4990

ATOM Mate for toio についている距離センサーを利用して、障害物から逃げるロボットを作りました。
本記事ではこの作品における Unity 側の実装について紹介します。

リポジトリ

https://github.com/teach310/ATOMMateForToioSample/tree/main

## 関連

https://qiita.com/Teach/items/98eda5c1b4f2795c4770

https://qiita.com/Teach/items/d8e483dcfc6e61b4877a

## 環境

- Unity 2022.3
- CoreBluetoothForUnity 0.4.7

## ATOM Mate for toio とは

https://www.switch-science.com/products/8500

[toio™ コア キューブ](https://www.switch-science.com/products/6300)と連携するために作られた M5ATOM 用拡張ベースです。

toio は上部がレゴ®ブロック互換、ATOM Mate for toio は下部がレゴ®ブロック互換となっているため、そのまま物理的にくっつける形になります。そのため、toio がなくても M5Atom のバッテリー、距離センサーとして使うことができます。

本記事の作品では M5AtomS3 を上部に取り付けています。M5AtomS3 側の実装については以下の記事にまとめています。

https://qiita.com/Teach/items/3fee929b2c4671815d0a

## システム構成

前述の通り、toio と ATOM Mate for toio は物理的にくっついているだけで通信していません。
そのため、Unity から ATOM Mate for toio の距離センサーの情報を取得するには、toio ではなく、ATOM Mate for toio と組み合わせた M5Atom と通信する必要があります。

Unity と toio 間の通信は [toio SDK for Unity](https://github.com/morikatron/toio-sdk-for-unity) を使うことができます。

M5Atom と Unity 間の通信は [CoreBluetoothForUnity](https://github.com/teach310/CoreBluetoothForUnity) というライブラリを作成し、それによって BLE 通信を行うようにしました。

![system.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/47bc679a-1a8b-3907-83ec-1a2c67281942.png)

主に以下のような通信を行っています。

M5Atom -> Unity: 距離センサーの値
Unity -> M5Atom: 顔の表示
Unity -> toio: 移動指示

## 距離センサーの値の取得

距離センサーの Bluetooth 仕様を以下のように設計しました。

|プロパティ|値|補足|
|:--|:--|:--|
|Service UUID |`0B21C05A-44C2-47CC-BFEF-4F7165C33908`|独自UUID|
|Characteristic UUID |`29C2D1B2-944A-4FBA-AFCD-133E09532556`|独自UUID|
|Properties|Read, Notify|

|データ位置|データ型|内容|
|:--|:--|:--|
|0|UInt16|距離 (mm)|

M5AtomS3 のリポジトリは以下です。

https://github.com/teach310/ToioFaceM5/tree/main

そして、これと通信するためのスクリプトが以下です。

https://github.com/teach310/ATOMMateForToioSample/blob/main/Assets/Scripts/ToioFace.cs

簡略化して説明すると以下のような流れになります。

1. Unity から M5AtomS3 をスキャンして探す (ServiceUUIDを使用)
2. M5AtomS3 に接続する
3. Characteristic という距離センサーの値を取得するためのデータの入れ物みたいなものを取得
4. Characteristic の Notify を有効化
5. DidUpdateValueForCharacteristic で距離センサーの値を受け取る

CoreBluetoothForUnity における ServiceUUID と CharacteristicUUID を用いた接続方法については以下の記事

https://qiita.com/Teach/items/becb5045cba1e964ac24

Notify (センサー端末からのデータ送信) については以下の記事で解説しています。

https://qiita.com/Teach/items/8830b502aa1a414869cb

## toio の振る舞い用AI

ランダムな移動と、障害物を回避するための振る舞いは FSM (有限状態機械) で実装しています。

![スクリーンショット 2023-12-18 23.32.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/9dab4a74-736c-89da-ed57-03f09b7b26c1.png)

OnUpdate でイベントを検知したら遷移という AnimationController のような作りにしています。

[ToioMoveRandomPoints.cs](https://github.com/teach310/ATOMMateForToioSample/blob/main/Assets/Scripts/ToioBehaviours/ToioMoveRandomPoints.cs)

```ToioMoveRandomPoints.cs
    public override void OnUpdate()
    {
        base.OnUpdate();

        if (face.Connected)
        {
            if (UpdateDistanceIfNeeded() && IsObstacleFront())
            {
                controller.ChangeBehaviour<ToioMoveAway>();
                return;
            }
        }

        if (!cube.isGrounded)
        {
            cube.PlayPresetSound(5, order: Cube.ORDER_TYPE.Strong); // Mat out
            controller.ChangeBehaviour<ToioStay>();
            return;
        }

        if (cubeManager.synced)
        {
            Movement mv = cubeHandle.Move2Target(targetPos, maxSpd: MaxSpeed, tolerance: Tolerance).Exec();
            if (mv.reached)
            {
                face.SetExpression(ToioFace.Expression.Neutral);
                SetTargetPos();
            }
        }
    }
```

## 距離センサーの値の誤検知の対策

距離センサーの値によって障害物との距離を把握し、距離が近ければ回避行動を行うようにしています。
しかし、距離センサーの値は誤検知が多く、障害物がないのに近い値が取得されることがあります。

そこで、直近3回の距離センサーの値を保持し、その中で最も近い値を採用するようにしています。

```ToioMoveRandomPoints.cs
    bool UpdateDistanceIfNeeded()
    {
        if (_latestDistanceFrame == face.LatestDistanceUpdateFrame)
        {
            return false;
        }

        distanceQueue.Enqueue(face.Distance);
        if (distanceQueue.Count > 3)
        {
            distanceQueue.Dequeue();
        }
        _latestDistanceFrame = face.LatestDistanceUpdateFrame;
        return true;
    }

    bool IsObstacleFront()
    {
        var average = distanceQueue.Average();
        return 1 < average && average < 150;
    }
```

## おわりに

本記事では ATOM Mate for toio と Unity で通信する方法と、距離センサーの値の取得方法について紹介しました。

私は本作品を作ることで、Bluetooth Low Energy (BLE) や、ネイティブプラグイン、Swift 等様々な知見を得ることができました。特に BLE は BLE 対応おもちゃで遊ぶ等応用が効きそうなため学んで良かったと思っています!

CoreBluetoothForUnity のアドベントカレンダーもあるため、よろしければそちらもご覧ください！

https://qiita.com/advent-calendar/2023/core-bluetooth-for-unity

toio めちゃめちゃ楽しいです！もし興味があればぜひ遊んでみてください！

## 謝辞

Unity ゲーム開発者ギルドのみなさんとの交流のおかげで日々の開発が楽しく行えました。この場を借りて感謝申し上げます！UGDGはいいぞ！