Level SequenceのRepeater Eventを使ってみたい

これは[Unreal Engine Advent Calender 2024](https://qiita.com/advent-calendar/2024/ue) 23日目の記事です。

# 結果

画質が悪いのですが、先に結果です。
![adcaleUE24_result.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/2242b240-9473-67fa-f67a-f5c512d0d562.gif)

作っていたら結構な量になってしまったので細かく説明はいたしませんが、使用したBlueprintは全て見える形でキャプチャしておきますので、ご興味ありましたらそちらを参照ください。

# Sequencer Eventについて
[Unreal Engine Sequencerイベントのドキュメント](https://dev.epicgames.com/documentation/ja-jp/unreal-engine/cinematic-event-track-in-unreal-engine?application_version=5.3)
予め作成しておいたBlueprintイベントを、Level Sequence（以下Sequencer）で呼び出して使用するのは珍しいことではない思います。

イベント発生フレームを指定し、キーを配置すればイベントが呼ばれます。
Sequencerで呼び出せるイベントは二種類、TriggerとRepeaterがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/ce14a43c-7a0e-141b-c653-f0f2df74b0be.png)
### Trigger
Triggerイベントは文字通り、イベント発生の引き金となるためのトリガーであるため、そのフレームを通過すればイベントが実行されるという大変便利なものです。
しかし再生時にタイムスライダが必ずそのイベントを通過するのであればいいのですが、例えばマテリアルの色が少しずつ変化していくとか、フェードイン・アウトに使用するなどのように、「このフレームではこの状態であってほしい」という状況に対応できません。

### Repeater
そこでもう一つの、Repeaterイベントです。

Repeaterイベントは、点ではなく範囲で配置するイベントです。範囲内であれば処理を繰り返します。
これでイベントを通過できる範囲が広くなり、多少確認などしやすくなるはずです。
リサーチしてみたところあまり使われていないような気がしたのと、もしかしたら結構使えるのでは…？と思ったので少しだけ触ってみました。

今回は前述した「このフレームではこの状態であってほしい」をフェードインで実現するために少し作業してみたのですが、あまり満足な結果にならなかったため、タイトルを「使ってみたい」としています。
使えなくは無いのですが…
もしより良い使用法などありましたらご指摘頂けますと大変ありがたいです。

# 実装
使用バージョン
Unreal Engine 5.3.2
Starter Content使用
Json Blueprint Utilities Plugin使用
## 準備
まず、簡単にレベルを作成してSequencerで処理を仕込みました。
細かくは解説しませんが、やったことは下記です。
- マテリアルの準備
  - StarterContent MinimalDefaultに置かれているStaticMeshであるChairを、StaticMeshActorに変換
  - そのStaticMeshのマテリアルのBlendModeをMaskedに変更。OpacityMaskにはOpacityと命名したパラメータからDitherTemporalAAを通して適用
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/ab6fae7e-3e70-724f-cb0d-6747a9590558.png)
   - それぞれのM_Chairマテリアルからマテリアルインスタンスを作成しMI_Chairとし、二つの椅子モデルに適用
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/e28f4243-530e-f6e2-93f2-24b1d3670c94.png)
  - 前述したOpacityパラメータを調節できるよう、椅子であるStaticMeshActorのStaticMeshにDynamicMaterialを作成できる処理を呼べる初期化イベント（Call on Editor）の作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/303fec8a-6ff4-27b3-e244-4154b62161f2.png)

- Sequencerの準備
  - 二つの椅子をトラックとして登録![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/9b352e6d-8c8c-10bb-2ed5-59269af5ac01.png)




これらの準備が出来たら、ブループリントを作成していきます。
大雑把に書くとこのような流れになっています。

1. SequencerからStaticMeshActorのイベントを呼び出し
2. 予め用意しておいたタグ（後述）で、実行中のRepeaterイベント（Track）を特定
3. フレームを元に透明度を操作

詳しく書いていきます。

### 1.SequencerからStaticMeshActorのイベントを呼び出し
1.と書きつつもこの処理の前に、呼び出せるイベントを用意しておく必要があります。
StaticMeshActorに「Fade」と名付けたカスタムイベントを作成し、引数を下記の通りにしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/fed93ca3-1a44-c6ce-d9ee-6c36577109b3.png)

切れてしまってますが、「Movie Scene Sequence」の型は Movie Scene Sequenceです。


