NintendoSwitchのJoy-conを使用して簡単な触覚インターフェースを実現しよう

これは[UT-virtual Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ut-virtual)の12月15日を担当するとなって書き上げたものです。

 
#はじめに
この記事で扱うものは筆者がコガネブログさんの記事
[【Unity】Nintendo Switch の Joy-Con のジャイロ・加速度・傾きの値を取得したり、振動させたりすることができる「JoyconLib」紹介](http://babas.hatenablog.com/entry/2017/11/12/090000)
を読み記事で紹介されたJoyconLib等を使用して実装したものです。今回はUnityで触覚インターフェースとしてJoy-conを使う方法とJoy-conの振動原理について書こうと思います。

**注意**：この記事ではJoy-conのキー入力を取得する方法は一部分しか載せません。知りたい方は上記の記事を読んでください。またここに載せたJoyconLibを使う場合はライセンス等[^1]の一読を強く推奨します。

#Joy-conの振動原理
いろいろな記事等を調べてみるとJoy-conにはALPSのハプティックリアクタが採用されているらしくこれは320Hzと160Hzの二つの共振方向によって様々な振動を与えることができるらしいです。実のところ僕自身こういった振動についてあまり詳しくなく、これぐらいのことしかわらないです。




#使用するもの
- Unity[^2]
- JoyconLib
- Joy-con

#Joy-conとパソコンの接続
今回はJoy-conとパソコンとの接続はBluetoothを用います。ご自身の使用しているパソコンによって接続の仕方が違いますが、Windowsでの設定方法については**[ここ](http://baba-s.hatenablog.com/entry/2017/11/12/160534)**の記事を参考にした方がいいでしょう。とてもわかりやすく書いてあります。
macはあまり触ったことがないのですが、調べてみるとBluetoothを検知可能にしてその状態で`システム環境設定 → Bluetoothの接続`で設定をすればいいみたいです。

#JoyconLibを導入しよう！
1. ダウンロードまで
2. Unityへ

##ダウンロードまで
下記の各URLにアクセスし、上のURLでJoyconLib-master.zipを、下のURLでUnity-Wiimote-master.zipをダウンロード[^3]します。
[https://github.com/Looking-Glass/JoyconLib](https://github.com/Looking-Glass/JoyconLib)
[https://github.com/Flafla2/Unity-Wiimote]
(https://github.com/Flafla2/Unity-Wiimote)

以下のようになっていれば大丈夫です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/309104/436e2e3d-8554-9df1-35dc-6223801f6e6b.png)

##Unityへ
ダウンロード出来たらそれを展開しておきます。
次にUnityプロジェクトを開き、`JoyconLib-master/Assets`フォルダにある`JoyconLib_scripts`と`Unity-Wiimote-master/Assets/Wiimote/Plugins/win64`フォルダにある`hidapi.dll`をドラッグアンドドロップで追加します。

以下のようになっていれば大丈夫です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/309104/f47da298-efb4-f971-74e2-fa3a92d180a6.png)

#Joy-conの振動機能を使いこなそう
1. 動かせるようになるまで
2. 解説
    1. コードについて
    2. パラメータについて

##1. 動かせるようになるまで
プロジェクトへ導入ができたら次はスクリプトを書いて実際に動かしてみましょう。プロジェクトにJoyconRumbleという名前のスクリプトを作成し以下のように書いてください。

```JoyconRumble.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class JoyconRumble : MonoBehaviour
{
    private static readonly Joycon.Button[] m_buttons =
        Enum.GetValues(typeof(Joycon.Button)) as Joycon.Button[];

    private List<Joycon> m_joycons;
    private Joycon m_joyconL;
    private Joycon m_joyconR;
    private Joycon.Button? m_pressedButtonL;
    private Joycon.Button? m_pressedButtonR;

    private void Start()
    {
        m_joycons = JoyconManager.Instance.j;

        if (m_joycons == null || m_joycons.Count <= 0) return;

        m_joyconL = m_joycons.Find(c => c.isLeft);
        m_joyconR = m_joycons.Find(c => !c.isLeft);
    }

    private void Update()
    {
        m_pressedButtonL = null;
        m_pressedButtonR = null;

        if (m_joycons == null || m_joycons.Count <= 0) return;

        foreach (var button in m_buttons)
        {
            if (m_joyconL.GetButton(button))
            {
                m_pressedButtonL = button;
            }
            if (m_joyconR.GetButton(button))
            {
                m_pressedButtonR = button;
            }
        }

        if (m_joyconL.GetButtonDown(Joycon.Button.DPAD_UP))
        {
            m_joyconL.SetRumble(160, 320, 0.6f, 200);
        }
        if (m_joyconR.GetButtonDown(Joycon.Button.DPAD_UP))
        {
            m_joyconR.SetRumble(160, 320, 0.6f, 200);
        }
    }
    private void OnGUI()
    {
        var style = GUI.skin.GetStyle("label");
        style.fontSize = 24;

        if (m_joycons == null || m_joycons.Count <= 0)
        {
            GUILayout.Label("Joy-Con が接続されていません");
            return;
        }

        if (!m_joycons.Any(c => c.isLeft))
        {
            GUILayout.Label("Joy-Con (L) が接続されていません");
            return;
        }

        if (!m_joycons.Any(c => !c.isLeft))
        {
            GUILayout.Label("Joy-Con (R) が接続されていません");
            return;
        }

        GUILayout.BeginHorizontal(GUILayout.Width(960));

        foreach (var joycon in m_joycons)
        {
            var isLeft = joycon.isLeft;
            var name = isLeft ? "Joy-Con (L)" : "Joy-Con (R)";
            var button = isLeft ? m_pressedButtonL : m_pressedButtonR;

            GUILayout.BeginVertical(GUILayout.Width(480));
            GUILayout.Label("押されているボタン：" + button);
            GUILayout.EndVertical();
        }

        GUILayout.EndHorizontal();
    }
}


```
できたらシーンに空のオブジェクトを作ってそれに`JoyconRumble.cs`と`JoyconLib_script`フォルダ内にある`JoyconManeger.cs`をアタッチします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/309104/6b3f802f-2b69-91e2-2eb2-2fc1032667e8.png)
できたら動かしてみましょうJoy-conとのbluetooth接続がうまくいっていたら自身の押しているボタンが表示され、コントロ―ラの上ボタンで振動が確認できます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/309104/b58bd1e9-4157-2e5c-3370-3fb71f0dc130.png)


##2. 解説
###2-1. コードについて
今からはコードの解説をしていきます。`OnGUI`関数内は表示するための部分なので気になった方は調べてみましょう。今回は触れません。

```cs

private void Start()
    {
        m_joycons = JoyconManager.Instance.j;

        if (m_joycons == null || m_joycons.Count <= 0) return;

        m_joyconL = m_joycons.Find(c => c.isLeft);
        m_joyconR = m_joycons.Find(c => !c.isLeft);
    }
```
上記のstart関数内ではJoyconのインスタンスを生成し、Joyconが生成できているなら左右のコントロ―ラを検出しています。

```cs
private void Update()
    {
        m_pressedButtonL = null;
        m_pressedButtonR = null;

        if (m_joycons == null || m_joycons.Count <= 0) return;

        foreach (var button in m_buttons)
        {
            if (m_joyconL.GetButton(button))
            {
                m_pressedButtonL = button;
            }
            if (m_joyconR.GetButton(button))
            {
                m_pressedButtonR = button;
            }
        }

        if (m_joyconL.GetButtonDown(Joycon.Button.DPAD_UP))
        {
            m_joyconL.SetRumble(160, 320, 0.6f, 200);
        }
        if (m_joyconR.GetButtonDown(Joycon.Button.DPAD_UP))
        {
            m_joyconR.SetRumble(160, 320, 0.6f, 200);
        }
    }
```
こちらのUpdate関数内ではJoyconを検出できた場合ボタンが押されたときそのボタンが何ボタンか判別し、それが特定のボタンであったとき振動をするようにしています。つまりもしオブジェクトとの衝突などで振動を引き起こしたかったら、最後のif文の条件式を`OnCollsionEnter`関数や`OnTriggerEnter`関数とかに変更するといいと思います。

ここで振動をさせるための関数`SetRumble()`についてみていこうと思っています。JoyconLibを導入するときにおかれた`Joycon.cs`をみると

```cs
 public void SetRumble(float low_freq, float high_freq, float amp, int time = 0)
    {
        if (state <= Joycon.state_.ATTACHED) return;
		if (rumble_obj.timed_rumble == false || rumble_obj.t < 0)
        {
            rumble_obj = new Rumble(low_freq, high_freq, amp, time);
        }
    }
```
とあります。これから引数は左から**低い方の周波数、高い方の周波数、振幅、周期**みたいです。ですので一つ目には160Hz、二つ目には320Hzが入っていますね。そして重要なのは`new Rumble(low_freq, high_freq, amp, time);`でインスタンスが生成されているところです。これによって振動を起こすオブジェクトが生成されているみたいですね。

###2-2. パラメータについて
では実際にどういった値を入れるのが適切かですが、まず160と320の二つは振動について詳しくない方はいじらない方がいいと思われます。そしてそれ以降の値については振動についての知見がある人はご自由にしていただき、ない人は実験みたいに何度も試してみるのがいいと思います。

気づかれた方もいらっしゃるかもしれませんがこのJoyconLibを使用した振動を起こす方法では二つの周波数で別々に振幅と周期を設定することができないようで、Swithのゲームのようにとてもリアルな触覚を再現するには与えるパラメータの値等の工夫する必要があるみたいです。なのでこの方法では自身の体にオブジェクトが当たったなどの簡単な触覚の提示等に適しているかと思います。

#最後に
今回はJoyconを使った触覚インターフェースについて書きました。振動等を使って触覚を再現するすることはこれからより増えていくことだと思います。（実際にHTC Viveのコントロ―ラにもHD振動の機構がある。）

ぜひ取り組んでみてはいかがでしょう！(スマブラsp買った人ならSwitch持ってますよね？)

あとJoyconLibについて僕自身も理解しきれていないのでもしチョットワカル方がいらっしゃれば教えてください。

#
[^1]: JoyconLibのGitHubページ内にあるLICENSEとREADME.mdあたりを読んで置いたらいいと思います。
[^2]: もし自身の環境でできなかったらバージョンを2018.2.9f1あたりでもう一度やってみてください。
[^3]: 最初の画面で`Clone or download`ボタンを押すと`Open in Desktop`と`Download Zip`の二つがありますが後の方を選べばダウンロードできます。