【LeapMotion+UniRx】手の動きでカメラを動かす

![leap-motion-unirx-capture.png](https://qiita-image-store.s3.amazonaws.com/0/221845/faefb4f9-1902-bf4c-cb36-df74fe553768.png)

# はじめに
マウスやキーボードが使えなくて、入力装置がLeapMotionしかない状況で、 `Main Camera` を動かす方法を考えてみました。

# デモ動画
実際に作ったのはこんな感じです。（表示しているのはLookingGlassですが、趣旨とは関係ないので説明を省きます。）

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/LookingGlass?src=hash&amp;ref_src=twsrc%5Etfw">#LookingGlass</a> 用のアプリを作成中。 <a href="https://twitter.com/hashtag/LeapMotion?src=hash&amp;ref_src=twsrc%5Etfw">#LeapMotion</a> で取得した手の動きで、Unity内のカメラを動かしてみました。手をグーの形にしている間はカメラを動かせます。グーの形じゃなくなったら終了します。マウスドラッグみたいな感覚で使えるの便利です。更に、前後にも動かせます！ <a href="https://t.co/fE8lM754vW">pic.twitter.com/fE8lM754vW</a></p>&mdash; せぎゅ (@segur_vita) <a href="https://twitter.com/segur_vita/status/1108794958318702592?ref_src=twsrc%5Etfw">2019年3月21日</a></blockquote>


手の形がグーのときは、手の動きに合わせてカメラを動かします。グーじゃないときは動かしません。
マウスドラッグの3D版みたいな感じです。手前や奥にも動かせます。

# サンプルコード

こんな感じのコードを作りました。このコードをカメラのオブジェクトにアタッチすれば、動くはずです。 `UniRx` を使っています。

```C#
using Leap;
using System.Collections.Generic;
using System.Linq;
using UniRx;
using UniRx.Triggers;
using UnityEngine;

/// <summary>
/// カメラ制御
/// </summary>
public class CameraController : MonoBehaviour
{
    /** カメラの移動スピード */
    private float speed = 0.025f;

    /** LeapMotionのコントローラー */
    private Controller controller;

    /** エントリポイント */
    void Start()
    {
        // LeapMotionのコントローラー
        controller = new Controller();

        // LeapMotionから手の情報を取得
        var handsStream = this.UpdateAsObservable()
            .Select(_ => controller.Frame().Hands);

        // グー終了判定ストリーム
        var endRockGripStream = handsStream
            .Where(hands => !IsRockGrip(hands));

        // カメラ制御
        handsStream
            // グーなら
            .Where(hands => IsRockGrip(hands))
            // 手のひらの位置を取得
            .Select(hands => ToVector3(hands[0].PalmPosition))
            // バッファに前回と今回の２つの値を詰める
            .Buffer(2, 1)
            // 今回と前回の差から手のひらの移動ベクトルを計算
            .Select(positions => positions[1] - positions[0])
            // 値をログに出力
            .Do(movement => Debug.Log("移動: " + movement))
            // グーが終了したらバッファをクリアにする
            .TakeUntil(endRockGripStream).RepeatUntilDestroy(this)
            // カメラを移動
            .Subscribe(movement => transform.Translate(-speed * movement));
    }

    /** グーかどうか */
    bool IsRockGrip(List<Hand> hands)
    {
        return
            // 片手なら
            hands.Count == 1 &&
            // 全ての指の内、開いている数が0個なら
            hands[0].Fingers.ToArray().Count(x => x.IsExtended) == 0;
    }

    /** LeapのVectorからUnityのVector3に変換 */
    Vector3 ToVector3(Vector v)
    {
        return new Vector3(v.x, v.y, -v.z);
    }
}
```


# `UpdateAsObservable` について

`Update()` をストリームに変換したものです。

こちらの記事が丁寧でとても参考になります。
[UniRx入門 その4 -Updateをストリームに変換する方法とメリット-](https://qiita.com/toRisouP/items/30c576c7b0a99f41fb87)


# `IsRockGrip` について

ここで、手が **グー** の形かどうか判定しています。
まず、 `hands.Count` で、LeapMotionが検出している手の数が1つかどうかを確認しています。
そして、 `hands[0].Fingers.ToArray().Count(x => x.IsExtended)` で、開いている指の数を数えています。
この数が0ならじゃんけんの **グー** だと判断します。

この手法はこちらの記事を参考にさせていただきました。
[LeapMotion+Unityでグー・チョキ・パーを認識する](https://qiita.com/Hirai0827/items/bc0224393cbfa1551e8a)


# `ToVector3` について

- LeapMotionから得られる座標値は右手系
- Unityの世界座標は左手系

という違いがあったので、z座標の正負を反転させています。


# `TakeUntil` について

手がグーじゃなくなったときにバッファを破棄するために、 `TakeUntil` を使っています。
この処理をしないと、グーじゃなくなった直前の手の位置がバッファに保持され、次に手をグーにしたときに、一気にカメラが動きます。

こちらの記事を参考にさせていただきました。
[【UniRx】ピンチイン・アウトをちゃちゃっと作る](https://qiita.com/Ryopon/items/2ae4977a7b43a7eb8e29)


# さいごに
両手を使えばピンチイン・アウトとかもできそうですね！やってみようと思います。

**2019年4月22日追記**
両手編を書きました！
[【LeapMotion+UniRx】手の動きでカメラを動かす：両手編](https://qiita.com/segur/items/4d9947db66d70def9762)