Nuitrack 1.5.0
3D スケルトン トラッキング ミドルウェア
 すべて クラス 名前空間 関数 変数 Typedefs 列挙型 列挙子 プロパティ イベント グループ ページ
Nuitrack と Motion Capture を使用してアニメーションを作成

このチュートリアルでは、Nuitrack を使って、Unity 内で使用するアバターのアニメーションを短時間で簡単に作成する方法を紹介します。追加で必要となるツールは、スケルトンのトラッキングを行い、アニメーションを記録するための深度センサー (サポートしているセンサーの一覧を参照) だけです。アニメーションの記録方法を 3通り紹介します。必要に応じて、どの方法を使うかを選べます。

Generic アニメーション は、どんなモデルにでも使える方法です。一般的に、Humanoid アニメーションよりも簡単ですが、Game オブジェクトの階層に紐付けされています。つまり、アニメーション Game オブジェクトの名前や位置を変更すると、アニメーションが正しく表示されないということです。

GameObjectRecorder を使用しているアニメーションは、 Generic アニメーションの簡略版といえるかもしれません。この方法も、どんなモデルにも使用できます。これは便利な方法ですが、まだ最適化されていません。結果的に、Game Objects すべて (全く動かなかったアバターの部分も) が記録されるので、不要なキーの長い一覧ができてしまいます。

Humanoid アニメーション は名前の通り、人型のモデルに使用されます。特定のモデルでのアニメーションを記録後に、それを別の人型モデルに適用できるので、Generic アニメーションよりも多用途に使用できます。さらに、特定の筋肉の動きの範囲を指定できます。

Umc_9.gif
GameObjectRecorder を使用したアニメーション

このプロジェクトを作成するために必要なものは以下の通りです。

  • Nuitrack Runtime と Nuitrack SDK
  • サポートされているセンサー (サポートしているセンサーの一覧は、Nuitrack Webサイトを参照)
  • Unity 2019.2.11f 以上

完成済みプロジェクトは、 Nuitrack SDK: Unity 3D > NuitrackSDK.unitypackage > Tutorials > Motion Captureにあります。

自分に合ったアニメーション タイプを選び、作業を始めましょう!

シーンのセットアップ

  1. 新規プロジェクトを作成します。プロジェクトに Nuitrack Skeleton Tracking Unity パッケージをインポートします。Ethan モデル、もしくは他の人型モデルを、ダウンロードしたパッケージからシーンへとドラッグ アンド ドロップします。モデルは、関節が正しく一致するように Tポーズにします。そのためには、モデルの手 (例えば、この例では EthanLeftArmEthanRightArm) の Rotation パラメーターを以下のスクリーンショットにあるように設定します。

    Umc_1.png
    "EthanLeftArm" オブジェクトの設定
    Umc_2.png
    "EthanRightArm" オブジェクトの設定

  2. RiggedAvatar スクリプトを Nuitrack Skeleton Tracking Unity パッケージからドラッグ アンド ドロップします。アニメーションを作成したい関節を選択します。すべて合わせると、Nuitrack は 20 の関節をトラッキングします。関節の一覧はこちらAnimator コンポーネントがある場合は、削除することで、RiggedAvatar スクリプトの変更がブロックされません。選択した関節の例:

    Umc_3.png

  3. モデル全体が、カメラの視野に入るように配置します。
  4. 記録の開始と終了を示す、赤いインジケーターが必要です。キャンバス を作成し、その上に録画のような赤いボタンの画像を作成します。このインジケーターを右上の角に表示させます。
  5. NuitrackScriptsプレハブを Nuitrack SDK からシーンにドラッグ アンド ドロップします。設定で、アバターのスケルトン トラッキングに必要なモジュール、深度モジュール、スケルトン トラッカー モジュール、ユーザートラッカー モジュールをクリックして選択します。

    Umc_4.png

  6. この段階で、プロジェクトを実行し、アバターがユーザーの動きに合わせて動くかどうかを確認できます。

