LoginSignup
5

More than 3 years have passed since last update.

posted at

updated at

Organization

AnimationCurveを使わずにVRMをC#で走らせてみる【Unity】

これは Unity Advent Calendar 2019 の25日目の記事です。
昨日は @youri_ssk さんによる 2.5Dキャラクターアニメーション - Mirror Animation Playable でした。

Animation Curveを使わずにC#だけでVRMを走らせてみる

Animation Curveを使えば、便利なGUIで3Dアニメーションを作ることができます!

そう!普通の人ならAnimation Curveを使いましょう!!(もちろん、BlenderとかUnity外のツールでもいいです)

しかし!Qiita読者の皆さんは プログラマー なんです!

プログラマーだったらプログラミングで3Dアニメーションを作りたいですよね!

というわけで、VRMアバターをC#で動かしてみます!

最終的にはこんな感じのアニメーションが作れました。

201912241856a.gif

動作環境

以下の環境でやりました。

  • Windows 10 64bit
  • Unity 2019.2.11f1
  • UniVRM 0.54.0

初期状態

とりあえずVRMファイルをシーンに読み込んでみます。

今回は 今里尚吾 くんにご協力いただきます!(VRoid Studioで作りました!)

image.png

Hierarchyを見てみたらこんな感じでした!

image.png

股関節を30度に曲げてみる

まずは、股関節を曲げてみましょう。

以下のスクリプトを作成します。

using System;
using UnityEngine;

public class PendulumRunning : MonoBehaviour
{
    // transformを保管する変数
    private Transform cHips;
    private Transform lUpperLeg;
    private Transform rUpperLeg;

    void Start()
    {
        // 腰のtransformを取得
        cHips = transform.Find("Root")
                        .Find("J_Bip_C_Hips");

        // 股関節のtransformを取得
        lUpperLeg = cHips.Find("J_Bip_L_UpperLeg");
        rUpperLeg = cHips.Find("J_Bip_R_UpperLeg");
    }

    void FixedUpdate()
    {
        // 脚を30°傾ける
        lUpperLeg.rotation = Quaternion.AngleAxis(-30.0f, Vector3.right);
        rUpperLeg.rotation = Quaternion.AngleAxis(30.0f, Vector3.right);
    }
}

作成できたら、シーン内のVRMにアタッチします。

image.png

脚が前後に開きました!

image.png

image.png

振り子のように足を揺らしてみる

せっかくなので、アニメーションさせたいですよね。

ということで、振り子のように揺らしてみます!

    void FixedUpdate()
    {
        // 1秒周期の振り子を用意する
        float pendulum = (float)Math.Sin(Time.time * Math.PI);

        // 股関節を右軸(x軸)を中心に±60°幅で揺らす
        lUpperLeg.localRotation = Quaternion.AngleAxis(-60.0f * pendulum, Vector3.right);
        rUpperLeg.localRotation = Quaternion.AngleAxis(60.0f * pendulum, Vector3.right);
    }

201912241743a.gif

いいかんじ!

膝を曲げてみる

膝も曲げてみたくなったので、 LowerLeg を追加してみました。あと係数とかを微調整したのがこちらです。

public class PendulumRunning : MonoBehaviour
{
    private Transform cHips;
    private Transform lUpperLeg;
    private Transform rUpperLeg;
    private Transform lLowerLeg;
    private Transform rLowerLeg;

    void Start()
    {
        cHips = transform.Find("Root")
                        .Find("J_Bip_C_Hips");
        lUpperLeg = cHips.Find("J_Bip_L_UpperLeg");
        rUpperLeg = cHips.Find("J_Bip_R_UpperLeg");

        // 膝のtransformを取得
        lLowerLeg = lUpperLeg.Find("J_Bip_L_LowerLeg");
        rLowerLeg = rUpperLeg.Find("J_Bip_R_LowerLeg");
    }

    void FixedUpdate()
    {
        float pendulum = (float)Math.Sin(Time.time * Math.PI);

        // 股関節の動きを少し変更
        lUpperLeg.localRotation = Quaternion.AngleAxis(-60.0f * pendulum - 20.0f, Vector3.right);
        rUpperLeg.localRotation = Quaternion.AngleAxis(60.0f * pendulum - 20.0f, Vector3.right);

        // 膝を揺らす
        lLowerLeg.localRotation = Quaternion.AngleAxis(-60.0f * pendulum + 60.0f, Vector3.right);
        rLowerLeg.localRotation = Quaternion.AngleAxis(60.0f * pendulum + 60.0f, Vector3.right);
    }
}

