Unityのアニメーションデータを外部プログラムで利用するはじめの一歩

ゲームやアプリなどの3Dコンテンツを作るとき、実際にUnityでビルドしなくても、Unityをエディターとして利用してアニメーションを作ったり、アニメーションデータを管理するお話です。

## Unityを3Dデータの母艦にするメリット
母艦というのは、ざっくりいうと3Dデータのビューワーとして使うという意味です。こんなメリットがあります。

- **コスト的メリット**：Maya、3ds Maxなどのツールを持っていなくても、UnityにFBXデータなどをインポートすることでアニメーションが確認できる。(ただしUnityのコストは必要)
- **母艦の役割**：Unity上でエフェクト、サウンドをアニメーションに配置してアニメーション全体を確認できる
- **将来への備え**：今後、自作プログラムからUnityに鞍替えする際にスムーズに移行できる(アニメーションデータはそのまま利用できる)

Unityのアニメーションを外部プログラムから利用する場合、**Unityからアニメーションデータを出力する必要がある**でしょう。あたりまえですね。
本記事ではそのアニメーションデータへのアクセス方法を紹介します。

## Unityのアニメーションデータへのアクセス方法
Unityのアニメーションデータにアクセスする方法を紹介します。※Unityのアニメーションの知識があるものとして書いていきます。

シンプルなカメラアニメーションを例にしましょう。カメラの位置と角度をアニメーションしています。

![camera_animation.gif](https://qiita-image-store.s3.amazonaws.com/0/16944/a340ee28-77ad-c7a7-745e-48cdd869648d.gif)

プロジェクトのステージ上に配置されたコンポーネントはこんな感じ。AnimationRootというGameObjectが親コンポーネントとして配置され、その子コンポーネントとしてCameraコンポーネント、その他の板、球体、ボックスが配置されています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/16944/82d36475-3b3a-5064-aada-b5d1b417c983.png)

AnimationRootには[Animatorコンポーネント]がセットされ、さらにAnimationRootControllerという名前の[AnimationControllerコンポーネント]がセットされています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/16944/0d8aacc0-34fe-29f9-b8db-3cef9689edc1.png)

AnimationRootControllerをAnimatorウィンドウで開いた図です。オレンジ色の四角でCameraAnimation1という名前の[AnimationClipコンポーネント]がセットされています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/16944/d24153dd-6b94-1e41-a8c0-c3d7eed0dd62.png)

[Animation]ウィンドウでは、先ほどのCameraAnimation1という[AnimationClip]を下記のようなキーフレームとアニメーションカーブで確認できます。
![camera_animation_timeline.PNG](https://qiita-image-store.s3.amazonaws.com/0/16944/41661cb4-cf51-b942-f116-17cc5cb5ab6f.png)
![camera_animation_graph.PNG](https://qiita-image-store.s3.amazonaws.com/0/16944/d1f56561-6a03-eb6c-394b-ff82120fb099.png)

アニメーションになじみの無い方は、ここまでAnimationなんたらという名前が多く、頭がこんがらがったのではと思います。ざっくりテキストで表すとこのような階層構造をイメージすると良いでしょう。

AnimationRoot (シーンに配置された親コンポーネント)
　└─AnimationRootController (アニメーションを管理するコントローラー)
　　　└─CameraAnimation1 (アニメーションクリップ)
　　　　　├─Camera1の位置アニメーション
　　　　　└─Camera1の回転アニメーション

### AnimationUtility.GetCurveBindings関数を使ったアニメーションカーブへのアクセス
Cameraの回転アニメーション情報にアクセスしてみましょう。
ポイントは、 **AnimationUtility.GetCurveBindings関数によりアニメーション曲線にアクセスできる**というところです。

```csharp

// 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つ**、これは素直にしっくりくると思います。

![回転rotation.png](https://qiita-image-store.s3.amazonaws.com/0/16944/e9347315-423a-3925-009c-1305cf037331.png)

下記6つの曲線です。
回転アニメーション曲線
- localEulerAnglesRaw.x：X軸回転
- localEulerAnglesRaw.y：Y軸回転
- localEulerAnglesRaw.z：Z軸回転
位置アニメーション曲線
- m_LocalPosition.x：x位置
- m_LocalPosition.y：y位置
- m_LocalPosition.z：z位置

次に各アニメーション曲線のキーフレームにアクセスしてみましょう。

### AnimationUtility.GetEditorCurve関数によるキーフレームへのアクセス

さらに下記コードにより、アニメーションカーブのそれぞれにアクセスできます。

```csharp

foreach (var binding in curveBindings)
{
  // 1アニメーションカーブ
  var curve = AnimationUtility.GetEditorCurve(animationClip, binding);
}
```

**AnimationUtility.GetEditorCurve関数**により取得されたAnimationCurveオブジェクトには、キーフレームの情報が格納されています。これらを出力して利用すれば、外部プログラムでUnityアニメーションを利用できるでしょう。

- time：時間
- value：各時間ごとのプロパティ値
- tangentMode：補間形式
- inTangent, outTangent：勾配をつけている場合、ここが変化します。



![image.png](https://qiita-image-store.s3.amazonaws.com/0/16944/647cb1ae-8281-289e-eaf5-266caec5c40d.png)

### クォータニオン補間のアニメーションカーブ
さて、先ほどわざわざ**曲線が6つあるからカーブが6つ**なんてことを書きましたが、今度は別のアニメーションデータにアクセスしてみます。

![回転クォータニオン.png](https://qiita-image-store.s3.amazonaws.com/0/16944/a611ee46-8148-a63c-6e29-2424354a506a.png)

お気づきでしょうか。**回転アニメーション曲線が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形式のデータをインポートするとこのような形式になっていることが多いです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/16944/fbd806f9-215a-354f-dfc7-0d4a2299344c.png)

下図のように**Euler Angles**となっている場合、回転アニメーションはXYZ回転として補間され、データとしてもXYZ回転の値を出力します。Unityで自身でアニメーションを追加するとこの形式になっているでしょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/16944/d4c82478-908c-ee32-63e3-26930b70e818.png)

# まとめ
Unityのアニメーションデータを外部プログラムで利用するためのはじめの一歩をご紹介しました。**曲線補間方式により、XYZ回転か、クォータニオン回転かを判断**してデータを使いわけする必要があります。(**m_LocalRotationプロパティ**を持っているか、**localEulerAnglesRawプロパティ**を持っているか、そこがポイント)

### コード一式

```csharp
[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化してコンパイル時間を減らそう！」についてです。