これは 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#で動かしてみます!
最終的にはこんな感じのアニメーションが作れました。
動作環境
以下の環境でやりました。
- Windows 10 64bit
- Unity 2019.2.11f1
- UniVRM 0.54.0
初期状態
とりあえずVRMファイルをシーンに読み込んでみます。
今回は 今里尚吾くんにご協力いただきます!(VRoid Studioで作りました!)
Hierarchyを見てみたらこんな感じでした!
股関節を30度に曲げてみる
まずは、股関節を曲げてみましょう。
以下のスクリプトを作成します。
usingSystem;usingUnityEngine;publicclassPendulumRunning:MonoBehaviour{// transformを保管する変数privateTransformcHips;privateTransformlUpperLeg;privateTransformrUpperLeg;voidStart(){// 腰の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");}voidFixedUpdate(){// 脚を30°傾けるlUpperLeg.rotation=Quaternion.AngleAxis(-30.0f,Vector3.right);rUpperLeg.rotation=Quaternion.AngleAxis(30.0f,Vector3.right);}}
作成できたら、シーン内のVRMにアタッチします。
脚が前後に開きました!
振り子のように足を揺らしてみる
せっかくなので、アニメーションさせたいですよね。
ということで、振り子のように揺らしてみます!
voidFixedUpdate(){// 1秒周期の振り子を用意するfloatpendulum=(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);}
いいかんじ!
膝を曲げてみる
膝も曲げてみたくなったので、 LowerLeg
を追加してみました。あと係数とかを微調整したのがこちらです。
publicclassPendulumRunning:MonoBehaviour{privateTransformcHips;privateTransformlUpperLeg;privateTransformrUpperLeg;privateTransformlLowerLeg;privateTransformrLowerLeg;voidStart(){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");}voidFixedUpdate(){floatpendulum=(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);}}
膝も曲がりました。
全身動かしてみる
同じ要領で、全身の関節の動かしてみます。色々調整したら、以下のようなコードになりました。
publicclassPendulumRunning:MonoBehaviour{privateTransformcHips;privateTransformlUpperLeg;privateTransformlLowerLeg;privateTransformrUpperLeg;privateTransformrLowerLeg;privateTransformcSpine;privateTransformcChest;privateTransformcUpperChest;privateTransformlShoulder;privateTransformlUpperArm;privateTransformlLowerArm;privateTransformrShoulder;privateTransformrUpperArm;privateTransformrLowerArm;voidStart(){// 全身の関節の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");}voidFixedUpdate(){// 速度を3倍に変更floatpendulum=(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);}}
だんだんそれっぽくなってきました。
ただ、重心が上下しないのはちょっと違和感がありますね。
重心を上下させてみた
重心も上下させてみます。
publicclassPendulumRunning:MonoBehaviour{privateTransformcHips;privateTransformlUpperLeg;privateTransformlLowerLeg;privateTransformrUpperLeg;privateTransformrLowerLeg;privateTransformcSpine;privateTransformcChest;privateTransformcUpperChest;privateTransformlShoulder;privateTransformlUpperArm;privateTransformlLowerArm;privateTransformrShoulder;privateTransformrUpperArm;privateTransformrLowerArm;// 腰の初期位置を保管する変数privateVector3firstHipsPosition;voidStart(){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;}voidFixedUpdate(){floatpendulum=(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);// 周期が半分の振り子を用意するfloathalfPendulum=(float)Math.Sin(Time.time*Math.PI*3.0f*2.0f);// 腰の位置を上下させるcHips.localPosition=firstHipsPosition+newVector3(0.0f,0.04f*halfPendulum,0.0f);}}
ちょ、ちょっとだけ、マシになったかな?
ゲシュタルト崩壊してきたので、このあたりで止めておきます。
一応、AnimationCurveは使わずにC#だけでアニメーションを作成することができました!
AnimationClipに保存してみる
せっかくだからAnimationClipに保存してみます。
Unityの GameObjectRecorderというAPIを使えば、シーン実行中のアニメーションをAnimationClipに保存できます。
以下のスクリプトを作成し、シーン内のVRMにアタッチしてください。
usingUnityEngine;usingUnityEditor.Animations;publicclassRecordTransformHierarchy:MonoBehaviour{publicAnimationClipclip;privateGameObjectRecorderm_Recorder;voidStart(){m_Recorder=newGameObjectRecorder(gameObject);m_Recorder.BindComponentsOfType<Transform>(gameObject,true);}voidLateUpdate(){if(clip==null){return;}m_Recorder.TakeSnapshot(Time.deltaTime);}voidOnDisable(){if(clip==null){return;}if(m_Recorder.isRecording){m_Recorder.SaveToClip(clip);}}}
適当なフォルダーに空のAnimationClipファイルを作成します。
ここでは仮に pendulum-running
というファイル名にします。
これを RecordTransformHierarchy
の Clip
に割り当てます。
これでシーンを実行すれば、AnimationClipに動きが保管されます!
FBX Exporter等を使えば、FBXに変換することもできますね!
さいごに
C#でもAnimationClipを作成することができました!
そのことに、果たして意味があるかはわかりませんが、個人的には Quaternion
の勉強をできたのが収穫です。
本記事作成にあたり、以下の記事を参考にさせていただきました。ありがとうございました。