Unity を検索

カスタム Timeline Marker の作成方法

2019年6月25日 カテゴリ: Engine & platform | 9 分 で読めます
取り上げているトピック
シェア

Is this article helpful for you?

Thank you for your feedback!

Unity 2019.1 から、Timeline が Marker(マーカー)に対応します!本記事ではカスタムマーカーの作成方法をご紹介します。

先のブログ記事にて、Timeline Signal を使用してイベントをトリガーする方法をご説明しました。Timeline Signal を設計した当初、私達は即座に、これを機能させるためにはクリップが使用できないことに気付きました。Signal の主な性質のひとつは「持続時間」を持たないことなので、新しいタイプのアイテムが必要となり、このために Timeline に Marker を追加することになりました。内部的には Signal は Marker を使用して実装されています。それでは、Timeline へのカスタムマーカーの追加方法を見て行きましょう。

本記事で使用されているコードとアセットはこちらからご入手いただけます

パート I:単純なマーカー

マーカーは、Timeline アセットに追加できる新しいアイテムで、時点を表すために使用されます。マーカーもクリップと同様、特性を持っています(Activation クリップ、Audio クリップ、Animation クリップなど)。これにより、独自のタイプのマーカーを作成してご自分のワークフローに適したものをビルドすることが可能となります。

新しいタイプのマーカーの追加は、Marker クラスを継承するクラスを作成するだけで行えます。

public class SimpleMarker : UnityEngine.Timeline.Marker {}

完了です!これで、このカスタムマーカーをタイムラインマーカー領域のどのトラックにでも追加できます。

この時点では、この単純なマーカーは単に表示されているだけのアイテムに過ぎません。つまり、このマーカーはトリガーされてもコードを実行することはできません。これは、役に立たないという意味ではありません。マーカーはスナップポイントや注釈用にも使用できます(パート V をご参照ください)。またマーカーは、ランタイム中でもエディター内でも Timeline API からアクセス可能です。

マーカーにコードを実行させるには、マーカーと別のシステムを組み合わせる必要があります。このシステムの仕組みを学びたい方は、次の 2 つのパートをお読みください。必要なければ、飛ばしてパート IV からお読みいただいても問題ありません。

パート II:Playable Notification

Playable API を使用すると、PlayableGraph の処理中にオブジェクトに通知を送ることができます。Playable Notification を使用して、イベントが発生したことをターゲットのオブジェクトに知らせることができます。ここでは、単純なグラフを構築して手動で通知を送信してみましょう。

まず notification(通知)INotification インターフェースを実装するクラス)を作成します。

public class MyNotification : INotification
{
    public PropertyName id { get; }
}

id プロパティを使用して notification (通知)を一意的に識別することも可能です。これらの例では特にその必要はありませんので、デフォルトの実装を使用します。

次に、receiver(レシーバー)INotificationReceiver インターフェースを実装するクラス)が必要です。この例には、notification(通知)が受領された時間をプリントする receiver(レシーバー)が含まれいます。

class ReceiverExample : INotificationReceiver
{
   public void OnNotify(Playable origin, INotification notification, object context)
   {
       if (notification != null)
       {
           double time = origin.IsValid() ? origin.GetTime() : 0.0;
           Debug.LogFormat("Received notification of type {0} at time {1}", notification.GetType(), time);
       }
   }
}

下の例では、新しい PlayableGraph と新しい Playable Output を作成しました。ReceiverExample を(AddNotificationReceiver メソッドを使用して)Playable Output に追加しています。これで、m_Receiver インターフェースが、この出力(output)に対して送信された通知を受領できるようになりました。

これで、通知を送信する準備がすべて整いました。PushNotification メソッドを使用して、この Playable Output から新しい通知を プッシュすることができます。

public class ManualNotification : MonoBehaviour
       {
    PlayableGraph m_Graph;
    ReceiverExample m_Receiver;

    void Start()
    {
        m_Graph = PlayableGraph.Create("NotificationGraph");
        var output = ScriptPlayableOutput.Create(m_Graph, "NotificationOutput");

        //レシーバーを作成・登録する
        m_Receiver = new ReceiverExample();
        output.AddNotificationReceiver(m_Receiver);

        //出力から通知をプッシュする
        output.PushNotification(Playable.Null, new MyNotification());

        m_Graph.Play();
    }
}

ここで注意しなければならないことがあります。PushNotification を呼び出しても、通知はすぐに送られる訳ではなく、キューに追加されるだけです。つまり、通知はグラフが完全に処理されるまで蓄積されるということです。LateUpdate の段階になる直前に、キューに入ったすべての通知がグラフの出力に対して送信されます。すべての通知が送信されると、新しいフレームの開始前にキューが消去されます。