アニメーション レコーダーのためのインターフェイスを作成

  1. アニメーション レコーダーの実装を数種類使用するので、はっきり区別できるようなインターフェイスを作成する必要があります。新しいスクリプトを作成し、IRecordable と名前を付けます。このスクリプトで、同じ名前のインターフェイスの説明を行います。スクリプトで、Game オブジェクトの状態を写真で撮るためのTakeSnapShot メソッドと、記録したアニメーションのクリップを取得するための GetClip を作成します。

    using UnityEngine;
    public interface IRecordable
    {
    void TakeSnapshot(float deltaTime);
    AnimationClip GetClip { get; }
    }
    注意
    このプロジェクトでは、レコーダーとのインタラクションを簡単にするためにインターフェイスを使用します。インターフェイスを使用することにより、複数ソース動作を一つのクラスに含めることができます。独自のレコーダーを作成し、プロジェクトに追加することも、IRecordable インターフェイスのおかげで簡単にできます。
  2. 新しいスクリプト GenericRecorder を作成し、 Generic レコードがどのように動作するかを定義します。まずは、レコーダーの操作/動作を、アニメーションの記録を行わずに確認します。GenericRecorder クラスを IRecordable インターフェイスか継承し、time 変数を指定します。

    public class GenericRecorder :IRecordable
    {
    float time = 0.0f;
    }

  3. TakeSnapshot メソッドを作成し、アニメーションの各フレームの写真をとります。

    float time = 0.0f;
    }
    public void TakeSnapshot(float deltaTime)
    {
    time += deltaTime;
    }

  4. AnimationClip メソッドを作成し、記録したアニメーション クリップを取得します。

    time += deltaTime;
    public AnimationClip GetClip
    {
    get
    {
    AnimationClip clip = new AnimationClip();
    return clip;
    }
    }
    }

  5. 新しいスクリプトを作成し、 UnityAnimationRecorder と名前を付けます。これにより、レコーダーとのインタラクション (記録とクリップの取得) を行うためのアルゴリズムを定義します。UnityEditor 名前空間を追加します。使用するアニメーションの種類を一覧にします (Generic、GenericExperimental、Humanoid)。次にあげるフィールドから、必要なものを追加します。recordMode はレコーダーの種類を保存するため、savePath はパスを記録したアニメーションに保存するため、fileName はファイル名を保存するため、poseCalibration は Tポーズ キャリブレーションの管理を行うため、recordIcon 記録開始/終了のインジケーターのためのフィールドです。isRecording ブール変数を作成することで、記録ステータスを確認し IRecordable インターフェイスにレファレンスを追加できます。

    using UnityEngine;
    using UnityEditor;
    public class UnityAnimationRecorder :MonoBehaviour
    {
    enum RecordMode { Generic, GenericExperimental, Humanoid };
    [Header("General")]
    [SerializeField] RecordMode recordMode = RecordMode.Generic;
    [Header("Save")]
    [SerializeField] string savePath = "Assets/NuitrackSDK/Tutorials/Motion Capture/Animations";
    [SerializeField] string fileName = "Example";
    [Header("Control")]
    [SerializeField] TPoseCalibration poseCalibration;
    [SerializeField] GameObject recordIcon;
    bool isRecording = false;
    IRecordable recordable = null;
    }

  6. アニメーション クリップの記録開始と終了は、Tポーズを使用します。Start メソッドで、T-Pose calibration completion イベントに登録します。レコーダーを初期化します。

    IRecordable recordable = null;
    void Start()
    {
    poseCalibration.onSuccess += PoseCalibration_onSuccess;
    switch (recordMode)
    {
    case RecordMode.Generic:
    recordable = new GenericRecorder();
    break;
    }
    }
    }

  7. onDestroy メソッドで、T-Pose calibration completion イベントの登録を解除します。

    break;
    }
    }
    private void OnDestroy()
    {
    poseCalibration.onSuccess -= PoseCalibration_onSuccess;
    }
    }

  8. PoseCalibration_onSuccess メソッドでは、アニメーション記録開始と終了の処理を行います。記録開始/終了インジケーターを有効にします。

    poseCalibration.onSuccess -= PoseCalibration_onSuccess;
    }
    private void PoseCalibration_onSuccess(Quaternion rotation)
    {
    if (!isRecording)
    {
    Debug.Log("Start recording");
    isRecording = true;
    }
    else
    {
    Debug.Log("Stop recording");
    isRecording = false;
    SaveToFile(recordable.GetClip);
    }
    recordIcon.SetActive(isRecording);
    }
    }
    }

  9. Update メソッドで、インターフェースを使用してレコードにアクセスし、各フレームのスナップショットを撮ります。

    recordIcon.SetActive(isRecording);
    }
    void Update()
    {
    if (isRecording)
    recordable.TakeSnapshot(Time.deltaTime);
    }
    }

  10. SaveToFile メソッドでは、記録したアニメーションをファイルに保存します (ファイルのパスとファイル名を指定します)。

    recordable.TakeSnapshot(Time.deltaTime);
    }
    void SaveToFile(AnimationClip clip)
    {
    string path = savePath + "/" + fileName + ".anim";
    clip.name = fileName;
    AssetDatabase.CreateAsset(clip, path);
    Debug.Log("Save to:" + path);
    }
    }
    注意
    AssetDatabase クラスは、エディターみアクセスできます。ビルドしてもエラーで動きません。
  11. Unity で空のオブジェクトを作成し、Recorder と名前を付けます。UnityAnimationRecorder スクリプトをこのオブジェクトにドラッグ アンド ドロップします。Pose Calibration (NuitrackScriptsから取得) と Record Icon (Image) にレファレンスを追加します。

    Umc_5.png

  12. プロジェクトを実行し、アニメーションの記録 (録画) を確認します。この時点では、記録の作業 (データの転送と受取り) が正しく行えるかのみ確認するので、作成したファイルは空になります。

    Umc_6.gif

