Unity5: Humanoidの骨格をC#プログラムで直接動かす


2017.08.02: created by
2017.08.03: revised by
2017.08.04: revised by
[Up] Japanese English

Humanoidの骨格を、各Boneの局所座標系の中でC#で直接動かす

実行時の動画 HumanoidRig.mp4



Unityの座標系

Unity の座標系は左手系です。 左手系の座標系では、各軸回りの回転は 「その軸の無限大から原点を見て時計回りが正方向」となります。




Humaonidの骨格

Unity の Humanoid (人型キャラクター)の骨格は階層化されたBoneから構成されています。 Mapping で「実線の外丸は必須Bone」「点線の外丸はオプショナルBone」を表わし、 塗り潰した丸は「このHumanoidにおいて割り当てられているBone」を表わします。



Humanoid の Bone を表現する型が Unity のHumanBodyBones であり、主なBoneとして以下の種類があります。 下の表には、各 Bone の Transform の親となる Bone も "Parent Bone" として明記しました。 また、各Bone のTransform における座標軸の向きを調査したので下図に示します。

Bone NameMandatoryParent Bone
Hipso-
SpineoHips
ChestSpine
UpperChestChest
NeckUpperChest
HeadoNeck
LeftShoulderUpperChest
LeftUpperArmoLeftShoulder
LeftLowerArmoLeftUpperArm
LeftHandoLeftLowerArm
LeftUpperLegoHips
LeftLowerLegoLeftUpperLeg
LeftFootoLeftLowerLeg
LeftToesLeftFoot
RightShoulderUpperChest
RightUpperArmoRightShoulder
RightLowerArmoRightUpperArm
RightHandoRightLowerArm
RightUpperLegoHips
RightLowerLegoRightUpperLeg
RightFootoRightLowerLeg
RightToesRightFoot




Humanoidの骨格をC#で直接動かす

