この記事はUnity #2 Advent Calendar 2019の12/11(水)の記事です
はじめに
この記事ではUnityのTimeline機能に存在するTrackEditor
について解説します
ターゲット
Unityを使った開発を行っておりTimeline機能を拡張して実装を進めているエンジニアがメインターゲットです
Unityの基礎的な内容やTimeline、Editor拡張などで頻出する単語・機能などに関しての詳細な説明は省略しますので予めご了承ください
また、このTrackEditor
の機能はUnity2019.2から利用できるTimeline Versition 1.1.*以降から使えるようになるものです
※詳しくはchangelogを参照
得られるもの
UnityEditor.Timeline.TrackEditor
にどのような機能があるのかを知ることができます
これらは、Timelineのウィンドウに表示されるトラック部分の見た目を拡張するための機能なため、使い方を知ることでTimeline周りのツール開発で見た目の拡張をより広げることができます
この機能に合わせてクリップの見た目を拡張するClipEditor
の機能も追加されていますが、そちらに関してはこの記事にて紹介しています
本題
下記のような独自で定義したTrackを例にTrackEditor
の実装を紹介していきます
[TrackClipType(typeof(SampleClip)]
public class SampleTrack : TrackAsset
{
// 省略...
}
public class SampleClip : PlayableAsset, ITimelineClipAsset
{
// 省略...
}
TrackEditor
いきなり全コードを貼り付けていきますが、1つずつ見ていきましょう
/// <summary>
/// SampleTrackのEditor拡張
/// </summary>
[CustomTimelineEditor(typeof(SampleTrack))]
public class SampleTrackEditor : TrackEditor
{
/// <summary>
/// トラックの見た目を拡張するオプションデータを返すためのメソッド
/// </summary>
/// <param name="track">対象となるトラック</param>
/// <param name="binding">Bindされているオブジェクト</param>
/// <returns>TrackDrawOptions</returns>
public override TrackDrawOptions GetTrackOptions(TrackAsset track, Object binding)
{
Debug.Log($"SampleTrackEditor.GetTrackOptions: track={track}, binding={binding}");
return new TrackDrawOptions
{
errorText = base.GetErrorText(track, binding, TrackBindingErrors.All),
trackColor = base.GetTrackColor(track),
minimumHeight = TrackEditor.DefaultTrackHeight,
icon = null,
};
}
/// <summary>
/// トラックが新しく生成されたタイミングで呼ばれるメソッド
/// </summary>
/// <param name="track">生成されたトラック</param>
/// <param name="copiedFrom">コピー&ペーストで生成された場合のコピー元</param>
public override void OnCreate(TrackAsset track, TrackAsset copiedFrom)
{
Debug.Log($"SampleTrackEditor.OnCreate: track={track}, copiedFrom={copiedFrom}");
base.OnCreate(track, copiedFrom);
}
/// <summary>
/// トラックの内容に変更があったタイミングで呼ばれるメソッド
/// </summary>
/// <param name="track">変更があったトラック</param>
public override void OnTrackChanged(TrackAsset track)
{
Debug.Log($"SampleTrackEditor.OnTrackChanged: track={track}");
base.OnTrackChanged(track);
}
}
CustomTimelineEditorAttribute
[CustomTimelineEditor(typeof(SampleTrack))]
public class SampleTrackEditor : TrackEditor
SampleTrackEditor
をSampleTrack
のTrackEditor
として認識させるためにクラスの宣言に対してCustomTimelineEditorAttribute
を付ける必要があります
TrackDrawOptions TrackEditor.GetTrackOptions(TrackAsset track, Object binding)
/// <summary>
/// トラックの見た目を拡張するオプションデータを返すためのメソッド
/// </summary>
/// <param name="track">対象となるトラック</param>
/// <param name="binding">Bindされているオブジェクト</param>
/// <returns>TrackDrawOptions</returns>
public override TrackDrawOptions GetTrackOptions(TrackAsset track, Object binding)
トラックの見た目を拡張するための構造体を生成してこのメソッドで返却することでそのトラックの見た目を変更することができます
引数から分かるようにトラック単位で呼び出されるので同じトラックの型であっても内容によって見た目を変更するといったことが可能です
現状だと変更できる見た目はTrackDrawOptions
の内容に限定されてしまいます
※後述で解説
bindオブジェクト
引数のbinding
にはそのトラックにバインドされているオブジェクトが入ってきます
今回はSampleTrack
のクラス宣言時にTrackBindingTypeAttribute
を付けていないため、トラックにオブジェクトをバインドしない実装になっています
この場合はbinding
は常にnull
が入ってきます
また、この**バインドされているオブジェクトの型を取得するためのGetBindingType()
**というメソッドが用意されています
bindingに値が入っているかに関係なくTrackAsset
だけで掘り起こしてくれるので、型情報が必要な場合はこちらを利用しましょう
呼ばれる頻度と処理負荷
トラックの再描画命令が飛ぶたびに呼ばれます
マウスオーバーするだけでも呼ばれたりするのでかなりの頻度で呼び出されます
更にそのトラックの数だけ呼び出されるので、ここでの処理はあまり複雑なものにしてしまうとあっという間に重くなります
処理結果をキャッシュしたりして毎回負荷の高い処理が走らないようにする工夫が必要です
TrackDrawOptions
return new TrackDrawOptions
{
// エラー表示に使うテキスト
errorText = base.GetErrorText(track, binding, TrackBindingErrors.All),
// トラックの色
trackColor = base.GetTrackColor(track),
// トラックの最小の高さ
minimumHeight = TrackEditor.DefaultTrackHeight,
// アイコン画像
icon = null,
};
トラックに対してどのような見た目の変更ができるのかをまとめているのがTrackDrawOptions
構造体です
string TrackDrawOptions.errorText
エラー表示に利用するテキストを設定します
テキストを設定するとアイコンがワーニング時のものになり、アイコンにマウスオーバーすると設定したテキストが表示されます
注意しなければならないのは自分でエラーかどうかを判断してエラーだった場合にこのプロパティにテキストを設定するという仕様だということです
エラーがない(正常動作をしている)場合はstring.Empty
(つまり空文字)を入れます
基底クラスのTrackEditor
には**エラーかどうかを判断した上でテキストを返すGetErrorText()
**というメソッドが用意されています
また、このメソッドではTrackBindingErrors
というビットフラグを使ってどのような内容をエラーとして含めるかを決めることもできます
ビット演算を使っているのになぜかFlagsAttribute
が付いていない
基本的にはこのメソッドで標準のエラー判定をしつつ、更に自前でエラー判定が必要であれば追加で判定をしていくという実装をすることになるでしょう
Color TrackDrawOptions.trackColor
トラックの色を指定することができます
この処理内容は、トラック側のクラス宣言時にTrackColorAttribute
で設定するアプローチと同様の内容です
Attributeによる指定ではトラックの型に対して1種類の色しか指定できませんでしたが、この方法では同じトラックの型でも色を使い分けることができます
もしどちらにも色の指定があった場合はこの**TrackDrawOptions
で設定した色が優先**されます
また、TrackColorAttribute
で設定されている色はGetTrackColor()
で取得することができます
float TrackDrawOptions.minimumHeight
トラックの最小の高さを設定することができます
ClipEditor
による拡張でクリップの見た目を作る際に、デフォルトの高さでは表現し切れないケースでもここで高さを変更することで対応することが可能です
Timelineのウィンドウでもトラックの高さを変更することが可能ですが、ここで指定した高さよりも小さくならないようになります
Texture2D TrackDrawOptions.icon
トラックのアイコン表示を設定できます
ここで設定したアイコンはTimelineウィンドウの各トラックの名前が表示される部分とトラックを選択した際に表示されるそのトラックのインスペクター表示部分のヘッダーに使用されます
キャラクター単位で同じようなトラックを使うような場合、キャラクターごとにアイコン画像を用意するなどして同一種別のトラックでも設定している内容を視覚的に間違えにくい状態を作れそうです
void OnCreate(TrackAsset track, TrackAsset copiedFrom)
/// <summary>
/// トラックが新しく生成されたタイミングで呼ばれるメソッド
/// </summary>
/// <param name="track">生成されたトラック</param>
/// <param name="copiedFrom">コピー&ペーストで生成された場合のコピー元</param>
public override void OnCreate(TrackAsset track, TrackAsset copiedFrom)
対応するトラックが生成されたタイミングを取得することができます
前述のGetTrackOptions()
メソッドがかなりの頻度で呼び出されるため、トラックの生成時に1度処理すれば十分なものはここで処理してしまい、GetTrackOptions()
メソッドでは既に処理されたその内容を使い回すような実装にするとパフォーマンスを稼げるでしょう
ただし、 この**TrackEditor
は作られたトラックごとにインスタンスが存在するわけではない**ので値のキャッシングには注意が必要です ※後述で解説
引数に渡されてくるTrackAsset
の情報をキーにして保持しておくのがよいでしょう
複製されたトラック
2番目の引数copiedFrom
にはコピー元のトラックが渡されてきます
ここは新規でトラックが生成された場合はnull
が入ってくるので、ここを見ることで今回生成されたトラックが新規作成されたものなのか複製されたものなのかを判断することができます
Unity標準のActivateTrack
では、ここを使って新規トラックの作成の場合は一緒にクリップも生成するという挙動を作っています
参考にできそうです
void OnTrackChanged(TrackAsset track)
/// <summary>
/// トラックの内容に変更があったタイミングで呼ばれるメソッド
/// </summary>
/// <param name="track">変更があったトラック</param>
public override void OnTrackChanged(TrackAsset track)
トラックの内容が変更されたかどうかを検知することができます
ただし2019.3.0f1で動作確認を取ったところ、関係するところに少しでも変更が入ると何でも通知が飛んできてしまうようでちょっと使いこなすのが大変そうな雰囲気でした
どういう変更だったかのイベント種別みたいなのも引数で欲しいな
試してみたのは以下の内容です
トラックの名前を変更する
こちらは名前を変更したトラックが引数に入ってくる形で1度だけ呼び出されました
トラック・クリップの追加・移動・削除
- トラックを追加する(
SampleTrack
) - トラックを追加する(
SampleTrack以外
) - トラックを移動する(
SampleTrack
) - トラックを移動する(
SampleTrack以外
) - トラックを削除する(
SampleTrack
) - トラックを削除する(
SampleTrack以外
) - クリップを追加する(
SampleClip
) - クリップを追加する(
SampleClip以外
) - クリップを移動する(
SampleClip
) - クリップを移動する(
SampleClip以外
) - クリップを削除する(
SampleClip
) - クリップを削除する(
SampleClip以外
)
これらの動作は同一Timeline内に存在するSampleTrack
の数だけ呼び出されました
※要するに全てに変更があった扱い
親Timelineから子Timelineへ移動
ControlTrack
でTimelineを階層構造にしている際に親Timelineから子Timelineへの移動を行った場合の挙動です
こちらは子Timelineに存在するSampleTrack
のみ呼び出されました
(ただしSampleTrack
の数だけ呼び出される)
子Timelineから親Timelineへ移動
上記の状態から親Timelineを再度ウィンドウに表示させる場合の挙動です
こちら逆に親Timelineに存在するSampleTrack
のみ呼び出されました
(ただしSampleTrack
の数だけ呼び出される)
SampleTrackEditor
のインスタンス
また、OnTrackChanged()
の挙動を調べる際にSampleTrackEditor
のインスタンスがどのように扱われているかも整理できました
同一Timeline内では1つのSampleTrackEditor
のインスタンスが使い回され、呼び出されるメソッドの引数に各SampleTrack
のインスタンスが入ってくるという挙動をしていました
しかし、子Timelineと組み合わせた場合は親Timelineと子TimelineでSampleTrackEditor
のインスタンスが違っていました
どうやら**TimelineAsset
(もしくはTimelineAssetのView)ごとにSampleTrackEditor
のインスタンスが生成されている**ようでした
これらの挙動は実際に拡張する上でキャッシング処理を作る際に意識しておいたほうが良いでしょう
おわりに
今回はTrackEditor
でどんなことができるのか深堀りしてみました
まだまだやれることは限られていますが、少しずつTimelineウィンドウ上もカスタマイズできるようになってきています
Timeline機能はカットシーンなどのアセット作成をする上で非常に強力なツールですので、見た目もカスタマイズしてプロジェクト仕様に合ったツール開発がドンドンできるようになっていけばいいなと思っています
TrackEditor
だけでもそこそこなボリュームになってしまったので、また時間を作って今度はClipEditor
の方も掘り下げて行ければ良いなと考えています
Unityさん、もっとカスタマイズできるようにして〜><