Generic レコーダー

  1. GenericRecorder スクリプトに戻り、Generic アニメーションの形を整え、記録したアニメーションが作成したファイルに実際に保存されるようにします。CurveContainer クラスを作成し、記録したオブジェクト (一つの軸に沿った位置や回転等) とカーブ (時間軸の中でプロパティがどのように変化するか) のプロパティを設定します。lastValue 変数は、前のフレーム値で、アニメーションの最適化に使用します (動かないオブジェクトのキーは保存されません)。カーブと、CurveContainer コンストラクター内の記録されたプロパティの名前を作成します。

    return clip;
    }
    }
    }
    class CurveContainer
    {
    public string Property { get; private set; }
    public AnimationCurve Curve { get; private set; }
    float? lastValue = null;
    public CurveContainer(string _propertyName)
    {
    Curve = new AnimationCurve();
    Property = _propertyName;
    }
    }

  2. 同じクラスで、addValue メソッドを作成し、現在の値をその前の値と比較します (最初のフレームの時と変更がある時に、アニメーションは記録として保存されます)。Keyframe は、時間と値を取得し、カーブに渡すキーです。その後、lastValue がアップデートされます。

    Property = _propertyName;
    }
    public void AddValue(float time, float value)
    {
    // If the current value is first or not equal to the previous one
    if (lastValue == null || !Mathf.Approximately((float)lastValue, value))
    {
    Keyframe key = new Keyframe(time, value);
    Curve.AddKey(key);
    lastValue = value;
    }
    }
    }

  3. ObjectAnimation クラスを作成し、オブジェクト (アニメーションにした関節) とカーブ (位置と回転の変化) を保存します。関節変形、カーブの一覧、ルート変形から現在の変形 (Ethan) へのパスを作成します。ObjectAnimation コンストラクターで、変形と位置を改装に保存し、カーブのコレクションを埋めます (オブジェクトの初期化の際、プロパティの名前をパラメーターとして渡します)。

    return clip;
    }
    }
    }
    class ObjectAnimation
    {
    Transform transform;
    public List<CurveContainer> Curves { get; private set; }
    public string Path { get; private set; }
    public ObjectAnimation(string hierarchyPath, Transform recordableTransform)
    {
    Path = hierarchyPath;
    transform = recordableTransform;
    Curves = new List<CurveContainer>
    {
    new CurveContainer("localPosition.x"),
    new CurveContainer("localPosition.y"),
    new CurveContainer("localPosition.z"),
    new CurveContainer("localRotation.x"),
    new CurveContainer("localRotation.y"),
    new CurveContainer("localRotation.z"),
    new CurveContainer("localRotation.w")
    };
    }
    }
    class CurveContainer

  4. TakeSnapShot メソッドで、フレームのスナップショットを撮ります (時間と値を CurveContainer に渡します)。

    new CurveContainer("localRotation.w")
    };
    }
    public void TakeSnapshot(float time)
    {
    Curves[0].AddValue(time, transform.localPosition.x);
    Curves[1].AddValue(time, transform.localPosition.y);
    Curves[2].AddValue(time, transform.localPosition.z);
    Curves[3].AddValue(time, transform.localRotation.x);
    Curves[4].AddValue(time, transform.localRotation.y);
    Curves[5].AddValue(time, transform.localRotation.z);
    Curves[6].AddValue(time, transform.localRotation.w);
    }
    }
    class CurveContainer

  5. アニメーション作成対象のオブジェクト一覧を作成します。

    public class GenericRecorder :IRecordable
    {
    float time = 0.0f;
    List<ObjectAnimation> objectAnimations = new List<ObjectAnimation>();
    }
    }
    public void TakeSnapshot(float deltaTime)

  6. GenericRecorder コンストラクターを作成します。これは、オブジェクトとルート オブジェクトの変形に対応します。変更が記録されるオブジェクトの一覧を作成します。

    List<ObjectAnimation> objectAnimations = new List<ObjectAnimation>();
    public GenericRecorder(Transform[] recordableTransform, Transform rootTransform)
    {
    foreach (Transform transform in recordableTransform)
    {
    string path = AnimationUtility.CalculateTransformPath(transform, rootTransform);
    objectAnimations.Add(new ObjectAnimation(path, transform));
    }
    }
    public void TakeSnapshot(float deltaTime)

  7. TakeSnapShot メソッドを編集します。オブジェクトのコレクションをループ処理し、スナップショットを撮ります。

    public void TakeSnapshot(float deltaTime)
    {
    time += deltaTime;
    foreach (ObjectAnimation animation in objectAnimations)
    animation.TakeSnapshot(time);
    }

  8. GetClip メソッドのアニメーション クリップを取得します。そのために、空のAnimationClip を作成し、オブジェクトのコレクションとオブジェクト プロパティのカーブに対してループ処理をします。カーブに1つのキーしか含まれていない場合、記録されていない (変更がない) ということになります。カーブを保存し、クリップを戻します。

    public AnimationClip GetClip
    {
    get
    {
    AnimationClip clip = new AnimationClip();
    foreach (ObjectAnimation animation in objectAnimations)
    {
    foreach (CurveContainer container in animation.Curves)
    {
    if (container.Curve.keys.Length > 1)
    clip.SetCurve(animation.Path, typeof(Transform), container.Property, container.Curve);
    }
    }
    return clip;
    }
    }

  9. UnityAnimationRecorderスクリプトに移動し、これらのパラメーターを Start メソッドに渡します。

    void Start()
    {
    poseCalibration.onSuccess += PoseCalibration_onSuccess;
    switch (recordMode)
    {
    case RecordMode.Generic:
    recordable = new GenericRecorder(transforms, root);
    break;
    }
    }

  10. Unity で、モデルをルート オブジェクトとして登録します。登録するには、[Recorder] > [Root] > [Ethan]を使用します。関節を設定します。

    Umc_7.png

  11. プロジェクトを確認するために、Ethan モデルをシーンにコピーします。この段階までで、Animator 要素ができています。記録したアニメーション クリップをモデルにドラッグ アンド ドロップします。Animation 要素に、Avatar が含まれている場合、アニメーションは始まりませんので、含まれていないことを確認してください。その後、プロジェクトを実行し、記録された Generic アニメーションを確認してください。

    Umc_8.gif