UnityのHumanoidをC#のプログラムを用いて自分で好きなように動かす方法を解説します。

  1. Unityで新しいプロジェクトを開始します。
  2. "Humanoid" という "3D" 形式のプロジェクトを NEW しています。




  3. 人型モデル(fbx)をUnityに取り込みます
    1. Assets -> Create -> Folder として "Models" という名前のフォルダをアセットに作成します。
    2. Projectウィンドウで Assets/Models/ フォルダを開いて、そこに 「MakeHuman で Unity5 で使用する人型モデルを作成する」 で作成した AsianBoy.fbx と textures/ フォルダをExplorer からドラッグします。



    3. [注意]上の操作は

      Assets -> Import New Asset... -> AsianBody.fbx
      から行なってもよいのですが、これだとAsianBoy.fbx に必要なtextureが 自動ではimportされず、モデルが真っ白になってしまいます。 この場合は Assets/Models/Materials/に生成された白いMaterialに対応する Textureを手動でimportしなくてはいけません。




    4. ProjectウィンドウでAsianBoyを選択をして、 Inspectorウィンドウで "Rig" で Animation Type を "Humanoid" に変更し、 Avatar Definition を "Create From This Model" のままとして、 "Apply"ボタンをクリックします。 Humanoidとして 正しく骨格が対応づけられた場合は"Configure"の前にチェックマーク がつきます。



    5. Inspectorウィンドウで "configure" をクリックして、ボーンが正しく設定されたか確かめておいた方が安全です。



    6. Inspectorウィンドウで "Mapping" をクリックして、ボーンの対応を見ます。 確認できたら "Done" をクリックします。






  4. Hierarchy に Assets/Models/AsianBoy を配置します。
    1. Assets/Models から AsianBoy を Hierarchy にドラッグします。



    2. InspectorのTransformのから Resetを選択して、Position (x,y,z)=(0,0,0)とします。
  5. Humanoid キャラクタを操作するスクリプトを作成します。
    1. Projectウィンドウの Assets で右クリック -> Create -> Folder -> Scripts として フォルダを生成し、Scriptsにrenameします。






    2. ProjectウィンドウのAssets/Scriptsで右クリック -> Create -> C# Script として C#のファイルを生成し、RigBone にrenameします。 さらに内容を次のように変更します。 このクラスは MonoBehavior を継承していないことに注意して下さい。






    3. Humanoid のデータは Unity に import された段階で Animator コンポーネントが付加されています。 Animator コンポーネンント中には、人の骨格に適するようにBoneが階層化されていて、 GetBoneTransform(HumanBodyBones) 関数でその Transform を取得できます。

      RigBone クラスでは指定したクォータニオン qで Transform の localRotation を変更するメソッドを用意しています。 直接クォータニオンを与えるのではなく、回転角度と回転軸を与える関数も同じ名前で用意しました。

      RigBone.cs
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      public class RigBone {
        public GameObject gameObject;
        public HumanBodyBones bone;
        public bool isValid;
        Animator animator;
        Quaternion savedValue;
        public RigBone(GameObject g, HumanBodyBones b) {
          gameObject = g;
          bone = b;
          isValid = false;
          animator = gameObject.GetComponent<Animator>();
          if (animator == null) {
            Debug.Log("no Animator Component");
            return;
          }
          Avatar avatar = animator.avatar;
          if (avatar == null || !avatar.isHuman || !avatar.isValid) {
            Debug.Log("Avatar is not Humanoid or it is not valid");
            return;
          }
          isValid = true;
          savedValue = animator.GetBoneTransform(bone).localRotation;
        }
        public void set(float a, float x, float y, float z) {
          set(Quaternion.AngleAxis(a, new Vector3(x,y,z)));
        }
        public void set(Quaternion q) {
          animator.GetBoneTransform(bone).localRotation = q;
          savedValue = q;
        }
        public void mul(float a, float x, float y, float z) {
          mul(Quaternion.AngleAxis(a, new Vector3(x,y,z)));
        }
        public void mul(Quaternion q) {
          Transform tr = animator.GetBoneTransform(bone);
          tr.localRotation = q * tr.localRotation;
        }
        public void offset(float a, float x, float y, float z) {
          offset(Quaternion.AngleAxis(a, new Vector3(x,y,z)));
        }
        public void offset(Quaternion q) {
          animator.GetBoneTransform(bone).localRotation = q * savedValue;
        }
        public void changeBone(HumanBodyBones b) {
          bone = b;
          savedValue = animator.GetBoneTransform(bone).localRotation;
        }
      }
      
    4. ProjectウィンドウのAssets/Scriptsで右クリック -> Create -> C# Script として C#のファイルを生成し、RigControl にrenameします。 さらに内容を次のように変更します。






    5. 2秒周期で動作します。左上腕と左下腕は水平方向に、上腕は上下方向に振ります。 右ももと右膝は90度曲げ伸ばしを繰り返します。

      各局所座標系における角度はつぎの間の値です。

      Bone回転軸最小値最大値
      LeftUpperArmX軸-8080
      LeftLowerArmX軸090
      RigtUpperArmZ軸-9090
      RigthUpperLegX軸90180
      RightLowerLegX軸090

      以下のプログラムでは RigBone.set(Quaternion)関数を使って関節を曲げています。 Humanoid全体を回転させるには、humanoid.transform.rotation に世界座標系における値を設定します。 Unity Editor の Transform における Rotation の順番は Y, X, Z の順なのでここでもその順番で適用しています。

      RigControl.cs
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      using System.Linq;
      using System;
      
      public class RigControl : MonoBehaviour {
        public GameObject humanoid;
        public Vector3 bodyRotation = new Vector3(0,0,0);
        RigBone leftUpperArm;
        RigBone leftLowerArm;
        RigBone rightUpperArm;
        RigBone rightUpperLeg;
        RigBone rightLowerLeg;
        void Start () {
          leftUpperArm = new RigBone(humanoid, HumanBodyBones.LeftUpperArm);
          leftLowerArm = new RigBone(humanoid, HumanBodyBones.LeftLowerArm);
          rightUpperArm = new RigBone(humanoid, HumanBodyBones.RightUpperArm);
          rightUpperLeg = new RigBone(humanoid, HumanBodyBones.RightUpperLeg);
          rightLowerLeg = new RigBone(humanoid, HumanBodyBones.RightLowerLeg);
        }
        void Update () {
          double t = Math.Sin(Time.time * Math.PI); // [-1, 1]
          double s = (t+1)/2;                       // [0, 1]
          double u = 1-s/2;                         // [0.5, 1]
          leftUpperArm.set((float)(80*t),1,0,0);
          leftLowerArm.set((float)(90*s),1,0,0);
          rightUpperArm.set((float)(90*t),0,0,1);
          rightUpperLeg.set((float)(180*u),1,0,0);
          rightLowerLeg.set((float)(90*s),1,0,0);
          humanoid.transform.rotation 
            = Quaternion.AngleAxis(bodyRotation.z,new Vector3(0,0,1))
            * Quaternion.AngleAxis(bodyRotation.x,new Vector3(1,0,0))
            * Quaternion.AngleAxis(bodyRotation.y,new Vector3(0,1,0));
        }
      }
      
  6. Hierarchy 内にEmpty Object を配置し、名前を RigController に変更します。 InspectorのTransformのから Resetを選択して、Position (x,y,z)=(0,0,0)とします。






  7. ProjectウィンドウのAssets/Scripts/RigControlを Hierarchy の RigController の上へドラッグし、Hierarchyの RigController が青い楕円で囲まれている状態でドロップします。



  8. Hierarchy の RigController を選択すると、Inspectorウィンドウに "Rig Control (Script)" コンポーネントが表示されるようになったので、 追加されたことがわかります。 このコンポーネント内の Humanoid に操作する Humanoid キャラクター (今回の例では "AsianBoy") を設定します。



  9. Hierarchy の中の Main Camera の位置を変更します。
  10. Main Camera が少し離れ過ぎているので Transform Position (x,y,z)=(0,1,-2)に設定しましょう。




  11. シーンを名前をつけて保存します。ここでは rig.scene として、Assets/Scenes に保存ました。
  12. File -> Save Scene As ... 
    
  13. をクリックして実行してみます。



  14. プログラムを実行すると、2秒周期で左手上腕、左手下腕、右手上腕、右足上腿、右足下腿が動きます。

    プログラムを実行中にHierarchy ウィンドウで RigController を選択して、 RigController の Inspector ウィンドウ中の "Rig Control 2 (Script)" コンポーネントの BodyRotation を (X,Y,Z)=(0,180,0)にするとカメラ方向を向きます。





