unityでtoio連携iOS app開発　3歳児でも遊べるシューティングゲームを作った話


これは「toio™(ロボットトイ | toio（トイオ）) Advent Calendar 2019」の23日目の記事になります。

# はじめに
2ヶ月前からiOS Appを作りたくて、Unityをいじり始めました。
せっかくなのでtoioも動かしたいなと思っていたのですが、BLEの制御はハードルが高くて二の足を踏んでいました。

そんなところにtoioアドベントカレンダーでちょうどいい記事が！
[Unityを使って手軽にスマホアプリからtoioを操作する方法（ライブラリ付き）](https://qiita.com/HrshTkg/items/fce40de243946821e425)

有料のUnity Assetを使ってtoioをUnityからBLE制御することができていたので、
この記事のソースコードを使ってシューティングゲームを作ってみました。
※なぜシューティングゲームを作ったの？　については記事最後に。

## 私のスキル
[以前の記事](https://qiita.com/yuyaHirano/items/ddeb9d8bf7f844e52f1f)でも書きましたが、元々はC言語しか知らない程度でした。
そんな中、Unityを使ったiOS App開発は2ヶ月前に勉強を始めたばかりでしたが、
この本を一通りなぞることでかなり初歩的な概要は掴めたように思います。
[Unityの教科書 Unity2019完全対応版 2D&3Dスマートフォンゲーム入門講座](https://amzn.to/2EMImKN)

この本は私のような初心者におすすめできます。

# 作ったもの
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">先ほどのを応用して、シューティングゲームも作りました！<br>toioを持って逃げる人と、スマホの画面タップして射的する人。<br>後ほどtoioアドベントカレンダーに記事載せます。<a href="https://twitter.com/hashtag/toio?src=hash&amp;ref_src=twsrc%5Etfw">#toio</a> <a href="https://t.co/92jiEMwNIB">pic.twitter.com/92jiEMwNIB</a></p>&mdash; Yuya Hirano @ BREMENGames ゲムマ秋Q15 (@idiot_radio_hy) <a href="https://twitter.com/idiot_radio_hy/status/1209057010840305664?ref_src=twsrc%5Etfw">December 23, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

・実空間でcore Cubeをマット上で動かして逃げる人
・スマホ内で動くcore Cubeを狙って画面をタップしてボールを打つ人
の二人で遊ぶ、対戦型シューティングゲームです。

大雑把にいうと、
- 1. toioの読み取りセンサの値をリアルタイムにアプリ内の描画に反映
- 2. スマホをタップしたら、その位置に向かってボールを飛ばす
- 3. ボールがcore Cubeに当たったら得点を入れる
こんな感じでプログラミングしていきました。

toioプログラミングに関わるのは1.の部分がほとんどなので、
この記事では１.に主に焦点を当てて説明します。
他はかなり大雑把に省きますので、質問など会ったら個別にコメント等お願いいたします。

# 1. toioの読み取りセンサの値をリアルタイムにアプリ内の描画に反映
Unity上の画面はこんな感じです。
オブジェクトの定義と、それらを動かすプログラム(スクリプト)を書くことでゲームを作っていきます。

<img width="1675" alt="スクリーンショット 2019-12-21 22.11.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542048/0f1d327d-646a-3ba3-96c1-ab222adbb067.png">

## 1-1　実空間とアプリ空間のScale合わせ
toio Core Cubeの実空間上の動きとスマホ内の動きが連動するので、サイズ感を合わせる必要があります。
今回はオブジェクトの"Scale"をcm単位として、モデリングしていきました。

例えば、core Cubeは実寸法が32x24x32mmなので、
Unity上では3.2 x 2.4 x 3.2 で定義する　と言った感じです。
Matも同様に、実寸法を測定して定義しました。
<img width="339" alt="スクリーンショット 2019-12-21 22.14.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542048/8e6c4bcc-efe2-a73f-9927-396cb8cc1e12.png">
<img width="340" alt="スクリーンショット 2019-12-21 22.15.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542048/8ec4d65a-f624-d252-38fc-bb5f502ea119.png">

## 1-2　読み取りセンサのScale合わせ
読み取りセンサで取得できるX/Y座標ですが、[公式の技術仕様](https://toio.github.io/toio-spec/docs/ble_id)にもあるように、単位がミリメートルではありません。

<img width="814" alt="スクリーンショット 2019-12-21 23.08.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542048/e722825b-b1c4-6536-314e-3ca6f207b8a5.png">

これは現物合わせなのですが、
toio IDで得られる座標を1.4倍することで実寸法とのスケールを合わせました。

## 1-3. 読み取りセンサの値のリアルタイム反映
この章が今回の記事の肝の部分になります。
ちょっと込み入った話になりますがご容赦ください。

[参考記事](https://qiita.com/HrshTkg/items/fce40de243946821e425)のプログラムを使用することで、読み取りセンサの値をnotifyで取得できるようになります。

今回はこのソースコードを拡張することでUhityオブジェクトの位置制御に利用します。
サンプルプログラムでは、読み取りセンサの値をNotifyで取得する度にUI上のtextを書き換えるような処理が書かれています。

この辺です。

```js
this.CubeController.GetComponent<CubeController>().GetIdInformation((positionX, positionY, standardID, angle) =>
{
    if (positionX != 0xffff || positionY != 0xffff)
    {
         this.PositionID.GetComponent<Text>().text = "X座標 : " + positionX.ToString("D3") + " / Y座標 : " + positionY.ToString("D3");
    }
```

この座標情報をCubeの位置の描画に反映すれば良さそうですが、そのままでは処理不可的にもったいないことになってしまいそうです。
アプリ上では画面更新のタイミング(1フレーム)に一度だけ表示を書き換えれば良いのですが、
このコードだとNotificationが飛んでくる度に書き換え処理が走ってしまいます。

なので、ここはスクリプトを分けてちょっと書き換えてみました。
以下のように動作するループごとに役割を分けてあげるとうまくいきそうです。

・最新の座標情報を保持するスクリプト (director.cs)
　・notificationが飛んでくるごとに最新の座標を保持
　・1フレームごとの処理よりも早いタイミングで動作

・core Cubeの位置の描画を行うスクリプト (coreCubeController.cs)
　・1フレームに1度だけ、最新の描画処理を行う

スクリプト分けたので、スクリプト間で値のやりとりが発生しますが実現方法は以下。

coreCubeController.csの中でpublicのclassでCubeの座標/Angle情報を保持するclassをpublicで宣言してあげて

```js
//coreCubeController.cs内で宣言
public class CubeStatus
{
    public int cube_PosX;
    public int cube_PosY;
    public int cube_Angle;
}

public class coreCubeController : MonoBehaviour
{
    public CubeStatus myCubeStatus = new CubeStatus();
}
```

それをdirector.csで読んで値を代入します。
これで最新の座標がmyCubeStatusの中に保持されます。

```js
//director.cs内の処理　座標代入だけ
this.CubeController.GetComponent<CubeController>().GetIdInformation((positionX, positionY, standardID, angle) =>
{
    if (positionX != 0xffff || positionY != 0xffff)
    {
        coreCube.GetComponent<coreCubeController>().myCubeStatus.cube_PosX = positionX;
        coreCube.GetComponent<coreCubeController>().myCubeStatus.cube_PosY = positionY;
    }
    if (angle != 0xffff)
    {
        coreCube.GetComponent<coreCubeController>().myCubeStatus.cube_Angle = angle;
    }
```

で、coreCubeController.csではそのmyCubeStatusの情報を使ってobjectの座標に反映するだけ。
coreCubeController.cs内のUpdate()はフレーム毎に呼ばれる処理なので、これで1フレームに1度だけの描画を実現することができました。
なお、past_*は前フレームの値を保持している変数です。この変数と平均をとることで、動きを多少平滑化しました。

```js
void Update()
{
    transform.position = new Vector3(1.4f*((myCubeStatus.cube_PosX + past_x)/2 -(955+545)/2) / 10.0f,
                                     0,
                                     -1.4f*((myCubeStatus.cube_PosY + past_y)/2 -(45+455)/2) / 10.0f);
    transform.eulerAngles = new Vector3(0,(myCubeStatus.cube_Angle + past_angle)/2 , 0);

    past_x = myCubeStatus.cube_PosX;
    past_y = myCubeStatus.cube_PosY;
    past_angle = myCubeStatus.cube_Angle;
}
```

ここまでのことをやることで、Cube位置のリアルタイム反映は実現完了です。
結構ヌルヌル動いています。これなら十分ゲームに使えそうです。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">toioの動きをリアルタイムにiPhoneアプリ上に反映出来た！<br>unityでプログラミングしてます。<br>2ヶ月前に初めて勉強し始めたけど、unityでのiPhoneアプリ開発思ったより簡単で楽しい。<a href="https://twitter.com/hashtag/toio?src=hash&amp;ref_src=twsrc%5Etfw">#toio</a> <a href="https://t.co/OmU8fMZjLU">pic.twitter.com/OmU8fMZjLU</a></p>&mdash; Yuya Hirano @ BREMENGames ゲムマ秋Q15 (@idiot_radio_hy) <a href="https://twitter.com/idiot_radio_hy/status/1209056018958651392?ref_src=twsrc%5Etfw">December 23, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


# 2. スマホをタップしたら、その位置に向かってボールを飛ばす
これは参考文献
[Unityの教科書 Unity2019完全対応版 2D&3Dスマートフォンゲーム入門講座](https://amzn.to/2EMImKN)
の7章を参考にすることで実現できます。

```js
Camera.main.ScreenPointToRay()
```
という関数がめちゃくちゃ便利で、メインカメラの原点からタップした座標に向かうベクトルを取得できます。

・タップしたらカメラ原点にボールを出現させる
・Camera.main.ScreenPointToRay()でタップした座標に向かうベクトル取得
・そのベクトル方向へ一定速度でボールを移動させる
で目的の挙動を作ることができます。

## 3. ボールがcore Cubeに当たったら得点を入れる
これも[参考文献](https://amzn.to/2EMImKN)の8章を参考にすることで実現できます。
流れは以下のような感じで書いてます。
・ボールとMat、ボールとCubeの当たり判定を行う
・ボールとMatが当たったら、ボールの移動を止め、１秒後にボールを消す
・ボールとCubeが当たったら、UIで用意していた得点表示用テキストの内容を書き換える(+100point)

# さいごに
まず、Unityほんとすごいなと。
スマホアプリ開発、着手するまでものすごく遠い世界の話だったのですが、この2ヶ月のUnity修行で簡単に作れるようになりました。
しかも、マルチプラットフォームなので、Androidでもきっと動くはず。夢が広がります！
あと、toio連携はやはり面白いです。実空間とスマホ内を連携させた遊び方はユニーク。
今回はまだ、ゲーム的に面白くなるような調整も出来てない初歩の初歩と言った段階でこの記事を書きましたが、
面白い物作ってApp storeに公開するのを目標に頑張っていきたいと思います。

## 余談
なぜシューティングゲームを作ったか？
ですが、3歳になる息子でも楽しめるものを作りたいという思いからです。
toioは6歳以上が対象で、ちょっとうちの息子にはまだ早い。

[Unityの教科書 Unity2019完全対応版 2D&3Dスマートフォンゲーム入門講座](https://amzn.to/2EMImKN)
に有るサンプルアプリを作っていたとき、
「画面をタップしたらボールが飛んでいくだけ」のサンプルアプリにいやに興味を示していたので、
toioと組み合わせてシューティングゲームにしてみました。

思惑通り、キャッキャ言って遊んでくれたので、今回は目的達成です！