グラフが再生されると、引数として送られた通知によって、m_Receiver インスタンスに OnNotify メソッドが呼び出されます。 再生モードで遷移中にはコンソールに以下のメッセージが表示されます。

Received notification of type MyNotification at time 0

レシーバーは通知を正常に受領することができました。それでは、通知が送られる時間はどのように制御するのでしょうか?これを行うには、さらに他の要素が必要です。

パート III:TimeNotificationBehaviour

ここまでで、PlayableGraph 経由で通知を送信する方法が分かりました。では次に、通知をスケジュール設定して、その時間に通知が送られるようにしましょう。これは組み込みクラス TimeNotificationBehaviour を使用して行えます。このクラスは標準の PlayableBehaviour なので、どんなグラフにでも追加することができます。これに、正確な時間に通知を送信するロジックを用いれば良いだけです。先の例に少し手を加えてみましょう。

public class ScheduledNotification : MonoBehaviour
    {
   PlayableGraph m_Graph;
   ReceiverExample m_Receiver;

   void Start()
   {
       m_Graph = PlayableGraph.Create("NotificationGraph");
       var output = ScriptPlayableOutput.Create(m_Graph, "NotificationOutput");

       //レシーバーを作成・登録する
       m_Receiver = new ReceiverExample();
       output.AddNotificationReceiver(m_Receiver);

       //TimeNotificationBehaviour を作成する
       var timeNotificationPlayable = ScriptPlayable<TimeNotificationBehaviour>.Create(m_Graph);
       output.SetSourcePlayable(timeNotificationPlayable);

       //TimeNotificationBehaviour に通知を追加する
       var notificationBehaviour = timeNotificationPlayable.GetBehaviour();
       notificationBehaviour.AddNotification(2.0, new MyNotification());

       m_Graph.Play();
   }
}

以下の PlayableGraph が生成されました。

ご覧の通り、私は PushNotification を直接 Playable Output に呼び出すのではなく、この Output に TimeNotificationBehaviour を添付し、それに Notification を追加しています。この Behaviour(挙動)は、自動的に正しい時間に通知(notification)を出力(output)にプッシュしてくれます。コンソールの表示は以下のようになりました。

Received notification of type MyNotification at time 2.00363647006452

以上で、通知の送信される時間の制御方法が分かりました!

さて、しかし… なぜきっかり 2 秒の時点で送信されなかったのでしょうか?TimeNotificationBehaviour に通知を追加した時に、きっかり 2 秒に指定したはずではなかったでしょうか。

notificationBehaviour.AddNotification(2.0, new MyNotification());

実は、AddNotification メソッドでは精密な時間は保証されません。PlayableGraph の時間は、Unity が新しいフレームのレンダリングを開始した時に更新されます。ゲームのフレームレートによっては、PlayableGraph の評価時間と、通知を TimeNotificationBehaviour に追加する際に指定した時間とが、正確に一致しないことがあります。AddNotification メソッドが保証するのは「PlayableGraph の時間が通知トリガー時間を超えた時点ですぐに通知が送信される」ことです。

パート IV:MarkerNotification

これらの新しい API はすべて、PlayableGraph 内で手動で通知を送信したい場合には便利ですが、作業量が大きくなる可能性があります。しかし幸運なことに、Timeline は通知を扱うための適切な PlayableGraph を自動で生成することができます!

パート I では、マーカーについてご説明しました。ここで、INotification インターフェースを実装する新しいマーカーを作成してみましょう。

public class NotificationMarker : Marker, INotification
   {
   public PropertyName id { get; }
   }

Marker から継承され INotification を実装するクラスが、この通知をサポートする PlayableGraph の作成が必要である旨を Timeline に伝えます。このマーカーを空のタイムラインに追加した場合、以下のような PlayableGraph が作成されます。

これは、Timeline がそれ自体の PlayableBehaviour を追加している点を除いては、パート III で作成した PlayableGraph とほぼ同じです。しかし、自分で PlayableGraph を作成するよりは遥かに簡単にできました!

残されたステップは、誰が通知を受け取るかを特定することだけです。この規則は以下の通り、Signal と同じです。

  • マーカーがタイムラインのヘッダー領域にある場合:現在のタイムラインを再生する PlayableDirector を所有するオブジェクトが通知を受け取ります。
  • マーカーがトラック上にある場合:トラックに関連するオブジェクトが通知を受け取ります。

