MEBIUSTOSのブログ

主にUnityに関する技術的な事を書いていきます。 Twitter @xflutexx

スクリプトでアニメーションクリップファイルを作成する

Unityのエディタスクリプトから、アニメーションクリップ(Animation Clip)ファイルを作成する方法です。

はじめに

先日公開したブレンドシェイプ値をアニメーションクリップとしてファイルに吐き出すコレ。

当初は複数のスキンメッシュに分散したブレンドシェイプを一箇所で操作したい!と、いうのが目的だったのですが、頑張ったらアニメーションクリップファイルを作成するまで出来てしまったので、その方法を記載しておきます。

余談ですが、後でMMD4MecanimFaciemForMecanim(MMD4Mecanimモデルの表情をメカニムで制御するツール)にもクリップ作成機能を搭載しようかと考え中。

超基本形

空のアニメーションクリップファイルを作るだけなら簡単です。

var animclip = new AnimationClip();
AssetDatabase.CreateAsset(
        animclip,
        AssetDatabase.GenerateUniqueAssetPath("Assets/test.anim")
    );
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();

うん。実にシンプル。
CreateAssetで作成して、SaveAssetsで保存して、Refreshで念のためアセットデータベースをリフレッシュ。

キーフレームとプロパティ値の設定

空のアニメーションクリップを作成しても何も嬉しくないので、プロパティを追加してみます。以下に示すコードはブレンドシェイプ「あ」のWeight値をプロパティとして設定したアニメーションクリップを作成するソースコードです。

var animclip = new AnimationClip();

EditorCurveBinding curveBinding = new EditorCurveBinding();
curveBinding.path = "SkinnedMeshObject";
curveBinding.type = typeof(SkinnedMeshRenderer);
curveBinding.propertyName = "blendShape.あ";

AnimationCurve curve = new AnimationCurve();
curve.AddKey(0f, 12.3f);

AnimationUtility.SetEditorCurve(animclip, curveBinding, curve);

AssetDatabase.CreateAsset(
        animclip,
        AssetDatabase.GenerateUniqueAssetPath("Assets/test.anim")
    );
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();

流れは下記のとおりです。

  1. EditorCurveBindingで、どのプロパティを操作するのかの指定。
  2. AnimationCurveで、キーフレームとプロパティ値の指定。
  3. AnimationUtility.SetEditorCurveで、アニメーションクリップにEditorCurveBindingとAnimationCurveを設定。

EditorCurveBinding

操作するプロパティを特定するため、3つのパラメータを指定します。

1. path

操作対象となるオブジェクトの名前を相対パスで指定します。ルートの場合は""になります。
先のソースコードではルートとなるオブジェクトの一階層下にある"SkinnedMeshObject"という名前のゲームオブジェクトを指定しています。

curveBinding.path = "SkinnedMeshObject";

別例として、以下のような階層構造をもつオブジェクトにおいてBオブジェクトを指定したい場合は次のようになります。

アニメーションクリップを適用するルートオブジェクト
 └Aオブジェクト(name:a_object)
  └Bオブジェクト(name:b_object)
curveBinding.path = "a_object/b_object";
2. type

操作するコンポーネントタイプを指定します。
先のソースコードではブレンドシェイプを操作するためSkinnedMeshRendererを指定しています。

curveBinding.type = typeof(SkinnedMeshRenderer);
3. propertyName

操作するプロパティを指定します。
ブレンドシェイプの場合は"blendShape." + ブレンドシェイプ名で指定します。(これを調べるのに紆余曲折あったのですが、それについては後ほど。)
先のソースコードではブレンドシェイプ「あ」を指定しています。

curveBinding.propertyName = "blendShape.あ";

AnimationCurve

キーフレーム時間と、プロパティの値を設定します。
AddKey(float time, float value)という感じで、第一引数にキーフレームの時間を、第二引数にプロパティの値を指定します。
先のソースコードではキーフレーム0秒、ブレンドシェイプのWeight値に12.3fを設定しています。

curve.AddKey(0f, 12.3f);

propertyNameに設定すべき値が分からなかった

当初、EditorCurveBindingのpropertyNameに対し、どのような形式で対象のブレンドシェイプを設定するのかが分かりませんでした。ネットを漁ったところ、エディタ拡張マニアクス 2015 (PDF)のP44-P49に、スプライトアニメーションを作成する場合におけるpropertyNameに設定すべき名前の調べ方がありました。そこで、ブレンドシェイプについても同様の方法にて調べてみたところ、SkinnedMeshRenderer内のm_BlendShapeWeights.Array.data[0]というパスに辿り着いたので、「これか!」と思い意気揚々とpropertyNameに設定してみたんですが、残念ながら動きませんでした。
悩んだ結果、正しく設定されたアニメーションクリップのpropertyNameがどうなっているのか調べるのが手っ取り早い!という結論に至り、アニメーションクリップ内のpropertyNameをDebugLogに出力するツールを作成しました。
それがこれです。(プロジェクトウィンドウ内のアニメーションクリップを選択)

using UnityEngine;
using UnityEditor;

public class ClipPropertyName : EditorWindow {
    [MenuItem("Window/Disp curve.propertyName")]
    static void ShowWindow() {
        EditorWindow.GetWindow<ClipPropertyName>();
    }

    void OnSelectionChange() {
        if (Selection.objects.Length > 1) {
            Debug.Log("Length: " + Selection.objects.Length);
        } else if (Selection.activeObject is AnimationClip) {
            foreach (var curve in AnimationUtility.GetCurveBindings((AnimationClip)Selection.activeObject)) {
                Debug.Log(curve.propertyName);
            }
        }
    }

    void OnGUI() {
        GUILayout.Label("Please select an Animation Clip");
    }
}

で、調べたらブレンドシェイプの場合は"blendShape." + ブレンドシェイプ名だった。という感じです。

pathの謎

EditorCurveBindingのpathはルートオブジェクトからの相対パスをゲームオブジェクトの名前で表すわけですが……。
「ゲームオブジェクトの名前って同じ場合あるよね?」
という疑問。同じ名前のオブジェクトがあった場合どうやって指定するんだろう……。
まぁとりあえず、現状困ってないので記憶の隅に追いやる。