GameObject レコーダー

  1. ExperimentalRecorder スクリプトを作成しますが、これは、 GameObjectRecorder のシェルとして機能します。UnityEditor.Experimental.Animations 名前空間を追加します。IRecordable インターフェイスから継承します。m_Recorder オブジェクトを指定します。

    #if UNITY_EDITOR
    using UnityEngine;
    #if UNITY_2017_4 || UNITY_2018_2
    using UnityEditor.Experimental.Animations;
    #endif
    public class ExperimentalRecorder :IRecordable
    {
    #if UNITY_2017_4 || UNITY_2018_2
    GameObjectRecorder m_Recorder;
    #endif
    }
    #endif

  2. root オブジェクトを受け入れるコンストラクターを作成します。お使いの Unity バージョンに応じたオブジェクトの初期化を行い、root オブジェクトを渡します。その後、root 以下のすべてのオブジェクトは、階層的に処理されます。

    GameObjectRecorder m_Recorder;
    #endif
    public ExperimentalRecorder(GameObject rootObject)
    {
    #if UNITY_2017_4
    m_Recorder = new GameObjectRecorder();
    m_Recorder.root = rootObject;
    m_Recorder.BindComponent<Transform>(rootObject, true);
    #elif UNITY_2018_2
    m_Recorder = new GameObjectRecorder(rootObject);
    m_Recorder.BindComponentsOfType<Transform>(rootObject, true);
    #else
    PrintErrorVersion();
    #endif
    }
    #endif
    注意
    現段階では、この種類のアニメーションの記録は、Unity の2つのバージョンでのみ利用できます。Unity バージョンに応じて実行されるコード部分を区別できるように、プロセッサー指示 if, elif, else, endif を使用します。
  3. TakeSnapShot メソッドを作成し、各フレームのスナップショットを撮ります。

    PrintErrorVersion();
    #endif
    }
    public void TakeSnapshot(float deltaTime)
    {
    #if UNITY_2017_4 || UNITY_2018_2
    m_Recorder.TakeSnapshot(deltaTime);
    #else
    PrintErrorVersion();
    #endif
    }
    }
    #endif

  4. GetClip メソッドで空のクリップを作成し、記録したアニメーションを保存します。記録が終了したら、m_Recorder を空にします。クリップを戻します。使用している Unity バージョンが正しくない場合、エラーが表示されます (PrintErrorVersion メソッドを参照)。

    PrintErrorVersion();
    #endif
    }
    public AnimationClip GetClip
    {
    get
    {
    #if UNITY_2017_4 || UNITY_2018_2
    AnimationClip clip = new AnimationClip();
    m_Recorder.SaveToClip(clip);
    m_Recorder.ResetRecording();
    return clip;
    #else
    PrintErrorVersion();
    return null;
    #endif
    }
    }
    void PrintErrorVersion()
    {
    Debug.Log("Check your Unity version");
    }
    }
    #endif

  5. UnityAnimationRecorder スクリプトに戻り、GameObjectRecorder を使ったレコーダーの初期化をStart メソッドに追加します。

    void Start()
    {
    recordable = new GenericRecorder(transforms, root);
    break;
    case RecordMode.GenericExperimental:
    recordable = new ExperimentalRecorder(root.gameObject);
    break;
    }

  6. プロジェクトを実行し、GameObjectRecorder を使ったアニメーションの記録 (録画) を確認します。

    Umc_9.gif

    記録したアニメーション クリップを再生するには、モデルにドラッグ アンド ドロップし、[Animator]セクションの Avatar 要素をクリックして解除します。

    Umc_10.gif

