加速度センサーでWheel Colliderを操作して車輪の再発明

# 動機
某教育番組にて車のハンドルを模した玩具を段ボールで工作する回があり、
それを見ながら子供と一緒に実際に作りました。
![DSC_0168.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211817/9c7a3d06-69d9-00bc-2ffe-0b18eb5ee139.jpeg)

そこそこ気に入ったようで、自分の声でアクセル音やタイヤのスリップ音を
再現しながら遊んでいました。

（これで実際に車の運転が体感できたらもっと面白いだろうな～）

そこで今回は「自分で工作したモノがコンピュータと連動して動かせる！」
という~~大人のエゴ~~感動を実現させることにしました。

# 何を実現させるか
子供の作ったハンドルの玩具を元にして「テレビに映した車が運転できる」を実現させます。

# 実現方法

#### 使用技術
ハンドルの玩具と連動させるためには回転を判別できるセンサーで検知する必要があります。
　⇒**加速度センサー**に決定

ジャイロセンサーも検討しましたが、~~複雑ですぐに作れなそうだった~~ユーザーの向きなどは
気にする必要がなかったので採用しませんでした。

#### 開発環境
テレビに車を映して操作する必要があり、前述の通り加速度センサーも備えていることを
考えると、スマートフォンで3Dモデル操作の仕組みをすぐに開発できるものということで、
　⇒**Unity**に決定

# 開発
## Wheel Colliderによる車の作成
Unityには**Wheel Collider**(ホイール コライダー)という車輪を再現できるオブジェクトがあり、
車両の3Dモデルに組み合わせることでUnity上で車の操作が実現できます。

このWheel Colliderには`motorTorque`と`steeringAngle`というプロパティがあり、
`motorTorque`で前進/後退のアクセル、`steeringAngle`でハンドルの操作ができます。

まずは[Wheel Collider チュートリアル](https://docs.unity3d.com/ja/2018.4/Manual/WheelColliderTutorial.html)を参考に車のオブジェクトとWheel Colliderを制御する
スクリプトを作成し、スクリプトを車のオブジェクトにアタッチしておきます。

## 加速度センサーの動作検証
カーレースなどのゲームでよくある操作ですが、スマホを前後左右に傾けることで
アクセルやハンドル制御しています。
今回は加速度センサーで同様の制御を行います。

Unityのスクリプト上では加速度センサーの値を`Input.acceleration`で取得できます。

今回はAndroidのスマートフォンをlandscape(横向き)で操作することを前提とします。
その上でUnityで加速度センサーの値を取得すると、以下であることが確認できました。

* `Input.acceleration.x`は**左右の傾き**
  * `-1`：左90度
  * `0`：地面に垂直
  * `+1`：右90度
* `Input.acceleration.z`は**前後の傾き**
  * `-1`：画面が天井向きで地面に平行
  * `0`：地面に垂直
  * `+1`：画面が床向きで下で地面に平行

言葉だけでは説明しづらいので、画像でも補足しておきます。

`Input.acceleration.x = 0`の状態
![DSC_0133.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211817/4a8f18fb-ffba-b193-d691-1f422ed90727.jpeg)

`Input.acceleration.x = 0.5`の状態
![DSC_0134.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211817/bb044e3b-9d9f-1603-96c1-8f08d10aee9e.jpeg)

`Input.acceleration.x = -0.5`の状態
![DSC_0135.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211817/c073d1fd-332d-4fcb-fd72-56fab652d91e.jpeg)

`Input.acceleration.z = 0`の状態
![DSC_0136.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211817/10db9a0d-cc9b-4582-19a1-3a850374b5f5.jpeg)

`Input.acceleration.z = -1.0`の状態
![DSC_0137.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211817/2365c23a-bf82-2770-f48c-e44a41aa2fce.jpeg)


このことから、`Input.acceleration.x`はハンドル操作を制御する`steeringAngle`に、
`Input.acceleration.z`はアクセルを制御する`motorTorque`に連動させればよいことが
わかりました。

## 加速度センサーと車の連動
Wheel Collider チュートリアルのコードの中では、Wheel Colliderの`motorTorque`と
`steeringAngle`をカーソルキー入力により制御しています。
その個所を加速度センサーから取得する値で制御するよう、以下のように変更します。

ここで`Input.acceleration.z`に`-1`を乗算しているのは、スマートフォンの前後の傾きで
取得できる値と`motorTorque`プロパティの前進/後退の値を一致させるためです。
(`motorTorque`は`>0`で前進のアクセル、`<0`で後退のアクセル、`=0`はアイドリング)
```csharp:SimpleCarController.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
    
public class SimpleCarController : MonoBehaviour {
    public List<AxleInfo> axleInfos; // 個々の車軸の情報
    public float maxMotorTorque; //ホイールに適用可能な最大トルク
    public float maxSteeringAngle; // 適用可能な最大ハンドル角度
        
    public void FixedUpdate()
    {
        // カーソルキーで操作する場合
        // float motor = maxMotorTorque * Input.GetAxis("Vertical");
        // float steering = maxSteeringAngle * Input.GetAxis("Horizontal");

        // 加速度センサーで操作する場合
        float motor = maxMotorTorque * Input.acceleration.z * (-1);
        float steering = maxSteeringAngle * Input.acceleration.x;

        foreach (AxleInfo axleInfo in axleInfos) {
            if (axleInfo.steering) {
                axleInfo.leftWheel.steerAngle = steering;
                axleInfo.rightWheel.steerAngle = steering;
            }
            if (axleInfo.motor) {
                axleInfo.leftWheel.motorTorque = motor;
                axleInfo.rightWheel.motorTorque = motor;
            }
        }
    }
}

[System.Serializable]
public class AxleInfo {
    public WheelCollider leftWheel;
    public WheelCollider rightWheel;
    public bool motor; //このホイールがエンジンにアタッチされているかどうか
    public bool steering; // このホイールがハンドルの角度を反映しているかどうか
}
```

## 車と効果音との連動
次にアクセルやハンドル操作に連動してエンジン音やタイヤのスリップ音を
出力できるようにします。

`AudioSource`コンポーネントを追加したオブジェクトを3つ作成します。
`AudioClip`には「アイドリング音」「アクセル音」「スリップ音」を用意します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211817/a446ba8e-a218-9d86-ef0c-d34b0175d79a.png)

