ModelImporterのdefaultClipAnimationsとclipAnimationsについて

モーションデータをアニメーションfbxで受け取る際、そのデータが大量に存在する場合するにはヒューマンエラーを防ぐ目的で、自動的にUnityのアニメーションのインポート設定を行いたいことがあります。 例えば、読み込んだアニメーションクリップのループ設定をファイル名から自動で指定するなどです。

その場合Unityでは、AssetPostprocessor.OnPreprocessAnimation()などのメソッドをフックして、アニメーションをインポートタイミング時に処理を差し込みます。

インポート設定はModelImporterクラスを介して行います。 このプロパティ中にclipAnimationsdefaultClipAnimationsが存在するのですが、この違いがぱっとドキュメントを読んだだけではわからなかったので、実験的に検証しつつまとめました。

TL;DR

defaultClipAnimationsfbx のTakeInfoで、clipAnimationsUnity設定(メタデータ) のTakeInfo。

TakeInfoとは

まず前提として、fbxでは1つのアニメーションデータに対して複数の区切り情報をつけて出力することができます(実際には1fbxファイルです)。これをTakeInfoといいます。

例えば、走るモーションがあったとして、データ上は走り開始、走りループ、走り終了が1つのモーションデータで作っている(こちらのケースのほうがアーティストさんがデータを確認しやすいためこういうデータづくりをするケースが有る)が、 ゲームで扱う場合は、走り中のループが行動によって不定なため、3つのモーションに分割したい場合などに、このTakeInfoをもとにモーションを分割することもできます。

TakeInfoはMotionBuilderなどで編集してfbxにTakeInfoを合わせて出力することが可能で、Unityでは設定した情報をfbx上から読み込むことができます。

Unityのインスペクタ上でのTakeInfo - 画像は https://docs.unity3d.com/Manual/Splittinganimations.html より

Unityのインスペクタ上でのTakeInfo - 画像は https://docs.unity3d.com/Manual/Splittinganimations.html より

UnityではTakeInfoを元にTake名と同名のアニメーションクリップを出力します

defaultClipAnimationsとclipAnimations

fbxに保存されているTakeInfoは前述したModelImporterdefaultClipAnimationsに格納されています。 defaultClipAnimationsはfbxのデータのため更新することができません(UnityはDCCツールなどで出力されたデータを基本は書き換えず、Unity独自の設定はメタデータで行う)。

UnityではさらにTakeInfoを上書きすることができ、このデータはfbxファイルのメタデータ内に書き込まれます。その情報をclipAnimationsで取得することができます。

当然、clipAnimationsは下記のようにスクリプトで編集することもできます。

void OnPreprocessAnimation(ModelImporter importer)
{
    var importer = assetImporter as ModelImporter;

    var clips = importer.defaultClipAnimations;
    foreach (var clip in clips)
    {
        // defaultClipAnimationsに対して設定を上書きを行う
        // 例えば Root Transform Position(Y)の設定を自動で行う
        // reference by https://gist.github.com/keijiro/fde84ff347a6747bcf6b
        clip.lockRootHeightY = true;
        clip.keepOriginalPositionY = false;
        clip.heightFromFeet = true;
        clip.heightOffset = -0.1f;
    }
    
    importer.clipAnimations = clips;
}

ちなみにclipAnimationsは自動で作成されるわけではなく、設定を変更したタイミングでデータが取得できます。デフォルトでは空配列となっています。 なので、すでに上書き設定が行われている場合に、上書き設定に対して処理を行いたい場合は、下記のようにclipAnimationsが空でないかの判定を行います。

void OnPreprocessAnimation(ModelImporter importer)
{
    var importer = assetImporter as ModelImporter;

    var clips = importer.clipAnimations;
    // 上書き設定が行われていなければ、fbxの設定を持ってくる
    // 空でなければ上書き設定を更新する
    if (clips.Length == 0) clips = importer.defaultClipAnimations;

    // ここからは一緒
    //...
}

1つ注意点なのが、fbxのデータが更新されるケースです。 例えばfbx側でモーションの尺を調整した場合、その設定自体defaultClipAnimationsModelImporterClipAnimationfirstFrameおよびlastFrameで取得できるのですが、 上記のスクリプトでは一度上書き設定をしてしまうと、以後defaultClipAnimationsを参照していないため、fbx側の尺の変更を反映させることができません

モーションの尺変更を反映させたい場合は、defaultClipAnimationsも参照しつつ、というスクリプトが必要です。

void OnPreprocessAnimation(ModelImporter importer)
{
    var importer = assetImporter as ModelImporter;

    var clips = importer.clipAnimations;
    // 上書き設定が行われていなければ、fbxの設定を持ってくる
    if (clips.Length == 0) clips = importer.defaultClipAnimations;

    for (var i = 0; i < clips.Length; ++i)
    {
        if (i >= defaultClipAnimations.Length)
        {
            continue;
        }

        // モーション尺の情報はfbxデータを正としておく
        clips[i].firstFrame = importer.defaultClipAnimations[i].firstFrame;
        clips[i].lastFrame = importer.defaultClipAnimations[i].lastFrame;

        // ...
    }
}

ただし、TakeInfoはその分割数さえもUnity側で上書きできるので、その場合はモーション尺の更新も必要ないでしょう(分割設定をUnityでやるということは分割をUnity側に委ねているはずなのでfbx側のTakeInfoを持ってくる必要はない)。

つまり用途ごとにこの辺の実装は異なるかと思います。

まとめ

defaultClipAnimationsclipAnimationsの違いについて説明しました。アニメーションのインポータはこの違いとデータの更新タイミングを理解し実装することが必要かと思います。

ちなみに著者は最後に紹介したミスをやらかしました...