Humanoid レコーダー

  1. Unity でモデルをセットアップします ([Models]フォルダー)。アバターの設定でアニメーションの種類を設定します。[Rig] > [Animation] > [Type] > [Humanoid]で設定します。

    Umc_11.png

  2. Unity でのスケルトンには、最低限の関節数のみが用意されています。モデル庭リア当てられた関節がこのセットの関節と一致しない場合、該当する体の部分が赤くハイライトされます。[Configure...]をクリックして、モデルのすべての関節が、正しく割り当てられているかを確認します。その後、[Done]をクリックします。

    Umc_12.png
    注意
    これ以降、モデルのアニメーションは、Game Objects の回転と位置の変更によって作成されるのではなく、筋肉の圧縮率 (特定の動作範囲で -1 から 1) によって作成されます。
    Umc_13.gif

  3. 使用済みモデル (Animator なし) をシーンから削除します。新しいモデル (Animator あり) をシーンにドラッグ アンド ドロップします。モデルは Tポーズをします。
  4. AnimatorAvatar スクリプトを作成して、モデルのアニメーションを行います。animator と関節の一覧のフィールドを追加します。SimpleJoint クラスを作成し、nuitrackJoint 関節タイプ (Nuitrack から取得)、Offset (センサーに関連したスケルトン オフセットを補う) 関節の変形を保存します。

    using UnityEngine;
    using System.Linq;
    using System.Collections.Generic;
    public class AnimatorAvatar :MonoBehaviour
    {
    [SerializeField] Animator animator;
    [SerializeField] List<SimpleJoint> joints = new List<SimpleJoint>();
    }
    [System.Serializable]
    class SimpleJoint
    {
    public nuitrack.JointType nuitrackJoint;
    public Quaternion Offset { get; set; }
    public Transform Bone { get; set; }
    }

  5. Start メソッドで、関節のループ処理を行います。Nuitrack の関節と Unity の関節を一致させる必要があります。Unity の関節で割り当てられていないものがある場合、コンソールに、その関節に関するエラー メッセージが表示されます。すべての関節が正しく割り当てられれば、モデルの関節を転送し、取得したものを joints に保存し、Offset を計算します。

    [SerializeField] List joints = new List();
    void Start ()
    {
    foreach (SimpleJoint item in joints)
    {
    HumanBodyBones unityBoneType = item.nuitrackJoint.ToUnityBones();
    Transform bone = animator.GetBoneTransform(unityBoneType);
    if (bone == null)
    {
    Debug.LogError("The bone " + unityBoneType + " is not found (check configuration of the humanoid in the Rig tab)");
    enabled = false;
    }
    else
    {
    item.Bone = bone;
    item.Offset = bone.rotation;
    }
    }
    }
    }
    [System.Serializable]

  6. animator, の処理の特異性ゆえに、LateUpdateUpdate の代わりに使用しています。LateUpdate でスケルトンの関節を処理します (「スケルトンを使用してアバターに生気を」 チュートリアルを参照)。

    item.Offset = bone.rotation;
    }
    }
    void LateUpdate()
    {
    if (CurrentUserTracker.CurrentSkeleton != null)
    {
    nuitrack.Skeleton skeleton = CurrentUserTracker.CurrentSkeleton;
    transform.position = Quaternion.Euler(0f, 180f, 0f) * (0.001f * skeleton.GetJoint(nuitrack.JointType.Torso).ToVector3());
    foreach (SimpleJoint item in joints)
    {
    nuitrack.Joint joint = skeleton.GetJoint(item.nuitrackJoint);
    Quaternion rotation = Quaternion.Inverse(CalibrationInfo.SensorOrientation) * joint.ToQuaternionMirrored() * item.Offset;
    item.Bone.rotation = rotation;
    }
    }
    }
    }
    [System.Serializable]

  7. GetHumanBodyBones プロパティで、Unity で割り当てられている関節 (このオブジェクトのアニメーションに使用) の一覧を戻します。

    item.Bone.rotation = rotation;
    }
    }
    }
    public HumanBodyBones[] GetHumanBodyBones
    {
    get
    {
    return joints.Select(x => x.nuitrackJoint.ToUnityBones()).ToArray();
    }
    }
    }
    [System.Serializable]

  8. GetAnimator プロパティは、animator を戻します。

    return joints.Select(x => x.nuitrackJoint.ToUnityBones()).ToArray();
    }
    }
    public Animator GetAnimator
    {
    get
    {
    return animator;
    }
    }
    }
    [System.Serializable]

  9. HumanoidRecorder スクリプトの動作を確認します。スクリプトをモデルにドラッグ アンド ドロップし、アニメーションを作成する animator と Nuitrack joints を割り当てます。この時点で、アバターは動くようになっています。
  10. 新しいスクリプトを作成して、HumanoidRecorder と名前を付け、IRecordable インターフェイスから継承します。time 変数 (現在時刻) を作成します。モデルの位置を保存するために使用する HumanPose オブジェクトを指定します。HumanPoseHandler は、 HumanPose のハンドラーです (animator らリクエスト)。辞書を2つ作成します。1つは筋肉、もう一つは root オブジェクト位置のためです。モデルの位置を修正する rootOffset 変数を作成します (ユーザーではなくセンサーが中央に配置されているため)。

    using UnityEngine;
    using System.Collections.Generic;
    public class HumanoidRecoder :IRecordable
    {
    float time = 0;
    HumanPose humanPose = new HumanPose();
    HumanPoseHandler humanPoseHandler;
    Dictionary<int, AnimationCurve> muscleCurves = new Dictionary<int, AnimationCurve>();
    Dictionary<string, AnimationCurve> rootCurves = new Dictionary<string, AnimationCurve>();
    Vector3 rootOffset;
    }

  11. HumanoidRecorder コンストラクターで rootOffset を指定し、 humanPoseHandler を初期化します(animator avatarroot transform のパラメーターを渡す)。アバターのアニメーションに使用される筋肉の情報を取得します: 関節を dof (“自由度”) 指数でループ処理し、各自由度指数に対して、既定の関節の筋肉指数をリクエストします。辞書への指標を含む筋肉を渡します。rootCurves の root オブジェクト位置を渡します。

    Vector3 rootOffset;
    public HumanoidRecoder(Animator animator, HumanBodyBones[] humanBodyBones)
    {
    rootOffset = animator.transform.position;
    humanPoseHandler = new HumanPoseHandler(animator.avatar, animator.transform);
    foreach (HumanBodyBones unityBoneType in humanBodyBones)
    {
    for (int dofIndex = 0; dofIndex < 3; dofIndex++)
    {
    int muscle = HumanTrait.MuscleFromBone((int)unityBoneType, dofIndex);
    if (muscle != -1)
    muscleCurves.Add(muscle, new AnimationCurve());
    }
    }
    rootCurves.Add("RootT.x", new AnimationCurve());
    rootCurves.Add("RootT.y", new AnimationCurve());
    rootCurves.Add("RootT.z", new AnimationCurve());
    }
    }

  12. TakeSnapShot メソッドで、アバターのスナップショットを撮り、humanPose に保存します。筋肉のループ処理をし、カーブに値を設定します。同じメソッドで、アニメーション クリップのすべてのデータを保存します。キーをアニメーション カーブに渡します。root オブジェクト位置を保存すると、モデルをシーン内で動かせます。

    rootCurves.Add("RootT.z", new AnimationCurve());
    }
    public void TakeSnapshot(float deltaTime)
    {
    time += deltaTime;
    humanPoseHandler.GetHumanPose(ref humanPose);
    foreach (KeyValuePair<int, AnimationCurve> data in muscleCurves)
    {
    Keyframe key = new Keyframe(time, humanPose.muscles[data.Key]);
    data.Value.AddKey(key);
    }
    Vector3 rootPosition = humanPose.bodyPosition - rootOffset;
    AddRootKey("RootT.x", rootPosition.x);
    AddRootKey("RootT.y", rootPosition.y);
    AddRootKey("RootT.z", rootPosition.z);
    }
    }

  13. AddRootKey メソッドで、プロパティと値を取得し、Keyframe キーを作成し、rootCurves 辞書に追加します。RootKey (x, y, z) を追加します。

    AddRootKey("RootT.z", rootPosition.z);
    }
    void AddRootKey(string property, float value)
    {
    Keyframe key = new Keyframe(time, value);
    rootCurves[property].AddKey(key);
    }
    }

  14. GetClip でアニメーション クリップを作成します。そのためには、筋肉のループ処理を行い、カーブをクリップに渡します。rootCurves にも同じことをします。これで、humanoid アニメーションでの全く新しいクリップが作成されます。

    rootCurves[property].AddKey(key);
    }
    public AnimationClip GetClip
    {
    get
    {
    AnimationClip clip = new AnimationClip();
    foreach (KeyValuePair<int, AnimationCurve> data in muscleCurves)
    {
    clip.SetCurve(null, typeof(Animator), HumanTrait.MuscleName[data.Key], data.Value);
    }
    foreach (KeyValuePair<string, AnimationCurve> data in rootCurves)
    {
    clip.SetCurve(null, typeof(Animator), data.Key, data.Value);
    }
    return clip;
    }
    }
    }

  15. UnityAnimationRecorder スクリプトに戻り、このアニメーション タイプに関するフィールドを追加します。Humanoid アニメーションを Start メソッドに追加します。

    [SerializeField] Transform[] transforms;
    [Header("Humanoid Animations")]
    [SerializeField] AnimatorAvatar animatorAvatar;
    void Start()
    {
    case RecordMode.Humanoid:
    recordable = new HumanoidRecoder(animatorAvatar.GetAnimator, animatorAvatar.GetHumanBodyBones);
    break;
    }
    }

  16. Unity でアバター アニメーションの記録を設定します。Humanoid 記録モード (Recorder > Record Mode > Humanoid) とアバター (Animator Avatar > Ethan (AnimatorAvatar)) を選択し、ルート オブジェクトをアップデート:Root > Ethan (new model) します。

    Umc_14.png

  17. プロジェクトを実行します。この作業で、アニメーションの確認が、モデルに追加せずに[Inspector]タブからできるようになります。
Umc_15.gif

Humanoid アニメーション タイプのどのモデルにでもアニメーションを適用して確認できます。以下に表示されるのは、標準 Unity モデルに適用された、記録済みアニメーションです。

Umc_16.gif