タイムラインの延長:実用的なガイド

Unity は Unity 2017.1 とともに Timeline をリリースしましたが、それ以来、それに関する多くのフィードバックをいただいています。多くの開発者と話し、フォーラムでユーザーに返信した結果、多くのユーザーがタイムラインを単なるシーケンス ツール以上の用途に使用したいと考えていることがわかりました。私はすでに、タイムラインを従来とは異なる用途で使用する方法について、これについて数回講演しています (たとえば、Unite Austin 2017で)。
タイムラインは最初から拡張性を主な目標として設計されました。この機能を設計したチームは、ユーザーが組み込みのクリップやトラックに加えて独自のクリップやトラックを作成したいと考えていることを常に念頭に置いていました。そのため、Timeline を使用したスクリプト作成に関する質問が数多くあります。Timeline の基盤となるシステムは強力ですが、初心者にとっては扱いが難しい場合があります。
しかし、まずタイムラインとは何でしょうか?これは、アニメーション クリップ、音楽、サウンド エフェクト、カメラ ショット、パーティクル エフェクト、その他のタイムラインなど、さまざまな要素を順序付けるリニア編集ツールです。本質的には、Premiere®、After Effects®、Final Cut® などのツールと非常によく似ていますが、リアルタイム再生用に設計されている点が異なります。
タイムラインの基本についてさらに詳しく知りたい場合は、Unity マニュアルの タイムラインのドキュメント セクション を参照することをお勧めします。これらの概念を広範囲に活用する予定です。
タイムラインは Playables API上に実装されています。
これは、複数のデータ ソース (アニメーション、オーディオなど) を読み取ってミックスし、出力を通じて再生できる強力な API のセットです。このシステムは、正確なプログラム制御を提供し、オーバーヘッドが低く、パフォーマンスに合わせて調整されています。ちなみに、これは Animator コンポーネントを駆動するステート マシンの背後にあるフレームワークと同じであり、Animator 用にプログラミングしたことがある場合は、おそらくいくつかの馴染みのある概念が見られるでしょう。
基本的に、タイムラインの再生が開始されると、Playables と呼ばれるノードで構成されたグラフが構築されます。これらは、PlayableGraphと呼ばれるツリーのような構造に編成されます。
注:シーン内の PlayableGraph のツリー (アニメーター、タイムラインなど) を視覚化したい場合は、PlayableGraph Visualizerというツールをダウンロードできます。この投稿では、これを使用して、さまざまなカスタム クリップのグラフを視覚化します。
ここで、タイムラインを拡張する方法を示す 3 つの簡単な例を紹介します。基礎を築くために、タイムラインにスクリプトを追加する最も簡単な方法から始めます。その後、ほとんどの機能を活用できるように、より多くのコンセプトが徐々に追加されます。
この投稿で使用したすべての例を 小さなデモ プロジェクト にパッケージ化しました。ぜひダウンロードして、一緒にご覧ください。それ以外の場合は、投稿自体を楽しむことができます。
注:アセットについては、各例のクラスを区別するためにプレフィックスを使用しました (「Simple_」、「Track_」、「Mixer_」など)。以下のコードでは、読みやすさを考慮してこれらのプレフィックスは省略されています。
この最初の例は非常に単純です。目標は、カスタム クリップを使用して Light コンポーネントの色と強度を変更することです。カスタム クリップを作成するには、次の 2 つのスクリプトが必要です。
- データ用:PlayableAssetからの継承
- ロジックの1つ:PlayableBehaviourからの継承
Playable API の中心的な信条は、ロジックとデータの分離です。このため、まず PlayableBehaviourを作成し、その中で次のように実行したいことを記述する必要があります。
public class LightControlBehaviour : PlayableBehaviour
{
public Light light = null;
public Color color = Color.white;
public float intensity = 1f;
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
if (light != null)
{
light.color = color;
light.intensity = intensity;
}
}
}何が起きてる?まず、変更したいライトのプロパティに関する情報があります。また、PlayableBehaviour に はオーバーライドできる ProcessFrame というメソッドがあります。
ProcessFrame は 更新ごとに呼び出されます。このメソッドでは、ライトのプロパティを設定できます。PlayableBehaviourでオーバーライドできる メソッドのリストは 次のとおりです。 次に、カスタム クリップ用の PlayableAsset を作成します。
public class LightControlAsset : PlayableAsset
{
public ExposedReference<Light> light;
public Color color = Color.white;
public float intensity = 1.0f;
public override Playable CreatePlayable (PlayableGraph graph, GameObject owner)
{
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);
var lightControlBehaviour = playable.GetBehaviour();
lightControlBehaviour.light = light.Resolve(graph.GetResolver());
lightControlBehaviour.color = color;
lightControlBehaviour.intensity = intensity;
return playable;
}
}PlayableAsset には 2 つの目的があります。まず、タイムライン アセット自体内でシリアル化されているため、クリップ データが含まれます。次に、Playable グラフに最終的に表示される PlayableBehaviour を構築します。
最初の行を見てください:
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);これにより、新しい Playable が作成され、カスタム動作である LightControlBehaviourがそれにアタッチされます。その後、PlayableBehaviour でライトのプロパティを設定できます。
ExposedReferenceはどうですか?PlayableAsset は アセットであるため、シーン内のオブジェクトを直接参照することはできません。ExposedReference は、CreatePlayable が呼び出されたときにオブジェクトが解決されるという約束として機能します。
これで、タイムラインに再生可能なトラックを追加し、その新しいトラックを右クリックしてカスタム クリップを追加できるようになりました。結果を確認するには、クリップにライト コンポーネントを割り当てます。
このシナリオでは、組み込みの再生可能トラックは、先ほど作成したような単純な再生可能クリップを受け入れることができる汎用トラックです。より複雑な状況では、専用のトラックにクリップをホストする必要があります。
最初の例の注意点の 1 つは、カスタム クリップを追加するたびに、各クリップに Light コンポーネントを割り当てる必要があることです。クリップの数が多い場合は面倒な作業になる可能性があります。この問題は、トラックの バインドされたオブジェクトを使用することで解決できます。

