ゲームやアプリなどの3Dコンテンツを作るとき、実際にUnityでビルドしなくても、Unityをエディターとして利用してアニメーションを作ったり、アニメーションデータを管理するお話です。
Unityを3Dデータの母艦にするメリット
母艦というのは、ざっくりいうと3Dデータのビューワーとして使うという意味です。こんなメリットがあります。
- コスト的メリット:Maya、3ds Maxなどのツールを持っていなくても、UnityにFBXデータなどをインポートすることでアニメーションが確認できる。(ただしUnityのコストは必要)
- 母艦の役割:Unity上でエフェクト、サウンドをアニメーションに配置してアニメーション全体を確認できる
- 将来への備え:今後、自作プログラムからUnityに鞍替えする際にスムーズに移行できる(アニメーションデータはそのまま利用できる)
Unityのアニメーションを外部プログラムから利用する場合、Unityからアニメーションデータを出力する必要があるでしょう。あたりまえですね。
本記事ではそのアニメーションデータへのアクセス方法を紹介します。
Unityのアニメーションデータへのアクセス方法
Unityのアニメーションデータにアクセスする方法を紹介します。※Unityのアニメーションの知識があるものとして書いていきます。
シンプルなカメラアニメーションを例にしましょう。カメラの位置と角度をアニメーションしています。
プロジェクトのステージ上に配置されたコンポーネントはこんな感じ。AnimationRootというGameObjectが親コンポーネントとして配置され、その子コンポーネントとしてCameraコンポーネント、その他の板、球体、ボックスが配置されています。
AnimationRootには[Animatorコンポーネント]がセットされ、さらにAnimationRootControllerという名前の[AnimationControllerコンポーネント]がセットされています。
AnimationRootControllerをAnimatorウィンドウで開いた図です。オレンジ色の四角でCameraAnimation1という名前の[AnimationClipコンポーネント]がセットされています。
[Animation]ウィンドウでは、先ほどのCameraAnimation1という[AnimationClip]を下記のようなキーフレームとアニメーションカーブで確認できます。
アニメーションになじみの無い方は、ここまでAnimationなんたらという名前が多く、頭がこんがらがったのではと思います。ざっくりテキストで表すとこのような階層構造をイメージすると良いでしょう。
AnimationRoot (シーンに配置された親コンポーネント)
└─AnimationRootController (アニメーションを管理するコントローラー)
└─CameraAnimation1 (アニメーションクリップ)
├─Camera1の位置アニメーション
└─Camera1の回転アニメーション
AnimationUtility.GetCurveBindings関数を使ったアニメーションカーブへのアクセス
Cameraの回転アニメーション情報にアクセスしてみましょう。
ポイントは、 AnimationUtility.GetCurveBindings関数によりアニメーション曲線にアクセスできるというところです。
// Unityエディターから実行できるコード
[MenuItem("Tools/GetAnimationClip")]
static public void GetAnimationClip()
{
// 選択したオブジェクト
var anim_clip = Selection.activeObject;
// 選択したオブジェクトがAnimationClipだったとき
if (anim_clip as AnimationClip)
{
Debug.Log(anim_clip);
// アニメーションカーブの配列
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(anim_clip as AnimationClip);
}
else {
// 選択したオブジェクトがAnimationClipではなかったとき
Debug.Log("AnimationClipじゃないよ");
}
}
XYZ回転補間のアニメーションカーブ
コード中の [curveBindings] という配列をデバッグ表示した図です。今回、曲線が6つあるため、curveBindingsが6つあります。(XYZ位置で3つ分、回転のXYZで3つ分)。曲線が6つあるからカーブが6つ、これは素直にしっくりくると思います。
下記6つの曲線です。
回転アニメーション曲線
- localEulerAnglesRaw.x:X軸回転
- localEulerAnglesRaw.y:Y軸回転
- localEulerAnglesRaw.z:Z軸回転
位置アニメーション曲線 - m_LocalPosition.x:x位置
- m_LocalPosition.y:y位置
- m_LocalPosition.z:z位置
次に各アニメーション曲線のキーフレームにアクセスしてみましょう。
AnimationUtility.GetEditorCurve関数によるキーフレームへのアクセス
さらに下記コードにより、アニメーションカーブのそれぞれにアクセスできます。
foreach (var binding in curveBindings)
{
// 1アニメーションカーブ
var curve = AnimationUtility.GetEditorCurve(animationClip, binding);
}
AnimationUtility.GetEditorCurve関数により取得されたAnimationCurveオブジェクトには、キーフレームの情報が格納されています。これらを出力して利用すれば、外部プログラムでUnityアニメーションを利用できるでしょう。
- time:時間
- value:各時間ごとのプロパティ値
- tangentMode:補間形式
- inTangent, outTangent:勾配をつけている場合、ここが変化します。
クォータニオン補間のアニメーションカーブ
さて、先ほどわざわざ曲線が6つあるからカーブが6つなんてことを書きましたが、今度は別のアニメーションデータにアクセスしてみます。
お気づきでしょうか。回転アニメーション曲線が4つあります。
回転アニメーションは以下の4カーブです。
- m_LocalRotation.x:クォータニオンのX成分
- m_LocalRotation.y:クォータニオンのY成分
- m_LocalRotation.z:クォータニオンのZ成分
- m_LocalRotation.w:クォータニオンのW成分
補間方法の違い
この違いは何が原因かといいますと、各AnimationClipのInterpolationの設定から来ています。下図のようにQuaternionとなっていると回転アニメーションはQuaternionで補間され、データとしてもQuaternionの値を出力します。UnityにMayaや3ds Maxなどで作成したFBX形式のデータをインポートするとこのような形式になっていることが多いです。
下図のようにEuler Anglesとなっている場合、回転アニメーションはXYZ回転として補間され、データとしてもXYZ回転の値を出力します。Unityで自身でアニメーションを追加するとこの形式になっているでしょう。
まとめ
Unityのアニメーションデータを外部プログラムで利用するためのはじめの一歩をご紹介しました。曲線補間方式により、XYZ回転か、クォータニオン回転かを判断してデータを使いわけする必要があります。(m_LocalRotationプロパティを持っているか、localEulerAnglesRawプロパティを持っているか、そこがポイント)
コード一式
[MenuItem("Tools/GetAnimationClip")]
static public void GetAnimationClip()
{
// 選択したオブジェクト
var obj = Selection.activeObject;
// 選択したオブジェクトがAnimationClipだったとき
if (obj as AnimationClip)
{
// AnimationClipオブジェクト
var animationClip = obj as AnimationClip;
Logger.Log("====================");
Logger.Log(animationClip.name);
// アニメーションカーブの配列
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(animationClip as AnimationClip);
foreach (EditorCurveBinding binding in curveBindings)
{
Logger.Log("-----------");
Logger.Log(binding.propertyName);
// 1アニメーションカーブ
AnimationCurve curve = AnimationUtility.GetEditorCurve(animationClip, binding);
// 各キーフレームの情報
for (var i = 0; i < curve.keys.Length; i++)
{
Keyframe key = curve.keys[i];
Logger.Log(key.time + ", " + key.value);
}
}
}
else {
// 選択したオブジェクトがAnimationClipではなかったとき
Logger.Log("AnimationClipじゃないよ");
}
}
出力データ
CameraAnimation1
m_LocalPosition.x
0, 0
0.8333333, -3.72
1.666667, 0
m_LocalPosition.y
0, 1
0.8333333, 3.86
1.666667, 1
m_LocalPosition.z
0, -10
0.8333333, -1.31
1.666667, -10
m_LocalRotation.x
0, 0
0.8333333, 0.3803079
1.666667, 0
m_LocalRotation.y
0, 0
0.8333333, 0.4737305
1.666667, 0
m_LocalRotation.z
0, 0
0.8333333, -0.1155413
1.666667, 0
m_LocalRotation.w
0, 1
0.8333333, 0.7858725
1.666667, 1
※実際に外部プログラムで利用する際には、扱いやすい形に整えます。
補足
- この記事ではWindows 10 proにインストールしたUnity 2017.2.0f3 (10/12リリース)を使用しています。
- Debug.Logではなく、Logger.Logとなっているのは、Unityの標準デバッグではないツールを使っているためです。(Debug.Logだと見づらいため)
明日はShaulaさんの「ライブラリコードをDLL化してコンパイル時間を減らそう!」についてです。