本記事はVTuber Tech #1 Advent Calendar 2019の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でかわいいやつを見つけたのでそれを使います
必要なものをダウンロードする
Unityのインストールは飛ばします
UniVRM
ReleaseページからUnityPackageをダウンロードします。
FinalIK
FinalIK
UnityAssetStoreにあります。有料なので買ってください。
JoyconLib
1.JoycobLibのリポジトリに行きます
2.「Clone or download」ボタンを押して「Download ZIP」を選択します
3.ダウンロードした「JoyconLib-master.zip」を展開しておきます
4.必要な別のファイルを持ってくるためここに行きます
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を持っていきます
4.モデルのInspectorからAddComponent→VRIKをアタッチ
5.さっき作ったCubeをVRIKのSolver→Spine→HeadTargetに投げ入れます
6.CubeのMeshRendererのチェックを外しておきます
7.実行して、Scene ViewでCubeの角度を変えてみると頭がそれに追従して動きます!
JoyconRを使って頭を制御する
1.Create→CreateEmptyでJoycon制御用のObjectを作ります。
2.JoyConLib_scriptsの中のJoyConManagerをアタッチします。
3.AddComponent→NewScriptから新しいScript「JoyConController」を作ります。
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で角度をとると、ずれてくるので、手動でまっすぐにできるようにしておきます
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);
}
}
完成
完成したものがこちら
Nintendo SwitchのJoyConだけでVtuberのトラッキングしてみた。意外とそれっぽい pic.twitter.com/UjQcgc1FX1
— 🦑イカ焼き🔥 (@Ikayaki_tail) December 22, 2019
最終的に完成したスクリプト
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);
}
}
おわりに
適当に作った割にはいいものができた。嬉しい。