Unityで加速度センサを用いてダーツを投げるまで

# はじめに

この記事は，Unity上でスマートフォンの加速度センサを用いてダーツを投げるまでの方法を記していきます。

環境は Unity 2017.3 です。
スマートフォンはAndroid端末を用います。
ビルド（実機テスト）に関しての詳細は省くので，分からなかった場合は他のサイト等を参考にしてください。
（Androidであれば[ここ:link:](https://unity3d.sakura.ne.jp/unity/android-build.html)，iOSであれば[ここ:link:](https://unity3d.sakura.ne.jp/unity/unity-for-ios.html)など）

# 全体像
はじめに，ダーツを投げるまでの全体像を確認します。

1. ダーツを用意する
1. 加速度を取得する
1. 加速度を計算する
1. ダーツを生成し，速度を与える
1. 実機テスト

こんな感じです。

# 作る

## ダーツを用意する

まず投げるためのダーツがなければ始まりません。
本格的なものを用意してもいいし，デフォルトのsphereを投げるとかでも良いのですが，今回はデフォルトの3Dオブジェクトを組み合わせてそれっぽい見た目のものを作っていくことにします。

今回はCube2つを薄くして組み合わせてフライトに，Capsule1つを伸ばして本体にしてみました。

階層と設定はこんな感じです。

```:階層
Dart ┬ Flight ┬ Flight1 (羽根)
     |        └ Flight2 (羽根)
     └ Schaft (本体)  
```

部位|種類|Position(X, Y, Z)|Rotation(X, Y, Z)|Scale(X, Y, Z)
:-:|:-:|:-:|:-:|:-:
Flight|Empty|`(0, 0, 0)`|`(0, 0, 0)`|`(0, 0, 0)`
Flight1|Cube|`(0, 0, -1.54)`|`(0, 0, 90)`|`(1, 0.02, 1)`
Flight2|Cube|`(0, 0, -1.54)`|`(0, 0, 0)`|`(1, 0.02, 1)`
Schaft|Capsule|`(0, 0, 0)`|`(90, 0, 0)`|`(0.1, 1.8, 0.1)`

Materialも適当に作って見た目を変えるとこんな感じになります。

[![https://gyazo.com/f126e80731a3aa92637e61f6e80866e3](https://i.gyazo.com/f126e80731a3aa92637e61f6e80866e3.png)](https://gyazo.com/f126e80731a3aa92637e61f6e80866e3)


最後に，物理挙動をさせるためにRigidbodyをInspector上でアタッチし，
後から使い回せるようにPrefab化しておきます。

[![https://gyazo.com/259bbcd603e4e70bd6a8b76dd66f374e](https://i.gyazo.com/259bbcd603e4e70bd6a8b76dd66f374e.png)](https://gyazo.com/259bbcd603e4e70bd6a8b76dd66f374e)

また，Prefab化したらHierarchyビュー上のダーツは削除しておきましょう。

## 加速度を取得する

次にダーツを投げるために加速度を取得していきます。

```c#:加速度を取得
var acceleration = Input.acceleration;
```

Unityだとこれだけで簡単に加速度を取得できてしまいます。

今回は加速度から速度を計算したいため，直近の15フレームの加速度を保持しておくことにします。
後で使いやすいように`Queue`で`x,y,z`の3種類を別々に保持しておきます。
script全体はこんな感じになります。

```c#:AccelerationScript.cs
using System.Collections.Generic;
using UnityEngine;

public class AccelerationScript : MonoBehaviour
{
    private Queue<float> lastXAccels = new Queue<float>();
    private Queue<float> lastYAccels = new Queue<float>();
    private Queue<float> lastZAccels = new Queue<float>();

    // 保持するフレーム数
    private readonly int ACCEL_NUM = 15;

    private void Start()
    {
        // 加速度のキューを0埋めしておく
        for (int i = 0; i < ACCEL_NUM; i++)
        {
            lastXAccels.Enqueue(0);
            lastYAccels.Enqueue(0);
            lastZAccels.Enqueue(0);
        }
    }

    private void Update()
    {
        float x = Input.acceleration.x;
        float y = Input.acceleration.y;
        float z = Input.acceleration.z;

        // 一番古い加速度をDequeueで削除。最新の加速度をEnqueueで追加
        lastXAccels.Dequeue();
        lastXAccels.Enqueue(x);
        lastYAccels.Dequeue();
        lastYAccels.Enqueue(y);
        lastZAccels.Dequeue();
        lastZAccels.Enqueue(z);
    }
}
```

`Queue`を使うために`using System.Collections.Generic;`するのを忘れずに。

## 加速度を整形する

次にダーツを飛ばせるように加速度を速度に整形していきます。(すごい！)

はじめに，スマホの持ち方（向き）を決めておきます。
（向きによってx,y,zが入れ替わるので注意が必要です）

今回は，奥行方向に関してはx，上下左右方向に関してはy,zを使うことにします。

奥行方向は直近のフレームの中の最大の絶対値を利用することにします。
Linqを使って，こんな感じに取得できます。
`Mathf.Clamp()`で0～2の範囲に抑えて，定数を掛けます。
`* 20`の部分はマジックナンバーです。最後に調整していきます。

```c#:奥行方向
using System.Linq;

var xMax = lastXAccels.Select(x => Mathf.Abs(x)).Max();
zVelocity = Mathf.Clamp(xMax, 0, 2) * 20;
```

左右上下方向は"ブレ"により決定することにします。
"ブレ"は平均値二乗誤差で計算することにします。
これもまたLinqを使って，こんな感じに書けます。
`Average()`で平均値を取得し，平均値との差の絶対値の合計を要素数`Count`で割ります。
`* 15`と`* 20`もマジックナンバーなので，最後にダーツの飛び具合を見ながら調整してください。

```c#:上下左右方向
var yAverage = lastYAccels.Average();
var yVariance = lastYAccels
    .Select(y => Mathf.Abs(y - yAverage))
    .Sum() / lastYAccels.Count;
yVelocity = yVariance * Mathf.Sign(lastYAccels.Last()) * 15;

var zAverage = lastZAccels.Average();
var zVariance = lastZAccels
    .Select(z => Mathf.Abs(z - zAverage))
    .Sum() / lastZAccels.Count;
xVelocity = zVariance * Mathf.Sign(lastZAccels.Last()) * 20;
```

全体をまとめるとこんな感じになります。
`CalcVelocity()`を外部から呼ぶと，直近の加速度から速度を計算して返してくれる感じです。

```c#:AccelerationScript.cs
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

public class AccelerationScript : MonoBehaviour
{
    private Queue<float> lastXAccels = new Queue<float>();
    private Queue<float> lastYAccels = new Queue<float>();
    private Queue<float> lastZAccels = new Queue<float>();

    // 保持するフレーム数
    private readonly int ACCEL_NUM = 15;

    private void Start()
    {
        // 加速度のキューを0埋めしておく
        for (int i = 0; i < ACCEL_NUM; i++)
        {
            lastXAccels.Enqueue(0);
            lastYAccels.Enqueue(0);
            lastZAccels.Enqueue(0);
        }
    }

    private void Update()
    {
        float x = Input.acceleration.x;
        float y = Input.acceleration.y;
        float z = Input.acceleration.z;

        // 一番古い加速度をDequeueで削除。最新の加速度をEnqueueで追加
        lastXAccels.Dequeue();
        lastXAccels.Enqueue(x);
        lastYAccels.Dequeue();
        lastYAccels.Enqueue(y);
        lastZAccels.Dequeue();
        lastZAccels.Enqueue(z);
    }

    public Vector3 CalcVelocity()
    {
        float xVelocity;
        float yVelocity;
        float zVelocity;

        // |x|の最大値
        var xMax = lastXAccels.Select(x => Mathf.Abs(x)).Max();
        // z方向の速度（奥行方向の速度をxMaxを元に計算）
        zVelocity = Mathf.Clamp(xMax, 0, 2) * 20;

        // yの絶対値平均誤差
        var yAverage = lastYAccels.Average();
        var yVariance = lastYAccels
            .Select(y => Mathf.Abs(y - yAverage))
            .Sum() / lastYAccels.Count;
        yVelocity = yVariance * Mathf.Sign(lastYAccels.Last()) * 15;

        // zの絶対値平均誤差
        var zAverage = lastZAccels.Average();
        var zVariance = lastZAccels
            .Select(z => Mathf.Abs(z - zAverage))
            .Sum() / lastZAccels.Count;
        xVelocity = zVariance * Mathf.Sign(lastZAccels.Last()) * 20;

        var vec = new Vector3(xVelocity, yVelocity, zVelocity);

        return vec;
    }
}
```

## ダーツを飛ばす

最後に，上で作ったメソッドを用いてダーツを投げれるようにしていきます。
今回は画面タップでダーツが投げられるようにします。
するとスクリプトはこんな感じになります。

```c#:Throw.cs
using UnityEngine;

public class Throw : MonoBehaviour
{
    [SerializeField]
    private GameObject dartsPrefab;
    [SerializeField]
    private AccelerationScript accelerationScript;

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            var velocity = accelerationScript.CalcVelocity();
            var dart = Instantiate(dartsPrefab);
            dart.GetComponent<Rigidbody>().velocity = velocity;
        }
    }
}

```

あとはInspector上で対応させていきます。

1. Hierarchy上でCreate Empty（GameManagerとかの名前に変えておく）
2. そこにInspector上でAccelerationScriptとThrowをアタッチ
3. さらにInspector上でThrowのDartsPrefabに先程作ったダーツのPrefabをセット
4. さらにInspector上でThrowのAcceleration ScriptにGameManagerをD&Dしてスクリプトをセット

するとこんな感じになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/9814/51d03e67-e6a5-16d7-5b5c-ecb43b754398.png)

## 実機テスト

実機テストの前に，カメラの位置とダーツの角度を調整しておきます。
Main CameraのPositionのZを`-10`から`-5`に，PrefabのDartのRotationを`0`から`-30`に変更します。

このままで実行すると，クリック毎にダーツが生成されてそのまま落ちていくのが確認できるはずです。
これは，パソコンだと加速度が必ず`(0, 0, 0)`として認識されるためです。

加速度を確認するには実機（スマホ）で動かしてみることが必須なので，実機でテストしていきます。
_JDKなどを入れていない場合は [はじめに](#はじめに) 内のリンクを参考に設定してください_

まず， `Ctrl/Cmd + Shift + B` で`Build Settings`を開き，ビルド設定をします。
`Add Open Scenes`かなにかでビルドするシーンを設定します。
[![https://gyazo.com/484647b6d9ab9f841d2d744cf1c6a9a1](https://i.gyazo.com/484647b6d9ab9f841d2d744cf1c6a9a1.png)](https://gyazo.com/484647b6d9ab9f841d2d744cf1c6a9a1)

次に，`Bulid Settings`内の`Player Settings...`を押して`Company Name`と`Product Name`を適当に設定します。
また，`Player Settings`内で`Other Settings`を開き，`Package Name`も併せて変更します。

[![https://gyazo.com/faf0f22fe5c504d996b4761b944c6a3b](https://i.gyazo.com/faf0f22fe5c504d996b4761b944c6a3b.png)](https://gyazo.com/faf0f22fe5c504d996b4761b944c6a3b)

さらに画面を横画面に固定するため，`Player Settings`内で`Resolution and Presentation`を開き，`Orientation`の`Default Oriantation`を`Landscape Right`に設定しておきます。

[![https://gyazo.com/791c5c9f01aca70031ae6fdb286319c3](https://i.gyazo.com/791c5c9f01aca70031ae6fdb286319c3.png)](https://gyazo.com/791c5c9f01aca70031ae6fdb286319c3)

そうしたら`Build And Run`を押し，保存先を指定してビルドと実行をします。

端末を振りながらタップするとダーツが飛んでいくことが確認できるはずです。

以上です。お疲れ様でした！

# おまけ

現状だとダーツは回転せずにそのまま飛んでいくので，横方向にランダムに回転させながら，飛んでいく方向に先端を向けさせてみようと思います。

```c#:DartsScript
using UnityEngine;

public class DartsScript : MonoBehaviour
{
    private Rigidbody rb;

    private void Awake()
    {
        rb = GetComponent<Rigidbody>();
    }

    private void Update()
    {
        float angularVelocity = Random.Range(-100, 100);
        // 進行方向を向かせる
        var look = Quaternion.LookRotation(rb.velocity);
        var z = transform.eulerAngles.z;
        var angle = Quaternion.Euler(0, 0, z + angularVelocity * Time.deltaTime);
        transform.rotation = look * angle;
    }
}
```

これをダーツのPrefabにアタッチしておけばOKです！

[![https://gyazo.com/7a9cace8cbde7df92105572f06a000d0](https://i.gyazo.com/7a9cace8cbde7df92105572f06a000d0.png)](https://gyazo.com/7a9cace8cbde7df92105572f06a000d0)

ここまで読んでくださりありがとうございました！