【Unity】Timelineから参照しているすべてのアセットパスを手に入れたい

# はじめに
UnityのTimelineを使用していてTimelineAssetが握っている参照アセットをすべて調べたくなりました。
やりたいことは以下です。
* Timelineが参照しているPrefabやAnimationClipを一覧表示
* さらにそのPrefabが参照してる各アセットを全表示
です。

先に見せてしまいますが、Timelineからこのようにアセット名＋参照Listが出るようにしたいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/938699b1-9f8d-454f-a556-c97b669d1098.png)

# ソースコード
https://github.com/ayaha401/AssetDependenciesFromTimeline-SourceCode

# 今回のアセット構造
## Timeline
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/b0f15dab-7a50-459d-9207-0e4eb1199da6.png)
タイムラインにはTrackGroup、さらにSubTrackを作り、それぞれにTrackを置きます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/e11cc935-2b57-4df1-86a8-889f72f2beb8.png)
上4つのAnimationTrackはUnity標準のAnimationTrackでAnimationClipをアタッチします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/a559b0d2-1fc8-4cc1-89b0-74d8d2b49b6a.png)
下の4つは自作のTrackとClipを配置します。PrefabTrackというものを作成しました。Prefabを3つ入れることができるだけです。
今回は参照関係を知りたいだけなので自作のTrackは特に機能はありません。

## ディレクトリ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/68dace5d-9aed-4eb6-9341-e52900423692.png)
Asset＞Timelineにタイムラインに関係するものを入れます。

アセットは001と002というIDがあり、Timelineからは001が参照されているのが正しいシチュエーションです。
001と似てるアセットが002にあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/f70b9cd2-493f-4f11-8a66-b6aee2e4fe4e.png)
アニメーション、マテリアル、Prefabがここに入っています。
002は間違っている想定の場所です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/b7bd04a9-bb0d-4fd2-a8d7-90b4dc52acc1.png)

そして、TimelineTrackの4番目では002を参照させます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/5e1ed324-f98c-48e2-86f6-64fc11532e69.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/c49ade53-398a-4f70-bff6-7f0839ab0c14.png)

各Trackで002が参照されてしまっていることをツールで知れるようにすることが目的です。

# やり方
## AssetDatabase.GetDependenciesを使用してみる
`AssetDatabase.GetDependencies`という依存関係を出してくれるメソッドがUnityであるので使用します。
使い方は以下です。
```C#
private string[] GetDependencies(TimelineAsset timelineAsset)
{
    var assetPath = AssetDatabase.GetAssetPath(timelineAsset);
    var dependencies = AssetDatabase.GetDependencies(assetPath, true);
    return dependencies;
}
```
`AssetDatabase.GetDependencies`は第2引数に`True`を入れると再帰的に参照を調べてくれます。
戻ってくる配列に重複はありませんでした。

出力結果はこうなりました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/c135a7ab-5655-41d8-a269-c9636ad1d5ab.png)

間違った場所にアクセスしているのが取得できました。
しかし、この方法だと冒頭で書いたやりたいことの
**Timelineが参照しているPrefabやAnimationClipを一覧表示**
が達成できません。
Timelineから参照関係は知れましたがこのままだとどのアセットに問題があるのかわからない状態です。

## Clipから調べる
`AssetDatabase.GetDependencies`で達成できなかった理由はアセット名を取得できていなかったこととアセットごとの参照を取得できていない点なのでそれを解決します。