トラックにはオブジェクトまたはコンポーネントをバインドできます。つまり、トラック上の各クリップはバインドされたオブジェクトに対して直接操作できます。これは非常に一般的な動作であり、実際にアニメーション、アクティベーション、Cinemachine トラックはこのように動作します。
複数のクリップを持つライトのプロパティを変更する場合は、バインドされたオブジェクトとしてライト コンポーネントを要求するカスタム トラックを作成できます。カスタム トラックを作成するには、TrackAssetを拡張する別のスクリプトが必要です。
[TrackClipType(typeof(LightControlAsset))]
[TrackBindingType(typeof(Light))]
public class LightControlTrack : TrackAsset {}ここには 2 つの属性があります。
- TrackClipType は 、トラックが受け入れる PlayableAsset タイプを指定します。この場合は、カスタム LightControlAssetを指定します。
- TrackBindingType は 、トラックが要求するバインディングのタイプを指定します (GameObject、コンポーネント、またはアセットのいずれかになります)。この場合、Light コンポーネントが必要になります。
また、PlayableAsset と PlayableBehaviour を トラックで動作させるには、少し変更する必要があります。参考までに、不要になった行はコメントアウトしておきました。
public class LightControlAsset : PlayableAsset
{
public LightControlBehaviour template;
public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) {
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph, template);
return playable;
}
}PlayableBehaviour には Light 変数は必要ありません。この場合、ProcessFrame メソッドはトラックのバインドされたオブジェクトを直接提供します。必要なのは、オブジェクトを適切な型にキャストすることだけです。それはいいですね!
public class LightControlAsset : PlayableAsset
{
//public ExposedReference<Light> light;
public Color color = Color.white;
public float intensity = 1f;
public override Playable CreatePlayable (PlayableGraph graph, GameObject owner)
{
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);
var lightControlBehaviour = playable.GetBehaviour();
//lightControlBehaviour.light = light.Resolve(graph.GetResolver());
lightControlBehaviour.color = color;
lightControlBehaviour.intensity = intensity;
return playable;
}
}PlayableAsset は 、Light コンポーネントの ExposedReference を保持する必要がなくなりました。参照はトラックによって管理され、PlayableBehaviourに直接渡されます。
タイムラインに LightControl トラックを追加し、それにライトをバインドできます。これで、そのトラックに追加する各クリップは、トラックにバインドされている Light コンポーネントに対して動作するようになります。
グラフ ビジュアライザーを使用してこのグラフを表示すると、次のようになります。