スクリプト側で再生・停止が制御できるように、各AudioSourceのSerializeFieldを
宣言しておきます。
```csharp:SimpleCarController.cs

public class SimpleCarController : MonoBehaviour
{
    [SerializeField] private AudioSource audioSourceIdling;
    [SerializeField] private AudioSource audioSourceAccel;
    [SerializeField] private AudioSource audioSourceSkid;

    (中略)

```

Scriptのコンポーネントにて、追加したSerializeFieldにAudioSourceをアタッチします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211817/9d8a0aa1-3d3c-64c3-c711-00d15e8a0e8a.png)

ここからは再びコーディングです。
アイドリング音は起動時にスタートしたいので、以下のように記述します。
```csharp:SimpleCarController.cs

public class SimpleCarController : MonoBehaviour
{

    (中略)

    void Start() {
        audioSourceIdling.Play();
    }

    (中略)

```

アクセル音とスリップ音の再生は`FixedUpdate()`内に記述します。
Wheel Colliderへの処理が終わった直後あたりにそれぞれ追加します。

スリップ音ですが、ここでは`steering`が±5の時には停止させています。
これは、ハンドルの回転にある程度の遊びを持たせておくことで、むやみやたらに
スリップ音が鳴ることを抑止するためです。

さらに臨場感を求めたい人であれば、アクセル音を数種類用意し、`motor`の数値によって
多段階にする、といったこだわりを入れても良いのではないかと思います。
```csharp:SimpleCarController.cs

public class SimpleCarController : MonoBehaviour
{

    public void FixedUpdate()
    {
        (中略)

        foreach (AxleInfo axleInfo in axleInfos)
        {
            if (axleInfo.steering)
            {
                axleInfo.leftWheel.steerAngle = steering;
                axleInfo.rightWheel.steerAngle = steering;
            }
            if (axleInfo.motor)
            {
                axleInfo.leftWheel.motorTorque = motor;
                axleInfo.rightWheel.motorTorque = motor;
            }
        }

        // アクセル音
        if(motor == 0)
        {
            audioSourceAccel.Stop();
        }
        else
        {
            if(!audioSourceAccel.isPlaying)
            {
                audioSourceAccel.Play();
            }
        }

        // スリップ音
        if(steering >= -5 && steering <= 5)
        {
            audioSourceSkid.Stop();
        }
        else
        {
            if(!audioSourceSkid.isPlaying)
            {
                audioSourceSkid.Play();
            }
        }
    }
```

車の3Dモデルを走らせる際には、Planeの広さを決めたり、壁を作ったり、
今回、車の3Dモデルやサーキット場の平面図はフリーのものを利用させてもらいました。
スマホ単体でも十分遊べる完成度w

## ハンドルと連動させる
さあ結合テストです笑

スマートフォンの映像は、Chromecastでテレビに映しておきます。

以下のようにハンドルの裏にスマートフォンをアタッチ(!?)させます笑
隙間からアプリを起動したら、スタートです。
![DSC_0170.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211817/0b0ca520-9667-d374-8a87-497bc9f0d8f6.jpeg)

ハンドルを前に倒すと進み、左右に切ると、ちゃんと曲がります。
![testplay1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211817/235b205d-2da5-7e89-b20f-ddb3c41e1650.gif)

# 最後に
平日の夜中に開発を進め、土曜の朝にお披露目。
ちゃんと動くこともあって、子供は素直に感動してくれたので良かったです。

作り終えて、こうして記事に起こしている時に知った、というか思い出したことですが、
段ボールの動作が連動する玩具として[NINTENDO LABO](https://www.nintendo.co.jp/labo/)がありましたね。

それを踏まえると今回はまさに[車輪の再発明](https://ja.wikipedia.org/wiki/%E8%BB%8A%E8%BC%AA%E3%81%AE%E5%86%8D%E7%99%BA%E6%98%8E)。