LoginSignup
11
6

More than 3 years have passed since last update.

[Unity][Timeline] Unity2019.2から使えるようになったTrackEditorでトラックの見た目を拡張する

Last updated at Posted at 2019-12-10

この記事は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の実装を紹介していきます

SampleTrack.cs
[TrackClipType(typeof(SampleClip)]
public class SampleTrack : TrackAsset
{
    // 省略...
}
SampleClip.cs
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

SampleTrackEditorSampleTrackTrackEditorとして認識させるためにクラスの宣言に対して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

エラー表示に利用するテキストを設定します
テキストを設定するとアイコンがワーニング時のものになり、アイコンにマウスオーバーすると設定したテキストが表示されます
スクリーンショット 2019-12-11 1.52.53.png
注意しなければならないのは自分でエラーかどうかを判断してエラーだった場合にこのプロパティにテキストを設定するという仕様だということです
エラーがない(正常動作をしている)場合はstring.Empty(つまり空文字)を入れます

基底クラスのTrackEditorにはエラーかどうかを判断した上でテキストを返すGetErrorText()というメソッドが用意されています
また、このメソッドではTrackBindingErrorsというビットフラグを使ってどのような内容をエラーとして含めるかを決めることもできます
ビット演算を使っているのになぜかFlagsAttributeが付いていない

基本的にはこのメソッドで標準のエラー判定をしつつ、更に自前でエラー判定が必要であれば追加で判定をしていくという実装をすることになるでしょう

Color TrackDrawOptions.trackColor

トラックの色を指定することができます
スクリーンショット 2019-12-11 2.10.48.png
この処理内容は、トラック側のクラス宣言時にTrackColorAttributeで設定するアプローチと同様の内容です
Attributeによる指定ではトラックの型に対して1種類の色しか指定できませんでしたが、この方法では同じトラックの型でも色を使い分けることができます

もしどちらにも色の指定があった場合はこのTrackDrawOptionsで設定した色が優先されます

また、TrackColorAttributeで設定されている色はGetTrackColor() で取得することができます

float TrackDrawOptions.minimumHeight

トラックの最小の高さを設定することができます
スクリーンショット 2019-12-11 2.23.36.png
ClipEditorによる拡張でクリップの見た目を作る際に、デフォルトの高さでは表現し切れないケースでもここで高さを変更することで対応することが可能です

Timelineのウィンドウでもトラックの高さを変更することが可能ですが、ここで指定した高さよりも小さくならないようになります

Texture2D TrackDrawOptions.icon

トラックのアイコン表示を設定できます
ここで設定したアイコンはTimelineウィンドウの各トラックの名前が表示される部分とトラックを選択した際に表示されるそのトラックのインスペクター表示部分のヘッダーに使用されます
スクリーンショット 2019-12-11 2.42.46.png

キャラクター単位で同じようなトラックを使うような場合、キャラクターごとにアイコン画像を用意するなどして同一種別のトラックでも設定している内容を視覚的に間違えにくい状態を作れそうです

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さん、もっとカスタマイズできるようにして〜><

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6