予想どおり、右側のクリップは 5 つのブロックとして 1 つにまとめられています。1 つのボックスをトラックとして考えることができます。次に、すべてがタイムライン(紫色のボックス)に入ります。
注:「Playable」と呼ばれるピンク色のボックスは、実際には Unity が作成するミキサー Playable です。だからクリップと同じ色なんです。ミキサーとは何ですか?次の例ではミキサーについて説明します。
タイムラインは、クリップの重なりをサポートし、クリップ間のブレンドやクロスフェードを作成します。カスタム クリップもブレンドをサポートします。ただし、これを有効にするには、すべてのクリップのデータにアクセスしてブレンドするミキサーを作成する必要があります。
ミキサーは、先ほど使用した LightControlBehaviour と同様に、PlayableBehaviourから派生します。実際には、ProcessFrame 関数を引き続き使用します。主な違いは、この Playable が、関数 CreateTrackMixer を オーバーライドすることによって、トラック スクリプトによってミキサーとして明示的に宣言されていることです。LightControlTrack スクリプトは次のようになります。
[TrackClipType(typeof(LightControlAsset))]
[TrackBindingType(typeof(Light))]
public class LightControlTrack : TrackAsset
{
public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount) {
return ScriptPlayable<LightControlMixerBehaviour>.Create(graph, inputCount);
}
}
このトラックの再生可能グラフが作成されると、新しい動作 (ミキサー) も作成され、トラック上のすべてのクリップに接続されます。
また、ロジックを PlayableBehaviour からミキサーに移動することも必要です。そのため、PlayableBehaviour は完全に空に見えるようになります。
public class LightControlBehaviour : PlayableBehaviour
{
public Color color = Color.white;
public float intensity = 1f;
}
基本的に、実行時に PlayableAsset から取得されるデータのみが含まれます。一方、ミキサーでは、すべてのロジックが ProcessFrame 関数に含まれます。
public class LightControlMixerBehaviour : PlayableBehaviour
{
// NOTE: This function is called at runtime and edit time. Keep that in mind when setting the values of properties.
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
Light trackBinding = playerData as Light;
float finalIntensity = 0f;
Color finalColor = Color.black;
if (!trackBinding)
return;
int inputCount = playable.GetInputCount (); //get the number of all clips on this track
for (int i = 0; i < inputCount; i++)
{
float inputWeight = playable.GetInputWeight(i);
ScriptPlayable<LightControlBehaviour> inputPlayable = (ScriptPlayable<LightControlBehaviour>)playable.GetInput(i);
LightControlBehaviour input = inputPlayable.GetBehaviour();
// Use the above variables to process each frame of this playable.
finalIntensity += input.intensity * inputWeight;
finalColor += input.color * inputWeight;
}
//assign the result to the bound object
trackBinding.intensity = finalIntensity;
trackBinding.color = finalColor;
}
}ミキサーはトラックにあるすべてのクリップにアクセスできます。この場合、現在ブレンドに参加しているすべてのクリップの強度と色の値を読み取る必要があるため、 for ループを使用してそれらを反復処理する必要があります。各サイクルで、入力 (GetInput(i)) にアクセスし、各クリップの重み (GetInputWeight(i)) を使用して最終値を構築し、そのクリップがブレンドにどの程度寄与しているかを取得します。
つまり、2 つのクリップがブレンドされているとします。1 つは赤を、もう 1 つは白をブレンドしています。ブレンドが 4 分の 1 まで進むと、色は 0.25 * Color.red + 0.75 * Color.whiteとなり、わずかに薄い赤になります。
ループが終了したら、合計をバインドされた Light コンポーネントに適用します。これにより、次のようなものを作成できます。

