[Unity][Timeline] スクリプトから動的に各種TimelineAsset/TrackAsset/TimelineClipなどを生成するTips

## 初めに
実際にTimelineの機能を使っていくとエディタ拡張を使ってプロジェクトの仕様に合わせた改造を施したくなってくると思います

今回はそのような用途に必要となるTimelineに関連するアセットの生成処理をUnityEditorの標準UI操作を一切挟まずに行う方法についてまとめます

## `TimelineAsset`を生成・保存する

```csharp
// TimelinenAssetのインスタンスを生成する
var timelineAsset = ScriptableObject.CreateInstance<TimelineAsset>();

// ファイルとして保存 ※AssetDatabase.CreateAssetの仕様上、ディレクトリは先に作っておく必要がある
var path = "Assets/Resources/TimelineAssets/Sample.playable";
AssetDatabase.CreateAsset(timelineAsset, path);
```

## `TrackAsset`系を生成する

```csharp
// ルートにGroupという名前でGroupTrackを生成する
var trackAsset = timelineAsset.CreateTrack<GroupTrack>(null, "Group");

// Groupトラックの子トラックとしてAnimationという名前でAnimationTrackを生成する
var animationTrack = timelineAsset.CreateTrack<AnimationTrack>(trackAsset, "Animation");
```

## `AnimationClip`を元にして`AnimationTrack`に`TimelineClip`を生成する

```csharp
animationClip = EditorGUILayout.ObjectField("Animation Clip", animationClip, typeof(AnimationClip), false) as AnimationClip;

var track = timelineAsset.CreateTrack<AnimationTrack>(null, "Animation Track");
// CreateClipメソッドにAnimationClipを渡すだけ
var clip = track.CreateClip(animationClip);

// TimelineClipからAnimationClipを取り出すにはAnimationPlayableAssetにキャストする
var asset = clip.asset as AnimationPlayableAsset;
Debug.LogFormat("animationClip={0}", asset.clip);
```

## `AudioClip`を元にして`AudioTrack`に`TimelineClip`を生成する

```csharp
audioClip = EditorGUILayout.ObjectField("Audio Clip", audioClip, typeof(AudioClip), false) as AudioClip;

var track = timelineAsset.CreateTrack<AudioTrack>(null, "Audio Track");
// CreateClipメソッドにAudioClipを渡すだけ
var clip = audioTrack.CreateClip(audioClip);

// TimelineClipからAudioClipを取り出すにはAudioPlayableAssetにキャストする
var asset = clip.asset as AudioPlayableAsset;
Debug.LogFormat("audioClip={0}", asset.clip);
```

## `ActivationTrack`に`TimelineClip`を生成する

```csharp
// BindされたObjectのActiveを切り替えるTrackなので生成時に合わせてBindしてあげると便利
var track = timelineAsset.CreateTrack<ActivationTrack>(null, "Activation Track");
playableDirector.SetGenericBinding(track, targetObject);

// Clipの生成に特別なことはない
var clip = track.CreateDefaultClip();
// Clipがある時間帯をActive状態にするものなので、生成に合わせて時間を設定してあげると便利
clip.duration = duration;
```

## `PlayableTrack`に`TimelineClip`を生成する

```csharp
var track = timelineAsset.CreateTrack<PlayableTrack>(null, "Playable Track");
// Clipの生成時にPlayableAssetを継承した任意の型を渡せる
var clip = track.CreateClip<SamplePlayableAsset>();
```

## Prefabを元にしてControlTrackにTimelineClipを生成する

```csharp

prefab = EditorGUILayout.ObjectField("Prefab", prefab, typeof(GameObject), false) as GameObject;
if (!PrefabUtility.IsPartOfPrefabAsset(prefab))
{
    return;
}

// Clipの生成まではシンプル
var track = timelineAsset.CreateTrack<ControlTrack>(null, "Control Track");
var clip = track.CreateDefaultClip();

// ControlPlayableAssetを取り出し、prefabの参照を持たせる
var asset = clip.asset as ControlPlayableAsset;
asset.prefabGameObject = prefab;
```

## シーン上のオブジェクトを元にしてControlTrackにTimelineClipを生成する

```csharp

sceneGo = EditorGUILayout.ObjectField("Scene GameObject", sceneGo, typeof(GameObject), true) as GameObject;
// 実は、Prefabじゃない=Scene上のオブジェクトと疑似判定できる
if (PrefabUtility.IsPartOfPrefabAsset(sceneGo))
{
    return;
}

// Clipの生成まではシンプル
var track = timelineAsset.CreateTrack<ControlTrack>(null, "Control Track");
var clip = track.CreateDefaultClip();

// 親のTimelineAssetを再生しているPlayableDirectorをcontextに渡してclip.assetのSerializedObjectを作る
var serializedObject = new SerializedObject(clip.asset, playableDirector);
// ExposedReferenceとして定義されているsourceGameObjectのプロパティを取得してシーン上のオブジェクトの参照を持たせる
var serializedProperty = serializedObject.FindProperty("sourceGameObject");
serializedProperty.exposedReferenceValue = sceneGo;
// clip.assetに対しての変更を適用する
serializedObject.ApplyModifiedProperties();
```

※ `ExposedReference<T>` については別記事で別途解説予定

## まとめ
今回は標準で用意されている5つのTrackに対してClipを生成する処理をまとめました

`ControlTrack`が若干ややこしいですが、ただ単に生成するだけであればそこまで難しい内容ではなかったと思います