Timelineが含んでいるアセットをすべて取得し、Dictionaryに格納すればうまくいくためそうします。
```C#
private void GetDependencies(TimelineAsset timelineAsset)
{
    foreach (var track in timelineAsset.GetOutputTracks())
    {
        GetDependenciesFromTrack(track);
    }
}
```
まずはTimelineからTrackの数だけループを行います。
```C#
/// <summary>
/// TrackのからClipを探し、参照を調べる
/// </summary>
private void GetDependenciesFromTrack(TrackAsset trackAsset)
{
    // Clipの数だけループする
    foreach (var clip in trackAsset.GetClips())
    {
        // シリアライズされたオブジェクト、プロパティを探し、その数だけループさせる
        SerializedObject serializedObject = new SerializedObject(clip.asset);
        SerializedProperty prop = serializedObject.GetIterator();
        while (prop.NextVisible(true))
        {
            // プロパティの種類がUnityEngine.Objectのものだけ調べます。
            if(prop.propertyType == SerializedPropertyType.ObjectReference)
            {
                // プロパティとしてついているスクリプトは参照を調べなくてよいので(今回は)分岐します
                Object referencedObject = prop.objectReferenceValue;
                if(referencedObject != null && referencedObject.GetType() != typeof(MonoScript))
                {
                    // GetDependenciesを使用してプロパティに入れられたアセットの参照を取得します。
                    var assetPath = AssetDatabase.GetAssetPath(referencedObject);
                    // .csは参照から除きます。(今回は)
                    var dependencies = AssetDatabase.GetDependencies(assetPath, true).Where(dependency => !dependency.EndsWith(".cs")).ToList();
                    var assetName = referencedObject.name;
                    if(!_assetDependencies.ContainsKey(assetName))
                    {
                        _assetDependencies[assetName] = dependencies;
                    }
                }
            }
        }

        // SubTrackに関して再帰的に処理します。
        foreach (var subTrack in trackAsset.GetChildTracks())
        {
            GetDependenciesFromTrack(subTrack);
        }
    }
}
```

```C#
foreach (var clip in trackAsset.GetClips())
```
Clipでループを回します。
そしてClipに入っているアセットをすべて取得します。
取得方法はここです。
```C#
SerializedObject serializedObject = new SerializedObject(clip.asset);
SerializedProperty prop = serializedObject.GetIterator();
while (prop.NextVisible(true))
{
    // プロパティの種類がUnityEngine.Objectのものだけ調べます。
    if(prop.propertyType == SerializedPropertyType.ObjectReference)
}
```
Clipにドラッグアンドドロップできる場所に入っているものがClipに入っているアセットなので`SerializedObject`,`SerializedProperty`を使用してシリアライズされているプロパティを取得します。
`while (prop.NextVisible(true))`でプロパティの数だけループします。
```C#
// プロパティの種類がUnityEngine.Objectのものだけ調べます。
if(prop.propertyType == SerializedPropertyType.ObjectReference)
```
ドラッグアンドドロップできるものがアセットと捉えているためこの処理をします。intや他のプロパティはアセット参照とは関係がないため扱いません。
```C#
// プロパティとしてついているスクリプトは参照を調べなくてよいので(今回は)分岐します
Object referencedObject = prop.objectReferenceValue;
if(referencedObject != null && referencedObject.GetType() != typeof(MonoScript))
{
    // GetDependenciesを使用してプロパティに入れられたアセットの参照を取得します。
    var assetPath = AssetDatabase.GetAssetPath(referencedObject);
    // .csは参照から除きます。(今回は)
    var dependencies = AssetDatabase.GetDependencies(assetPath, true).Where(dependency => !dependency.EndsWith(".cs")).ToList();
    var assetName = referencedObject.name;
    if(!_assetDependencies.ContainsKey(assetName))
    {
        _assetDependencies[assetName] = dependencies;
    }
}
```
`prop.objectReferenceValue`でObjectを取得できるため何をドラッグアンドドロップで入れたのか取得できています。Prefabであろうが、マテリアルであろうがあとは`AssetDatabase.GetDependencies(assetPath, true)`を行えばUnityの機能でアセット参照を全部出してくれます。
今回はアセット参照が知りたいため、`Where(dependency => !dependency.EndsWith(".cs"))`でC#スクリプトは除外します。
最終的にPrefab名＋かかわるアセット参照の関係でList表示したいので`Dictionary<string, List<string>>`を作成して入れます。Keyにはアセット名、Valueには参照関係のListを入れます。
このDictionaryを使用してEditor拡張で表示をすると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/3b1a9e97-ee6d-4c62-800d-22363ee2ea5c.png)
このように表示さて、SubTrackのClipに入っているアセットから参照情報を取得できました。

# 終わりに
このツールを作成していて最初は`AssetDatabase.GetDependencies`を使用しただけで終わろうと思っていましたがどのアセットに問題があるのか全くわからなかったのでPrefabやアセットどこにわかる表示を行えるようにしました。
GUIDから関わるアセットを全表示する方法が多分あるのですが、これはおそらくアセット全検索になり速度が出るのか怪しかったためこの方法に行きつきました。