赤いボックスがまさにあなたがプログラムしたミキサー Playable であり、完全に制御できるようになっていることがわかります。これは、ミキサーが Unity によって提供されるデフォルトのものであった上記の例 2 とは対照的です。
また、グラフはブレンドの途中にあるため、緑色のボックス 2 と 3 の両方にミキサーに接続する明るい線があり、それぞれの重みが 0.5 程度であることを示しています。
ミキサーでブレンドを実装するときは常に、ロジックを決定するのはユーザー次第であることに留意してください。2 つの色をブレンドするのは簡単ですが、AI システム内の異なる AI 状態を表す 2 つのクリップをブレンドすると (極端な例ですが) どうなるでしょうか?UI に 2 行のダイアログがありますか?ストップモーションアニメーションで 2 つの静的ポーズをどのようにブレンドしますか?ブレンドは連続的ではなく、「段階的」である可能性があります(つまり、ポーズは互いに変形しますが、個別の増分になります)。0, 0.25, 0.5, 0.75, 1).
この強力なシステムを活用すれば、シナリオはエキサイティングで無限に広がります。
このガイドの最後のステップとして、前の例に戻り、「テンプレート」と呼ばれるものを使用してデータを移動する別の方法を実装してみましょう。このパターンの大きな利点の 1 つは、テンプレートのプロパティをキーフレーム化できるため、タイムライン上で直接カスタム クリップのアニメーションを作成できることです。
前の例では、PlayableAsset と PlayableBehaviourの両方に、Light コンポーネント、色、強度への参照がありました。データは Inspector の PlayableAsset に設定され、実行時にグラフの作成時に PlayableBehaviour にコピーされました。
これは有効な方法ですが、データが重複するため、常に同期を保つ必要があります。これは簡単に間違いにつながる可能性があります。代わりに、PlayableAssetで参照を作成することにより、PlayableBehaviour「テンプレート」の概念を使用できます。まず、LightControlAsset を次のように書き換えます。
public class LightControlAsset : PlayableAsset
{
public LightControlBehaviour template;
public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) {
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph, template);
return playable;
}
}LightControlAsset に は、値自体ではなく、LightControlBehaviour への参照のみが含まれるようになりました。以前よりもさらにコードが少なくなりました!
LightControlBehaviour は変更しないでください。
[System.Serializable]
public class LightControlBehaviour : PlayableBehaviour
{
public Color color = Color.white;
public float intensity = 1f;
}テンプレートへの参照により、タイムラインでクリップを選択すると、このインスペクターが自動的に生成されるようになりました。

このスクリプトを配置したら、アニメーションを作成する準備が整います。新しいクリップを作成すると、トラック ヘッダーに円形の赤いボタンが表示されることに注意してください。つまり、クリップにアニメーターを追加しなくてもキーフレームを設定できるようになりました。赤いボタンをクリックしてクリップを選択し、キーを作成する場所に再生ヘッドを配置して、そのプロパティの値を変更するだけです。
白いボックス ボタンをクリックして曲線ビューを展開し、キーフレームによって作成された曲線を表示することもできます。

もう一つの特典があります。タイムライン クリップをダブルクリックすると、Unity がアニメーション パネルを開いてタイムラインにリンクします。次のボタンが表示されると、それらがリンクされていることがわかります。

このような場合、タイムラインとアニメーション ウィンドウの両方でスクラブすることができ、再生ヘッドは同期されたままになるので、キーフレームを完全に制御できます。アニメーション ウィンドウでアニメーションを変更して、より快適な環境でキーフレームを操作できるようになりました。

このビューでは、アニメーション カーブとドープシートを最大限に活用して、カスタム クリップのアニメーションを細かく調整できます。
注意:このようにアニメーション化すると、アニメーション クリップが作成されます。これらはタイムライン アセットの下にあります:

この投稿が、スクリプトを使用して Timeline を次のレベルに引き上げたときに Timeline が提供できる無限の可能性についての有益な紹介になったことを願っています。
ご質問、フィードバック、タイムラインの作成内容を Twitter で お知らせください。