201912241818a.gif

膝も曲がりました。

全身動かしてみる

同じ要領で、全身の関節の動かしてみます。色々調整したら、以下のようなコードになりました。

public class PendulumRunning : MonoBehaviour
{
    private Transform cHips;
    private Transform lUpperLeg;
    private Transform lLowerLeg;
    private Transform rUpperLeg;
    private Transform rLowerLeg;
    private Transform cSpine;
    private Transform cChest;
    private Transform cUpperChest;
    private Transform lShoulder;
    private Transform lUpperArm;
    private Transform lLowerArm;
    private Transform rShoulder;
    private Transform rUpperArm;
    private Transform rLowerArm;


    void Start()
    {
        // 全身の関節のtransformを取得
        cHips = transform.Find("Root")
                        .Find("J_Bip_C_Hips");
        lUpperLeg = cHips.Find("J_Bip_L_UpperLeg");
        lLowerLeg = lUpperLeg.Find("J_Bip_L_LowerLeg");
        rUpperLeg = cHips.Find("J_Bip_R_UpperLeg");
        rLowerLeg = rUpperLeg.Find("J_Bip_R_LowerLeg");
        cSpine = cHips.Find("J_Bip_C_Spine");
        cChest = cSpine.Find("J_Bip_C_Chest");
        cUpperChest = cChest.Find("J_Bip_C_UpperChest");
        lShoulder = cUpperChest.Find("J_Bip_L_Shoulder");
        lUpperArm = lShoulder.Find("J_Bip_L_UpperArm");
        lLowerArm = lUpperArm.Find("J_Bip_L_LowerArm");
        rShoulder = cUpperChest.Find("J_Bip_R_Shoulder");
        rUpperArm = rShoulder.Find("J_Bip_R_UpperArm");
        rLowerArm = rUpperArm.Find("J_Bip_R_LowerArm");
    }

    void FixedUpdate()
    {
        // 速度を3倍に変更
        float pendulum = (float)Math.Sin(Time.time * Math.PI * 3.0f);

        // 脚を揺らす        
        lUpperLeg.localRotation = Quaternion.AngleAxis(-60.0f * pendulum - 20.0f, Vector3.right);
        rUpperLeg.localRotation = Quaternion.AngleAxis(60.0f * pendulum - 20.0f, Vector3.right);
        lLowerLeg.localRotation = Quaternion.AngleAxis(-30.0f * pendulum + 60.0f, Vector3.right);
        rLowerLeg.localRotation = Quaternion.AngleAxis(30.0f * pendulum + 60.0f, Vector3.right);

        // 腰にひねりを加える
        cHips.localRotation = Quaternion.AngleAxis(10.0f * pendulum, Vector3.up) * Quaternion.AngleAxis(10.0f, Vector3.right);

        // 胸は腰と反対にひねる
        cChest.localRotation = Quaternion.AngleAxis(-10.0f * pendulum, Vector3.up);
        cUpperChest.localRotation = Quaternion.AngleAxis(-20.0f * pendulum, Vector3.up);

        // 腕を揺らす
        lUpperArm.localRotation = Quaternion.AngleAxis(60.0f * pendulum + 30.0f, Vector3.right) * Quaternion.AngleAxis(70.0f, Vector3.forward);
        rUpperArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum + 30.0f, Vector3.right) * Quaternion.AngleAxis(-70.0f, Vector3.forward);
        lLowerArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum + 60.0f, Vector3.up);
        rLowerArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum - 60.0f, Vector3.up);
    }
}

201912241847a.gif

だんだんそれっぽくなってきました。

ただ、重心が上下しないのはちょっと違和感がありますね。

重心を上下させてみた

重心も上下させてみます。

public class PendulumRunning : MonoBehaviour
{
    private Transform cHips;
    private Transform lUpperLeg;
    private Transform lLowerLeg;
    private Transform rUpperLeg;
    private Transform rLowerLeg;
    private Transform cSpine;
    private Transform cChest;
    private Transform cUpperChest;
    private Transform lShoulder;
    private Transform lUpperArm;
    private Transform lLowerArm;
    private Transform rShoulder;
    private Transform rUpperArm;
    private Transform rLowerArm;

    // 腰の初期位置を保管する変数
    private Vector3 firstHipsPosition;


