Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

Organization

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

この記事はUnity #3 Advent Calendar 2019の12/22(日)の記事です

はじめに

この記事ではUnityのTimeline機能に存在するClipEditorについて解説します

ターゲット

Unityを使った開発を行っておりTimeline機能を拡張して実装を進めているエンジニアがメインターゲットです
Unityの基礎的な内容やTimeline、Editor拡張などで頻出する単語・機能などに関しての詳細な説明は省略しますので予めご了承ください

また、このClipEditorの機能はUnity2019.2から利用できるTimeline Versition 1.1.*以降から使えるようになるものです
※詳しくはchangelogを参照

得られるもの

UnityEditor.Timeline.ClipEditorにどのような機能があるのかを知ることができます
これらは、Timelineのウィンドウに表示されるクリップ部分の見た目を拡張するための機能なため、使い方を知ることでTimeline周りのツール開発で見た目の拡張をより広げることができます

このClipEditorと同様にトラックに対する見た目の拡張に関する記事も書いていますので合わせて参照ください

本題

下記のような独自で定義したTrackを例にClipEditorの実装を紹介していきます

SampleTrack.cs
[TrackClipType(typeof(SampleClip)]
public class SampleTrack : TrackAsset
{
    // 省略...
}
SampleClip.cs
public class SampleClip : PlayableAsset, ITimelineClipAsset
{
    // 省略...
}

ClipEditor

まず初めにコードの全容を一気に公開し、その後で1つずつ掘り下げていく形で各機能を紹介します

using System.Collections.Generic;
using System.Linq;
using UnityEditor.Timeline;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;

[CustomTimelineEditor(typeof(SampleClip))]
public class SampleClipEditor : ClipEditor
{
    public override ClipDrawOptions GetClipOptions(TimelineClip clip)
    {
        return new ClipDrawOptions
        {
            errorText = GetErrorText(clip),
            highlightColor = GetDefaultHighlightColor(clip),
            icons = Enumerable.Empty<Texture2D>(),
            tooltip = "Tooltip"
        };
    }

    public override void DrawBackground(TimelineClip clip, ClipBackgroundRegion region)
    {
        base.DrawBackground(clip, region);
    }

    public override void OnCreate(TimelineClip clip, TrackAsset track, TimelineClip clonedFrom)
    {
        base.OnCreate(clip, track, clonedFrom);
    }

    public override void OnClipChanged(TimelineClip clip)
    {
        base.OnClipChanged(clip);
    }

    public override void GetSubTimelines(TimelineClip clip, PlayableDirector director, List<PlayableDirector> subTimelines)
    {
        base.GetSubTimelines(clip, director, subTimelines);
    }
}

CustomTimelineEditorAttribute

[CustomTimelineEditor(typeof(SampleClip))]
public class SampleClipEditor : ClipEditor

SampleClipEditorSampleClipClipEditorとして認識させるためにクラスの宣言に対してCustomTimelineEditorAttributeを付ける必要があります

ClipDrawOptions GetClipOptions(TimelineClip clip)

/// <summary>
/// クリップの描画を拡張するためのオプションデータを返すメソッド
/// </summary>
/// <param name="clip">対象となるクリップの情報</param>
/// <returns>ClipDrawOptions</returns>
public override ClipDrawOptions GetClipOptions(TimelineClip clip)

クリップの見た目を拡張するための構造体を生成してこのメソッドで返却することで、そのクリップの見た目を変更することができます

引数から分かるようにクリップ単位で呼び出されるので同じクリップの型であっても内容によって見た目を変更するといったことが可能です

トラックの方ではこのDrawOptions構造体のみでしか見た目の拡張はできませんでしたが、クリッでは背景の拡張も可能です
※後述で解説

ClipDrawOptions

return new ClipDrawOptions
{
    // エラー表示に使うテキスト
    errorText = GetErrorText(clip),
    // クリップの色
    highlightColor = GetDefaultHighlightColor(clip),
    // アイコン画像
    icons = Enumerable.Empty<Texture2D>(),
    // ツールチップ表示
    tooltip = "Tooltip"
};

クリップに対してどのような見た目の変更ができるのかをまとめているのがClipDrawOptionsです

string ClipDrawOption.errorText

エラー表示に利用するテキストを設定します
テキストを設定するとアイコンがワーニング時のものになり、アイコンにマウスオーバーすると設定したテキストが表示されます

注意しなければならないのは自分でエラーかどうかを判断してエラーだった場合にこのプロパティにテキストを設定するという仕様だということです
エラーがない(正常動作をしている)場合はstring.Empty(つまり空文字)を入れます

基底クラスのClipEditorにはエラーかどうかを判断した上でテキストを返すGetErrorText()というメソッドが用意されています

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

Object TimelineClip.asset プロパティに対応するPlayableAsset(今回の例で言うとSampleClip)の参照が取れるので、そこから更に中身を見てエラー判定をしましょう

Color ClipDrawOption.highlightColor

クリップの色を指定することができます

Color GetDefaultHighlightColor(TimelineClip clip)メソッドを利用するとトラック側に設定されている色を取得することができます

しかし2019/12/22現在で利用できるVer1.2.9まででは、ここで取得できるトラックの色はTrackColorAttributeで設定した色のみしか取れません
TrackEditorで設定したColor TrackDrawOptions.trackColorの値は取得できないため注意してください

TrackEditor CustomTimelineEditorCache.GetTrackEditor(TrackAsset track)というメソッドからそのトラックに対応したTrackEditorを取得できるようになっていますが、CustomTimelineEditorCacheクラスがpublicではないため、利用するにはReflectionの機能を使う必要があります

IEnumerable<Texture2D> ClipDrawOption.icons

クリップのアイコン表示を設定できます

ここで設定したアイコンはTimelineWindowの各クリップの名前が表示される部分の右隣に表示されます
トラックとは違い任意の個数を設定できます(横に並ぶ形で表示される)

しかしここで設定した内容はインスペクター側のアイコン表示には使われていないようです(なぜなのか)

string ClipDrawOption.tooltip

クリップのツールチップ表示を設定できます

void DrawBackground(TimelineClip clip, ClipBackgroundRegion region)

/// <summary>
/// クリップの背景を描画するタイミングで呼ばれるメソッド
/// </summary>
/// <param name="clip">対象となるクリップ</param>
/// <param name="region">クリップの背景を描画するために必要な情報</param>
public override void DrawBackground(TimelineClip clip, ClipBackgroundRegion region)

対象となるクリップの背景の描画を丸ごとカスタマイズすることができます

AudioTrackではこの機能を使ってクリップの背景に波形を表示する拡張を行っています
ただし、この部分はエディタ拡張をする際に利用するOnGUIイベントとほぼ同等の頻度で呼ばれるものなので、パフォーマンスに気を配る必要があります
AudioTrackで使われるAudioPlayableAssetEditorクラスでもTimelineClipごとにWaveformPreviewのインスタンスをキャッシュしていますので参考にしてみると良いでしょう

void OnCreate(TimelineClip clip, TrackAsset track, TimelineClip clonedFrom)

/// <summary>
/// クリップが新しく生成されたタイミングで呼ばれるメソッド
/// </summary>
/// <param name="clip">生成されたクリップ</param>
/// <param name="track">クリップの親となるトラック</param>
/// <param name="clonedFrom">複製元のクリップ</param>
public override void OnCreate(TimelineClip clip, TrackAsset track, TimelineClip clonedFrom)

対象となるクリップが生成されたタイミングを取得することができます

GetClipOptions()DrawBackground()がかなりの頻度で呼び出されるので生成時に1度だけ処理すれば良いものはここにまとめてキャッシュするような作りにしましょう

void OnClipChanged(TimelineClip clip)

/// <summary>
/// クリップの内容に変更があったタイミングで呼ばれるメソッド
/// </summary>
/// <param name="clip">変更があったクリップ</param>
public override void OnClipChanged(TimelineClip clip)

クリップの内容が変更されたかどうかを検知することができます

以前に若干強引な方法でクリップの内容に合わせて表示名を変更する記事を書きましたが、このタイミングを使えば責務的にもスッキリとした実装にできそうです

void GetSubTimelines(TimelineClip clip, PlayableDirector director, List<PlayableDirector> subTimelines)

/// <summary>
/// クリップの内容に紐付いて子のTimelineがある場合にそれをTimelineWindowに伝えるためのメソッド
/// </summary>
/// <param name="clip">対象となるクリップ</param>
/// <param name="director">クリップを再生しているPlayableDirector</param>
/// <param name="subTimelines">子Timelineの追加先List</param>
public override void GetSubTimelines(TimelineClip clip, PlayableDirector director, List<PlayableDirector> subTimelines)

このメソッドは他のものとは違って少し特殊です

既存の実装ではControlTrackの実装に使われています

ControlTrackはクリップ単位でシーン上のオブジェクトやPrefabを設定しそれを操作できるトラックですが、オプションとしてPlayableDirectorに時間を伝播できる仕様になっています
この際に、PlayableDirectorがアタッチされたGameObjectが設定されているクリップは、そのクリップをダブルクリックすることでそのPlayableDirectorに設定されているTimelineAsset今開いているTimelineAssetの子Timelineとして開くことができます

第一引数のTimelineClipに関連するTimelineAssetがセットされたPlayableDirectorの参照を第三引数のList<PlayableDirector> subTimelinesにAddすることでTimelineウィンドウの機能として対象のクリップからその子Timelineへジャンプすることできるようになります

複数のPlayableDirectorをAddした場合、恐らく最初?にAddしたPlayableDirectorTimelineAssetが子TimelineとしてTimelineウィンドウとして表示されます
その他のAddしたPlayableDirectorを表示したい場合は右クリックメニューからEdit Sub-Timelinesの項目を選ぶことで任意のPlayableDirectorを選ぶ形で表示します

あまり使わない内容ではあると思いますが、ControlTrackで使われるControlPlayableAssetEditorクラスに実装内容がありますので気になる方はチェックしてみましょう

おわりに

今回はClipEditorでどんなことができるのかを深堀りしてみました

トラックの拡張とは違い、背景の描画について拡張できたりするのが特徴です

上記記事と合わせて、皆さんが作る独自のTimeline編集環境が少しでもよいものになると幸いです

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
3
Help us understand the problem. What is going on with this article?