NitendoSwich Joycon + FinalIKで簡単にVTuberを作ってみる

本記事は[VTuber Tech #1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/vtuber)の23日目の記事です。
Vtuberハッカソンでやったことを書こうと思っていたのですが、プレミアム大会がまだ終わってないので、別のことを書きます。

# 準備
準備するもの

- Unity
- UniVRM
- FinalIK
- JoyconLib
- 任意のVRMファイル
- NintendoSwitchのJoy-con

## Unity
Qiitaを見るような人に説明は不要
今回使ったバージョンは2019.2.9f1
## UniVRM
VRMをUnityで動かすためのあれこれ

## FinalIK
Humanoidをいいかんじに制御するAsset

## JoyconLib
UnityでJoyCon使うためのやつ

## 任意のVRMファイル
[VroidHub](https://hub.vroid.com/)でかわいいやつを見つけたのでそれを使います
![守りたいこの笑顔](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309511/d69f6855-f672-4ab7-e251-d9522f660cd7.png)

[使ったモデル](https://hub.vroid.com/characters/8203905518842731822/models/7213026045466553548)

# 必要なものをダウンロードする
Unityのインストールは飛ばします

## UniVRM
[Releaseページ](https://github.com/vrm-c/UniVRM/releases)からUnityPackageをダウンロードします。

## FinalIK
[FinalIK](https://assetstore.unity.com/packages/tools/animation/final-ik-14290)
UnityAssetStoreにあります。有料なので買ってください。

## JoyconLib
1.[JoycobLib](https://github.com/Looking-Glass/JoyconLib)のリポジトリに行きます
2.「Clone or download」ボタンを押して「Download ZIP」を選択します
3.ダウンロードした「JoyconLib-master.zip」を展開しておきます
4.必要な別のファイルを持ってくるため[ここ](https://github.com/Flafla2/Unity-Wiimote)に行きます
5.「Clone or download」ボタンを押して「Download ZIP」を選択します
6.ダウンロードした「Unity-Wiimote-master.zip」を展開しておきます

# 使うAsset、オブジェクトを配置する
## VRM,UniVRM
1.UniVRMのUnityPackageをUnityEditor上に放り込みます。
2.VRMファイルをUnityのプロジェクトに追加すると、勝手にPrefabファイルが生成されます。

## FinalIK
FinalIKをAssetStoreからImportしておきます

## JoyconLib
「JoyconLib-master/Assets」フォルダ内の「JoyconLib_scripts」フォルダをUnityプロジェクトに追加します
「Unity-Wiimote-master/Assets/Wiimote/Plugins/win64フォルダ内の「hidapi.dll」を Unity プロジェクトに追加します

## JoyCon
JoyconをBluetoothでパソコンにつないでおきます

# FinalIKで頭を動かす
1.VRMモデルのPrefabをSceneに追加します。
2.Create→3DObject→CubeでCubeをSceneに追加します
3.頭の位置にCubeを持っていきます
![Cube](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309511/c2f568d8-be4e-bbdf-b6d1-792d4a83bdb4.png)
4.モデルのInspectorからAddComponent→VRIKをアタッチ
5.さっき作ったCubeをVRIKのSolver→Spine→HeadTargetに投げ入れます
![VRIK](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309511/7f835e54-21a4-dfc8-2fe3-d2ac992a1757.png)
6.CubeのMeshRendererのチェックを外しておきます
7.実行して、Scene ViewでCubeの角度を変えてみると頭がそれに追従して動きます！
![首が動くよ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309511/6a86ed90-e751-4925-2bd8-c4f0174f87b0.png)


# JoyconRを使って頭を制御する
1.Create→CreateEmptyでJoycon制御用のObjectを作ります。
2.JoyConLib_scriptsの中のJoyConManagerをアタッチします。
3.AddComponent→NewScriptから新しいScript「JoyConController」を作ります。

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

public class JoyConController : MonoBehaviour
{
    [SerializeField]
    private GameObject targetObject;

    private Joycon m_joyconR;

    void Start()
    {
        SetControllers();
    }
    
    void Update()
    {
        if (m_joyconR == null) return;

        rightProcess();
    }

    private void rightProcess()
    {
        Vector3 gyro = m_joyconR.GetGyro();
        Vector3 angle = targetObject.transform.rotation.eulerAngles + new Vector3(-gyro.y, gyro.z, -gyro.x) * 0.5f;
        targetObject.transform.rotation = Quaternion.Euler(angle);
    }

    private void SetControllers()
    {
        List<Joycon> joycons = JoyconManager.Instance.j;
        if (joycons == null || joycons.Count <= 0) return;
        m_joyconR = joycons.Find(c => !c.isLeft);
    }
}

```
JoyConControllerのTargetObjectにさっき作ったCubeを入れます。

頭にJoyconRをどうにか固定して、動かしてみましょう

# JoyconLを使ってズレを補正する
Joyconのgyroで角度をとると、ずれてくるので、手動でまっすぐにできるようにしておきます

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

public class JoyConController : MonoBehaviour
{
    [SerializeField]
    private GameObject targetObject;

    //staticとして定義しておく
    static Quaternion ZeroQuaternion = Quaternion.Euler(Vector3.zero);

    private Joycon m_joyconL;
    private Joycon m_joyconR;

    private bool isLerpControl = false;
    private float lerpTime;
    private Quaternion beforeQuaternion;


    void Start()
    {
        SetControllers();
    }
    
    void Update()
    {
        if (m_joyconL == null || m_joyconR == null) return;

        //JoyconLの処理は常に動かしておく
        leftProcess();

        //Lerpコントロール中はJoyconRでの制御はしない
        if (isLerpControl)
        {
            lerpProcess();
        }
        else
        {
            rightProcess();
        }
    }

    private void lerpProcess()
    {
        lerpTime += Time.deltaTime * 2.0f;

        if(lerpTime >= 1.0f)
        {
            lerpTime = 1.0f;
            isLerpControl = false;
        }

        targetObject.transform.rotation = Quaternion.Lerp(beforeQuaternion, ZeroQuaternion, lerpTime);
    }

    private void leftProcess()
    {
        if (m_joyconL.GetButtonDown(Joycon.Button.SHOULDER_1)) //L1が押されたとき
        {
            beforeQuaternion = targetObject.transform.rotation;
            isLerpControl = true;
            lerpTime = 0.0f;
        }
    }

    private void rightProcess()
    {
        //省略
    }

    private void SetControllers()
    {
        List<Joycon> joycons = JoyconManager.Instance.j;
        if (joycons == null || joycons.Count <= 0) return;
        m_joyconL = joycons.Find(c => c.isLeft);
        m_joyconR = joycons.Find(c => !c.isLeft);
    }
}

```


# 完成
## 完成したものがこちら
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Nintendo SwitchのJoyConだけでVtuberのトラッキングしてみた。意外とそれっぽい <a href="https://t.co/UjQcgc1FX1">pic.twitter.com/UjQcgc1FX1</a></p>&mdash; 🦑イカ焼き🔥 (@Ikayaki_tail) <a href="https://twitter.com/Ikayaki_tail/status/1208747354347560960?ref_src=twsrc%5Etfw">December 22, 2019</a></blockquote>


## 最終的に完成したスクリプト
```JoyConController.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class JoyConController : MonoBehaviour
{
    [SerializeField]
    private GameObject targetObject;

    static Quaternion ZeroQuaternion = Quaternion.Euler(Vector3.zero);

    private Joycon m_joyconL;
    private Joycon m_joyconR;

    private bool isLerpControl = false;
    private float lerpTime;
    private Quaternion beforeQuaternion;


    void Start()
    {
        SetControllers();
    }
    
    void Update()
    {
        if (m_joyconL == null || m_joyconR == null) return;

        leftProcess();

        if (isLerpControl)
        {
            lerpProcess();
        }
        else
        {
            rightProcess();
        }
    }

    private void lerpProcess()
    {
        lerpTime += Time.deltaTime * 2.0f;

        if(lerpTime >= 1.0f)
        {
            lerpTime = 1.0f;
            isLerpControl = false;
        }

        targetObject.transform.rotation = Quaternion.Lerp(beforeQuaternion, ZeroQuaternion, lerpTime);
    }

    private void leftProcess()
    {
        if (m_joyconL.GetButtonDown(Joycon.Button.SHOULDER_1))
        {
            beforeQuaternion = targetObject.transform.rotation;
            isLerpControl = true;
            lerpTime = 0.0f;
        }
    }

    private void rightProcess()
    {
        Vector3 gyro = m_joyconR.GetGyro();

        Vector3 angle = targetObject.transform.rotation.eulerAngles + new Vector3(-gyro.y, gyro.z, -gyro.x) * 0.5f;

        targetObject.transform.rotation = Quaternion.Euler(angle);
    }

    private void SetControllers()
    {
        List<Joycon> joycons = JoyconManager.Instance.j;
        if (joycons == null || joycons.Count <= 0) return;
        m_joyconL = joycons.Find(c => c.isLeft);
        m_joyconR = joycons.Find(c => !c.isLeft);
    }
}

```

# おわりに
適当に作った割にはいいものができた。嬉しい。