### 2. 予め用意しておいたタグ（後述）で、実行中のRepeaterイベント（Track）を特定

続いて、このイベントをSequencerから呼び出します。
手順はキャプチャの通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/fe11b113-b057-02d2-dd44-f0e44736188e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/77556fdc-ff55-27ec-96b0-4c00b01a42d0.png)
すると自動でこのように繋がると思いますが、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/84f53983-5781-5d0e-a2ba-ddb05b1f8906.png)
下記画像のように直します。
Level Sequence Directorを経由してからしか渡せない情報を渡します。
ChairBなどに対して利用する際にも、こちらのイベントを使用します。
というか、この方法であればどのStaticMeshActorでも使用できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/e1b5da39-55e5-132f-f4d7-7774f39c9c89.png)
さて、画像の通り整理ができたとしても、引数として用意するTagに何を入れるかが判りません。
ここで後述すると書いたTagを用意します。

SequencerにTrackとして追加してあるChairを右クリックし、画像の流れでタグを追加します。
名前は管理しやすければ何でも構いません。ここでは「ChairA」としました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/7e822a63-36a1-5df2-ef2e-99c830b761df.png)
Trackのタグとして「ChairA」を付与しましたら、今度はイベントに持たせる引数として同じ名前のタグを渡しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/26643d5d-5b7d-f74b-8dec-bc61f545bc1d.png)

ここまでの準備が済んでようやく、ブループリント側での処理に進めます。
ただしその処理がとてもスマートとは言い難いです。
大量のTrackから実行中のイベントを、ひとつひとつ絞り込むしか方法が見つけられなかったためです。
最終的なブループリントを添付しますが、ご覧の通り冗長です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/fc6f4b92-c5b9-41fa-ca0a-7888bdc6f167.png)


大まかな流れはコメントに記しました。
Sequenceノードで、初期化（Then1）とその後（Then2）のフローに分けています。
- タグからBindingIDを取得
    - Sequencer内のTrackを全て取得
        - BindingIDが一致したイベントのみを対象に絞る
            - EventTrackのみを対象に絞る
                - Repeaterイベントのみを対象に絞る
- 実行中のイベントで透明度を変更

とあります。
これ、Then1の冗長な処理を一回で出来ないのか？と思い探ってみたのですが、その方法がどうも見つからず。
そしてその対象に絞るという方法も、![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/c431106f-3fa3-d032-cae5-3e5754388a13.png)
Convert Struct To Json Stringで無理矢理文字列にした上で部分的にIDを抽出したり、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/9aeb8bf8-78e8-8493-1f9d-b9aa50d9ad31.png)
得られたTrackのObject Nameに文字列が含まれているかどうかで判定したりと、かなり無理矢理な方法で取得しています。
あと、Find Binding by Tagでタグを使用してますね。

### 3. フレームを元に透明度を操作
ようやくたどり着いた先で、透明度を処理できました。
SequenceノードのThen2の処理です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324499/f71fc99a-a9f1-9c17-8e96-28ec4710c213.png)
それぞれのStaticMeshActorごとに保持しているRepeaterイベントが何なのかを特定しておくまでは前述のSequenceノードThen1で出来たのですが、結局その後に「今どのRepeaterイベントが実行中か？」は取得できませんでした。代わりに、実行中であるRepeaterイベントの範囲内にあるかどうかをいちいち「In Range」を使用して判定しました。Start FrameとEnd Frameの取得に実行ピンが必要になることも結構特殊かと思います。

今回のサンプルではフェードインしか作成していませんが、もちろんフェードアウトや、色の変更なども可能となります。

# 振り返り
とりあえず目標通り、Repeatイベントを使用して「このフレームではこの状態であってほしい」は達成できました。
しかしあまりスマートな実装にはならなかったことが悔やまれます。
特に嫌な部分はこの辺りかと思います。

- 予めタグを設定しておかなければならず、面倒
- タグからTrack特定にかけての段階が多く、冗長
- 最終的に実行中であるRepeatイベントの特定はできず、フレーム毎に判定している

このあたりがすっきり片付けば、もう少し便利に使えるかと思います。
ただ、とりあえず使う分にはこの実装でも可能ですし、一部自動化・コンポーネント化するなどしたらもっと便利になりそうです。

駆け足になってしまってわかりづらい箇所も多いと思いますが、以上です。
ご指摘等頂きましたら追記・修正していこうと思います。