    void Start()
    {
        cHips = transform.Find("Root")
                        .Find("J_Bip_C_Hips");
        lUpperLeg = cHips.Find("J_Bip_L_UpperLeg");
        lLowerLeg = lUpperLeg.Find("J_Bip_L_LowerLeg");
        rUpperLeg = cHips.Find("J_Bip_R_UpperLeg");
        rLowerLeg = rUpperLeg.Find("J_Bip_R_LowerLeg");
        cSpine = cHips.Find("J_Bip_C_Spine");
        cChest = cSpine.Find("J_Bip_C_Chest");
        cUpperChest = cChest.Find("J_Bip_C_UpperChest");
        lShoulder = cUpperChest.Find("J_Bip_L_Shoulder");
        lUpperArm = lShoulder.Find("J_Bip_L_UpperArm");
        lLowerArm = lUpperArm.Find("J_Bip_L_LowerArm");
        rShoulder = cUpperChest.Find("J_Bip_R_Shoulder");
        rUpperArm = rShoulder.Find("J_Bip_R_UpperArm");
        rLowerArm = rUpperArm.Find("J_Bip_R_LowerArm");

        // 腰の初期値を取得する
        firstHipsPosition = cHips.localPosition;
    }

    void FixedUpdate()
    {
        float pendulum = (float)Math.Sin(Time.time * Math.PI * 3.0f);
        lUpperLeg.localRotation = Quaternion.AngleAxis(-60.0f * pendulum - 20.0f, Vector3.right);
        rUpperLeg.localRotation = Quaternion.AngleAxis(60.0f * pendulum - 20.0f, Vector3.right);
        lLowerLeg.localRotation = Quaternion.AngleAxis(-30.0f * pendulum + 60.0f, Vector3.right);
        rLowerLeg.localRotation = Quaternion.AngleAxis(30.0f * pendulum + 60.0f, Vector3.right);
        cHips.localRotation = Quaternion.AngleAxis(10.0f * pendulum, Vector3.up) * Quaternion.AngleAxis(10.0f, Vector3.right);
        cChest.localRotation = Quaternion.AngleAxis(-10.0f * pendulum, Vector3.up);
        cUpperChest.localRotation = Quaternion.AngleAxis(-20.0f * pendulum, Vector3.up);
        lUpperArm.localRotation = Quaternion.AngleAxis(60.0f * pendulum + 30.0f, Vector3.right) * Quaternion.AngleAxis(70.0f, Vector3.forward);
        rUpperArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum + 30.0f, Vector3.right) * Quaternion.AngleAxis(-70.0f, Vector3.forward);
        lLowerArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum + 60.0f, Vector3.up);
        rLowerArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum - 60.0f, Vector3.up);

        // 周期が半分の振り子を用意する
        float halfPendulum = (float)Math.Sin(Time.time * Math.PI * 3.0f * 2.0f);

        // 腰の位置を上下させる
        cHips.localPosition = firstHipsPosition + new Vector3(0.0f, 0.04f * halfPendulum, 0.0f);
    }
}

201912241856a.gif

ちょ、ちょっとだけ、マシになったかな?

ゲシュタルト崩壊してきたので、このあたりで止めておきます。

一応、AnimationCurveは使わずにC#だけでアニメーションを作成することができました!

AnimationClipに保存してみる

せっかくだからAnimationClipに保存してみます。

Unityの GameObjectRecorder というAPIを使えば、シーン実行中のアニメーションをAnimationClipに保存できます。

以下のスクリプトを作成し、シーン内のVRMにアタッチしてください。

using UnityEngine;
using UnityEditor.Animations;

public class RecordTransformHierarchy : MonoBehaviour
{
    public AnimationClip clip;

    private GameObjectRecorder m_Recorder;

    void Start()
    {
        m_Recorder = new GameObjectRecorder(gameObject);
        m_Recorder.BindComponentsOfType<Transform>(gameObject, true);
    }

    void LateUpdate()
    {
        if (clip == null){
            return;
        }
        m_Recorder.TakeSnapshot(Time.deltaTime);
    }

    void OnDisable()
    {
        if (clip == null){
            return;
        }

        if (m_Recorder.isRecording)
        {
            m_Recorder.SaveToClip(clip);
        }
    }
}

適当なフォルダーに空のAnimationClipファイルを作成します。

image.png

ここでは仮に pendulum-running というファイル名にします。

image.png

これを RecordTransformHierarchyClip に割り当てます。

image.png

これでシーンを実行すれば、AnimationClipに動きが保管されます!

FBX Exporter等を使えば、FBXに変換することもできますね!

さいごに

C#でもAnimationClipを作成することができました!

そのことに、果たして意味があるかはわかりませんが、個人的には Quaternion の勉強をできたのが収穫です。

本記事作成にあたり、以下の記事を参考にさせていただきました。ありがとうございました。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5