6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

UnityAdvent Calendar 2019

Day 25

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

Last updated at Posted at 2019-12-24

これは 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 の勉強をできたのが収穫です。

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

6
5
0

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
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?