Unity Timelineの拡張テク

この記事は[Unity Advent Calendar 2022](https://qiita.com/advent-calendar/2022/unity)の13日目の記事です。
(遅刻しました…)


## 拡張とは

Unity Timelineは「トラック」とその中の「クリップ」、そしてAnimationEventのように決まった時間で何かを発生させるのに便利な「マーカー」を自作できます。

これらはスクリプトでも生成できますが、多くはTimelineウィンドウで作業することが多いでしょう。この記事ではその拡張周りを紹介します。

## 環境

- Unity 2020.3.29f1
    - Timeline 1.4.8


## 基本

トラック、クリップ、マーカーは `UnityEditor.Editor` を継承し、かつ属性 `UnityEditor.CustomEditor` をつけたクラスを作ることで、Inspectorウィンドウに表示するGUIのカスタマイズができます。

それ以外に、Timelineウィンドウの動作時に呼ばれるものがあります。トラック、クリップ、マーカーそれぞれで微妙に異なります。



### トラック
`UnityEditor.Timeline.TrackEditor` を継承し、
`UnityEditor.Timeline.CustomTimelineEditorAttribute` 属性を付けたクラスです。

```csharp
[UnityEditor.Timeline.CustomTimelineEditorAttribute(typeof(TrackSample))]
public sealed class TrackSampleTimelineEditor : UnityEditor.Timeline.TrackEditor {
    // 拡張をここにかく
}
```

#### 異常のあるトラックに「⚠️」を表示する
```csharp
public override UnityEditor.Timeline.TrackDrawOptions GetTrackOptions(
  UnityEngine.Timeline.TrackAsset track,
  UnityEngine.Object              binding
) {
  var errorText = GetErrorText(
    track: track,
    boundObject: binding,
    detectErrors: UnityEditor.Timeline.TrackBindingErrors.All
  );

  if (string.IsNullOrEmpty(errorText)) {
    //ここで、そのトラック用のエラーチェックをすると良い
    var trackAsset = (TrackSample)track;

    // string.Emptyをいれると「エラーはなし」
    // 何らかの文字列が入っていると、Timelineウィンドウ状のトラックに ⚠️ とWarningで表示されます
    errorText = string.Empty;
  }

  return new UnityEditor.Timeline.TrackDrawOptions {
    errorText     = errorText,
    minimumHeight = DefaultTrackHeight,
    trackColor    = GetTrackColor(track),
    icon          = null,
  };
}
```

#### トラック作成の検知
```csharp
public override void OnCreate(
  UnityEngine.Timeline.TrackAsset track,
  UnityEngine.Timeline.TrackAsset copiedFrom
) {
  // ここに作成時にセットアップしたいことを書きます
  // copiedFrom は複製時のコピー元です
}
```

#### トラック編集中の検知
```csharp
public override void OnTrackChanged(UnityEngine.Timeline.TrackAsset track) {
  // 編集中のトラックに何か加工をするときに使いましょう
}
```



### クリップ周り
`UnityEditor.Timeline.ClipEditor` を継承し、かつ属性`UnityEditor.Timeline.CustomTimelineEditorAttribute` を付けたクラスが該当します。
```csharp
[UnityEditor.Timeline.CustomTimelineEditorAttribute(typeof(ClipSample))]
public sealed class ClipSampleTimelineEditor : UnityEditor.Timeline.ClipEditor {
  // 拡張をここにかく
}
```
#### 異常のあるクリップに「⚠️」を表示する
```csharp
public override UnityEditor.Timeline.ClipDrawOptions GetClipOptions(UnityEngine.Timeline.TimelineClip clip) {
  var errorText = GetErrorText(clip);
  if (string.IsNullOrEmpty(errorText))
  {
    // ここにクリップ固有のエラーチェックを入れる
    var clipAsset = (ClipSample)clip.asset;

    // string.Emptyをいれると「エラーはなし」
    // 何らかの文字列が入っていると、Timelineウィンドウ状のトラックに ⚠️ とWarningで表示されます
    errorText = string.Empty;
  }

  return new UnityEditor.Timeline.ClipDrawOptions
  {
    errorText      = errorText,
    tooltip        = string.Empty,
    highlightColor = GetDefaultHighlightColor(clip),
    icons          = System.Linq.Enumerable.Empty<UnityEngine.Texture2D>(),
  };
}
```

#### クリップ作成の検知
```csharp
public override void OnCreate(UnityEngine.Timeline.TimelineClip clip, UnityEngine.Timeline.TrackAsset track, UnityEngine.Timeline.TimelineClip clonedFrom) {
  // ここに作成時にセットアップしたいことを書きます
  // copiedFrom は複製時のコピー元です
}
```

#### クリップ編集中の検知
```csharp
public override void OnClipChanged(UnityEngine.Timeline.TimelineClip clip) {
  // 編集中のクリップに何か加工をするときに使いましょう
}
```


### マーカー周り
`UnityEditor.Timeline.MarkerEditor` を継承し、かつ属性 `UnityEditor.Timeline.CustomTimelineEditorAttribute` を付けたクラスが該当します。
```csharp
[UnityEditor.Timeline.CustomTimelineEditorAttribute(typeof(MarkerSample))]
public sealed class MarkerSampleTimelineEditor : UnityEditor.Timeline.MarkerEditor {
  // ここに拡張を書きます
}
```


#### 異常のあるマーカーに「⚠️」を表示する
```csharp
public override UnityEditor.Timeline.MarkerDrawOptions GetMarkerOptions(UnityEngine.Timeline.IMarker marker) {
  // マーカーのエラーチェック
  // マーカーの場合、UnityTimeline側にあるエラーチェックはなし
  // string.Emptyをいれると「エラーはなし」
  // 何らかの文字列が入っていると、Timelineウィンドウ状のトラックに ⚠️ とWarningで表示されます
  var errorText = string.Empty;

  return new UnityEditor.Timeline.MarkerDrawOptions() {
    tooltip   = string.Empty,
    errorText = string.Empty,
  };
}

```

#### マーカー作成の検知
```csharp
public override void OnCreate(
  UnityEngine.Timeline.IMarker marker,
  UnityEngine.Timeline.IMarker clonedFrom
) {
  // ここに作成時にセットアップしたいことを書きます
  // copiedFrom は複製時のコピー元です
}
```

#### マーカー編集中の検知
ありません…。