INotificationReceiver インターフェースを実装し、ターゲットのオブジェクト上に存在するコンポーネントはすべて、通知を受け取ります。

私の例では、2 つの NotificationMarker をタイムラインのヘッダー領域に追加しました。また、タイムラインを再生するオブジェクトに NotificationReceiver を追加しました。

コンソールからの出力は以下のようになります。

Received notification of type NotificationMarker at time 1.00330553948879

Received notification of type NotificationMarker at time 2.016666666666

この出力はパート III とまったく同じです。

通知をサポートする適切な PlayableGraph を生成するのは、INotification インターフェースを実装するマーカーのみです。以下、カスタムマーカーの各種作成方法の特徴を分かりやすくまとめた表をご覧ください。

パート V:Custom Style

カスタムマーカーは一般的な「ピン」のアイコンで表されますが、このアイコンはお好きな画像に変更していただけます。ここで、この方法をご説明するために Annotation(注釈)マーカーを作成します。

最初のステップはスタイルシートの作成です。スタイルシートを使用すると、エディターのビジュアルを拡張することができます。これを行うには、StyleSheets/Extensions フォルダー階層内の Editor フォルダー内に common.uss という名前のファイルを追加します。この例では以下の場所に新しいファイルを追加しました。

“5-Annotation/Editor/Stylesheets/Extensions/common.uss”

(Unity Style Sheet 用の)USS ファイルは、CSS のような構文を使用して新しいスタイルを記述します。以下はその一例です。

Annotation
   {
   width:18px;
   height:18px;
   background-image: resource("Assets/5-Annotation/Editor/pencil.png");
   }

このスタイル内で、サイズのプロパティを指定するとともに、鉛筆アイコンを使用することを指定しました。次に、このスタイルを、Timeline が画面にマーカーを描画する際に使用するように指定します。

[CustomStyle("Annotation")]
public class Annotation : Marker
   {
   [TextArea] public string annotation;
   }

CustomStyle 属性を使用して、どのスタイルを使用するかを指定できます。この例では、common.uss ファイルに追加した Annotation スタイルを使用することにします。

この Annotation マーカーをタイムラインに追加すると、私の作成したカスタムスタイルが使用されます。

パート VI:すべてを組み合わせる

マーカーと通知で何ができるかをお見せするため、Jump マーカーを GitHub レポジトリに追加しました。このマーカーは、JumpReceiver と組み合わせると、タイムライン上のあるポイントから別のポイントに「ジャンプ」します。ジャンプ先の時点は Destination マーカーで指定します。この例は、カスタムスタイルも含め、本記事で扱ったすべての要素を組み合わせて使用した状態です。

オレンジ色の矢印がジャンプ元の時点、紫色の矢印がジャンプ先の時点です。これをどのように行ったか知りたい方は、こちらをご覧ください。

通知とマーカーは PlayableGraph と Timeline API に追加できる非常に強力な要素です。これらの例を参考にしながら、ぜひ面白いものを作ってみてください!ご不明な点がございましたら Timeline フォーラム をぜひご利用ください。

【おまけ】パート VII:クリップからの通知

PlayableGraph が通知を送信でき、Timeline がそれを使用してマーカーの機能を強化させることができることは、上記によって理解できました。それでは、クリップはどうでしょう?クリップも通知を送れるのでしょうか?

答えは ―「はい、送れます!」

Playable Behaviour から通知を送ることができます。

public class ClipNotificationBehaviour : PlayableBehaviour
   {
   double m_PreviousTime;

   public override void OnGraphStart(Playable playable)
   {
       m_PreviousTime = 0;
   }

   public override void ProcessFrame(Playable playable, FrameData info, object playerData)
   {
       if ((int)m_PreviousTime < (int)playable.GetTime())
       {
           info.output.PushNotification(playable, new MyNotification());
       }

       m_PreviousTime = playable.GetTime();
   }
}

この例では、クリップの処理中に通知を毎秒プッシュします。タイムラインにこのクリップを追加して、タイムラインを実行するオブジェクトに NotificationReceiver を追加すると、以下の出力が生成されます。

Received notification of type MyNotification at time 1.01593019999564

Received notification of type MyNotification at time 2.00227000191808

Received notification of type MyNotification at time 3.01137680560353

成功しました!すでにご自分の Playable Behaviour クラスをお持ちで通知を送りたい場合は、必ずしもマーカーが必要とは限りません。この機能の大部分はクリップがすでにサポートしています。

2019年6月25日 カテゴリ: Engine & platform | 9 分 で読めます

Is this article helpful for you?

Thank you for your feedback!

取り上げているトピック