BoneのlocalRotationの初期値を利用する (上記の改良版)

  1. 新しいシーンを作ります。
  2.   File -> Save Scene as .. -> rig2.scene
    
  3. Assets/Scripts/の下に C# Script を生成して、名前を RigControl2.cs とします。
  4. RigControl2.cs では、 RigBone クラスの offset (Quaternion) メソッドを使う 、すなわち、 Bone 毎の localRotation の初期値に対して回転を加える ように変更しています。

    前の例における RigControl.cs では RigBone クラスの set() 関数 を使っていました。 すなわち、 Bone ごとの localRotation の初期値を利用せずに Bone を動かしていた ので、 Bone ごとに回転角として特別な値を設定する必要がありました。 たとえば RightUpperLeg では 0 °を設定すると足が真上を向いてしまうため、 90°から180°の間の値を設定していました。

    RigControl2.cs においては RigBone クラスの offset() 関数を使うことで 初期値に対する回転を加えている ので、 どの Bone にも 0 °周辺の値を与えれば正しく動作します。 もちろん、正方向と負方向のどちらがその関節にとって自然な動きかは Bone 毎に判断が必要ですが、ずっと考えやすい方法だと思います。

    ここでは各Boneには次の範囲の回転を与えています。

    Bone回転軸最小値最大値
    LeftUpperArmX軸-8080
    LeftLowerArmX軸090
    RigtUpperArmZ軸-9090
    RigthUpperLegX軸-900
    RightLowerLegX軸090
    RigControl2.cs
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using System.Linq;
    using System;
    
    public class RigControl2 : MonoBehaviour {
      public GameObject humanoid;
      public Vector3 bodyRotation = new Vector3(0,0,0);
      RigBone leftUpperArm;
      RigBone leftLowerArm;
      RigBone rightUpperArm;
      RigBone rightUpperLeg;
      RigBone rightLowerLeg;
      void Start () {
        leftUpperArm = new RigBone(humanoid, HumanBodyBones.LeftUpperArm);
        leftLowerArm = new RigBone(humanoid, HumanBodyBones.LeftLowerArm);
        rightUpperArm = new RigBone(humanoid, HumanBodyBones.RightUpperArm);
        rightUpperLeg = new RigBone(humanoid, HumanBodyBones.RightUpperLeg);
        rightLowerLeg = new RigBone(humanoid, HumanBodyBones.RightLowerLeg);
      }
      void Update () {
        double t = Math.Sin(Time.time * Math.PI); // [-1, 1]
        double s = (t+1)/2;                       // [0, 1]
        leftUpperArm.offset((float)(80*t),1,0,0);
        leftLowerArm.offset((float)(90*s),1,0,0);
        rightUpperArm.offset((float)(90*t),0,0,1);
        rightUpperLeg.offset((float)(-90*s),1,0,0);
        rightLowerLeg.offset((float)(90*s),1,0,0);
        humanoid.transform.rotation 
          = Quaternion.AngleAxis(bodyRotation.z,new Vector3(0,0,1))
          * Quaternion.AngleAxis(bodyRotation.x,new Vector3(1,0,0))
          * Quaternion.AngleAxis(bodyRotation.y,new Vector3(0,1,0));
      }
    }
    
  5. アセット中の Assets/Scripts/RigControl.cs をHierarchy 中の RigController 上にドロップします。 これでRigController に " Rig Control 2 (Script)" コンポーネントが付加されます。
  6. RigController を選択して Inspector ウィンドウ中の"Rig Control 2 (Script)" の Humanoid に、Hierarchy中の AsianBoy を設定します。
  7. 以前に RigController に付加した "Rig Control (Script)" の方は使わないので、 チェックをはずして動作しないようにしておくか、 または、 Remove Component しておきます。
  8. 実行します。
  9. 各Boneの localRotation の初期値に回転を加えているので若干ゆがんだ回転をしているようにも見えます。 しかし、こちらの方がプログラムで統一的に変形を加える方法としては、ずっと見通しがよいように思われます。




    実行時の動画 HumanoidRig2.mp4



ここで説明した Unity のプロジェクトファイルはこちら Humanoid.zip



http://karel.tsuda.ac.jp/