のしメモ アプリ開発ブログ

Unityアプリとかロボットとか作ってるときに困ったこととかメモ

VRMをランタイムで読み込んでVRIKをアタッチさせる

VR向けアバターフォーマット「VRM」をランライムで読み込んで、その後VRIKにアタッチさせて動かしたかったので、手順をメモ。

VRM
VRM - dwango on GitHub

準備

VRM

UniVRM-0.40.unitypackageをインポートしておく
https://github.com/dwango/UniVRM/releases

VRIK

VRIKが必要なのでFinal IKをインポートしておく
Final IK - Asset Store

VRMモデル

下記のリンクからVRMモデルをダウンロードして、UnityのWWWクラスで取得できるパスに置いておく
「ニコニ立体ちゃん (VRM)」 / ニコニ立体 さんの作品 - ニコニ立体

実装

1. VRMをランタイムで読み込む

githubにあるRuntimeSampleLoader.unitypackageを参考に読み込み処理を実装
https://github.com/dwango/UniVRM/releases

2. VRIKをアタッチさせる

var vrIK = avatar.AddComponent<VRIK>();

3. VRIKのリファレンスを設定させる

vrIK.AutoDetectReferences();

4. エラーがでるので解除

今のままだと下記のエラーがでる

NullReferenceException: Object reference not set to an instance of an object
RootMotion.FinalIK.IKSolverVR+Arm.Stretching () (at Assets/Plugins/RootMotion/FinalIK/IK Solvers/IKSolverVRArm.cs:220)

初期化処理を入れておく

// NullReferenceエラーがでるので初期化しておく
vrIK.solver.leftArm.stretchCurve = new AnimationCurve ();
vrIK.solver.rightArm.stretchCurve = new AnimationCurve ();

5. 頭、左手、右手のターゲットを指定

vrIK.solver.spine.headTarget = headTarget.transform;
vrIK.solver.leftArm.target = leftHandTarget.transform;
vrIK.solver.rightArm.target = rightHandTarget.transform; 

6. その他微調整(任意)

// 歩幅の設定
vrIK.solver.locomotion.footDistance = 0.1f;

ランタイムでVRIKが設定できた!


(headTarget, leftHandTarget, rightHandTargetを動かすとモデルも動きます。)

最終的なスクリプト

VRMRuntimeLoader.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using VRM;
using RootMotion.FinalIK;

namespace ViRD.Samples{
    public class VRMRuntimeLoader : MonoBehaviour
    {
        void Start()
        {
            // 読み込むURLを指定
            var path = "http://hogehoge/AliciaSolid.vrm";

            StartCoroutine(LoadVrmCoroutine(path, go =>
            {
                // ターゲットを仮作成
                var headTarget = new GameObject();
                var leftHandTarget = new GameObject();
                var rightHandTarget = new GameObject();
                headTarget.transform.position = new Vector3(0f, 1.5f, 0f);
                leftHandTarget.transform.position = new Vector3(-0.5f, 0.8f, 0f);
                rightHandTarget.transform.position = new Vector3(0.5f, 0.8f, 0f);

                // VRIKのセットアップ
                SetupVRIK(go, headTarget, leftHandTarget, rightHandTarget);
            }));
        }

        IEnumerator LoadVrmCoroutine(string path, Action<GameObject> onLoaded)
        {
            var www = new WWW(path);
            yield return www;

            if(!string.IsNullOrEmpty(www.error))
            {
                Debug.LogError(www.error);
                yield break;
            }
            
            VRMImporter.LoadVrmAsync(www.bytes, onLoaded);
        }

        void SetupVRIK(GameObject avatar, GameObject headTarget, GameObject leftHandTarget, GameObject rightHandTarget){

                // VRIKを設定
                var vrIK = avatar.AddComponent<VRIK>();

                // リファレンス紐付け
                vrIK.AutoDetectReferences();

                // NullReferenceエラーがでるので初期化しておく
                vrIK.solver.leftArm.stretchCurve = new AnimationCurve ();
                vrIK.solver.rightArm.stretchCurve = new AnimationCurve ();

                // 頭や腕のターゲット設定
                vrIK.solver.spine.headTarget = headTarget.transform;
                vrIK.solver.leftArm.target = leftHandTarget.transform;
                vrIK.solver.rightArm.target = rightHandTarget.transform; 

                // 歩幅の設定            
                vrIK.solver.locomotion.footDistance = 0.1f;
        }
    }
}