ブループリントからレベルシーケンスにキーフレームを打つ

この記事は[Unreal Engine 4 (UE4) その1 Advent Calendar 2022の4日目](https://qiita.com/advent-calendar/2022/ue)の記事です。


[過去の記事](https://qiita.com/_akoto_/items/512e191ee6f01e355e59)で、レベルシーケンサーに、ブループリントからオブジェクトを配置する記事を投稿したので、
今回はその続きとして、ブループリントからアニメーションのキーフレームの入力するまでの内容になります。


# 記事の内容

* レベルシーケンスの構造について
* Bind / Spawnables / Possessableの違いについて
* キーフレームの追加の仕方
* SequenceBindingProxy → Actor / Actor → SequenceBindingProxy
* Runtimeと非Runtimeの挙動の違い


# 確認環境

* Windows 11 Home
* UnrealEngine 4.27.2


# レベルシーケンスの構造について

最初にレベルシーケンスの構造について説明します。
レベルシーケンスには、**トラック**が存在し、その中に**セクション**があり、セクションの中に**チャンネル**が存在しており、チャンネルの中に**キーフレーム**が打たれています。

**キーフレームを打つこと**で、アニメーション(変化する値)させています。

:::note info
 * トラックに複数のセクションを追加する事は可能ですが、**シーケンスエディタで表示されるのは最初に追加したセクション**だけ
 * セクションとチャンネルは、**1対1の関係**で、セクションが追加されるとチャンネルも追加される
:::

具体的に、レベルシーケンスの内容を見ながら説明します。

![レベルシーケンスの構造の説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/ab248b60-626e-1e20-f0bf-83ec50259c17.png)


上の画像を元に説明します。
レベルシーケンスの中に<font color="magenta">**マスタートラックのショットトラック(紫の枠)**</font>と<font color="red">**スポーンされるアクター(赤い枠)**</font>が追加されています。
アクターは、<font color="blue">**トラック(青い枠)**</font>を持っており、トラックの中に<font color="#f0e68c">**セクション(黄色い枠)**</font>を持ち、
セクションの中に<font color="#f0e68c">**チャンネル(黄色い枠)**</font>があり、チャンネルの中に<font color="#7fff00">**打たれているキーフレーム(緑の丸)**</font>です。

:::note info
**マスタートラック**には、ショットトラック、カメラカットトラックなど、トラックを追加する際に出るダイアログ(下の画像)にあるトラックがマスタートラックになります。フォルダなど、一部はマスタートラックではないものもあります。
:::

![MasterTrackの種類.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/d17d6450-e51b-a819-b370-9490d0961ba1.png)

:::note warn
レベルシーケンスにスポーンされているたアクターには、**直下にトラックを持った場合**と**コンポーネントがトラックを持っている場合**が存在します。
:::

レベルシーケンスの構造については、negimochiさんが[ブログ](https://negimochi.work/sequencer_custom_track_0/)で詳細に説明をされている為、ここでは簡単な説明に留めます。

### トラック / セクション / チャンネルへのアクセスについて

![レベルシーケンスのオブジェクトの説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/e181cdd8-9869-6027-a3d6-0b6c6eeb57d7.png)

上の画像は、新規に作成したレベルシーケンサーにカメラとショットが一つ追加されている状態です。
このLevelSequenceに対して、GetMasterTracksを行うと、**カメラカットトラックのみが入った配列**が返ってきます。
**CineCameraActorやCube(StaticMeshActor)のオブジェクトは取れません**。

**GetMasterTracksノード**
GetMasterTracksノードは、LevelSequenceオブジェクトからマスタートラックを取得できるノードです。
![GetMasterTracksノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/50710721-08b9-490d-3ee2-cf7ad041ede2.png)



##### アクターを取得する
CineCameraActorやCubeにアクセスするには、どうすればいいのでしょうか？

この場合は、**GetBindingsノード**または**GetSpawnablesノード**を使用します。
実際に、ノードを実行し返ってきた結果をダンプしてみました。

GetBindingsノードで返ってきた配列
![GetBindingsのログ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/31fa51b0-21d6-caaa-6015-d49eccd394b3.png)

GetSpawnablesノードで返ってきた配列
![GetSpawnablesのログ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/ef8615ea-8788-c686-741f-41664516ed37.png)

GetBindingsノードでは、**CameraComponentが含まれている**のに対し、GetSpawnablesノードでは**含まれていない**点に注意してください。
コンポーネントは、**Spawnableなオブジェクトではない**のです。その為、Bind情報から取得します。

:::note warn
GetBindingsノードやGetSpawnablesノードでは、Actor型のオブジェクトではなく、**SequenceBindingProxyというオブジェクト**で返って来ます。
レベルシーケンスでアクターやコンポーネントをアクセスする際に、この**SequenceBindingProxyオブジェクトを操作して、アクセスを行います**。
SequenceBindingProxyオブジェクトの詳細は、後述します。
:::

##### トラックを取得する
**Cine Camera Actor**や**Cube**が持っている**Spaned**や**Transform**のトラックへのアクセスはどう行えばいいのでしょうか？
GetSpawnablesでアクターを取得し、そのアクターに対してGetTracksを行う事で、**Spaned(MovieSceneSpawnTrack)** や  **Transform(MovieScene3DTransformTrack)** へのアクセスが可能です。

![GetSpawnablesのアクターのトラックを取得するまで.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/e64b8dbf-4ced-e2c1-09fe-5078d945ab69.png)

次に気になるのが、**CameraComponent**の下にあるシネマティクスで公開されているPropertyへのアクセスについてです。
その為には、まず**CameraComponentへアクセス**する必要があります。
CameraComponentへアクセスは、GetSpawnablesでアクターを取得し、そのアクターに対して**GetChildPossables**ノードを行う事で、CameraComponentへのアクセスが可能です。
ここで少し疑問に思うのが、**GetSpawnablesノート**では取得できなかったCameraComponentを**GetChildPossables**ノードで取得できる点です。
違和感を感じるとこかも知れませんが、GetChildPossables内の実装では、最初にGetBindingsの中身からアクセスしている点が挙げられます。
その為、コンポーネントはGetChildPossablesノードでアクセスしていく事がベターだと思われます。

![GetSpawnablesのアクターのコンポーネントを取得するまで.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/5f9989a0-25d7-6838-1c08-e4886669f412.png)

##### チャンネルを取得する
チャンネルを取得するまでの流れです。

![GetSpawnablesのアクターのチャンネルを取得するまで.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/ebf74e07-cb8a-652a-ff58-8207356b1a44.png)

長いですが、レベルシーケンサー内の一つのチャンネルを見つけるまで、
Spawnableオブジェクト ➡ コンポーネント ➡ Track ➡ Section ➡ Channel の順で探していく必要があます。

:::note info
**FindTracksbyType**ノードや**FindChannelsbyType**ノードというものも実装されている為、必要によって使って行きましょう。
:::

##### GetDisplayNameで名前を見る際の注意点

ここで一つ、注意が必要な事があります。
上の画像のコメントにも記載していますが、トラックを見分ける判断材料が、**GetDisplayName**しかありません。

具体的な説明をすると、CameraComponentには、**Current Focal Length**と**マニュアル焦点距離 (フォーカス設定)**と**Current Aperture**の三つのプロパティが追加されています。
このプロパティがどのトラックであるか判断材料が、コメントに記載した、**MoviwSceneTrackが持っているGetDisplayNameだけ**だという事です。
トラックやセクションやチャンネルで、Pure関数として**GetObjectName**と**GetDisplayName**が使用できるのですが、
**MovieSceneFloatTrack_0**や**MovieSceneFloatSection_0**または**MovieSceneScriptingFloatChannel_0**っという名前を返してくる為、判断材料とはなりえません。

:::note warn
トラックの名前を記録し、トラックの中を解析していく事が大切かと思います。
:::


# Bind / Spawnables / Possessableの違いについて

ここで、シーケンスにアクターが管理された状態について説明します。
状態としては、**Bind(Binding)** / **Spawnables** / **Possessable**の三つの状態があります。

**Bind**
Bind状態とは、レベルシーケンサーに配置されている状態であると言えます。
GetBindingsノードで取得してくるオブジェクトもこの状態のオブジェクトであると考えていいでしょう。

**SpawnablesとPossessable**
SpawnablesとPossessableの違いについてです。
Spawnablesは、**レベルシーケンサーに配置されている状態でスポーンされる**オブジェクトの事、Possessableは**レベルにスポーンされているオブジェクトが、レベルシーケンサーに配置されている状態**です。
詳細は、[公式のドキュメントに記載](https://docs.unrealengine.com/4.27/ja/AnimatingObjects/Sequencer/Overview/SpawnablesPossessables/)があります、

ドラック&ドロップで、コンテンツブラウザからアクターをBindしたアクターは、同時にSpawnablesされている為、Bind状態とSpawnables状態を意識する機会は少ないでしょう。

:::note warn
上で説明したとおり、**GetBindingノードとGetSpawnablesノードの結果が違う点**に注意してください。
:::




# キーフレームの追加の仕方
この記事の本題のブループリントからのキーフレームを打ち方について、説明していきます。

![テストでのチェック用のシーケンス.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/f6b0d171-4afc-c1c8-7de9-f43c9871596a.png)

上の画像は、レベルシーケンサーに、ドラック&ドロップでスタティックメッシュに配置し、LocationのY座標の0フレーム目に-420を打ち、20フレーム目に420のキーフレームを打って、Y軸の移動を行っています。


### 説明サンプル用ブループリント

ブループリントから同じ動作をするブループリントが下のブループリントです。こちらは、ユーティリティウィジェットで作成しています。

<iframe src="https://blueprintue.com/render/t1zaxm61/" scrolling="no" allowfullscreen></iframe>

表示されない方は、下のリンクから確認できます。
[ブループリントからアクターをスポーンしてキーフレームを追加するサンプル](https://blueprintue.com/blueprint/t1zaxm61/)

### ノードの説明

**FindChannelsbyTypeノード**
アニメーションのキーフレーム打つトラックを見つける際に使用します。
結果が、配列となっている為、ループを行い目的のトラックを見つける必要があります。

![FindChannelsbyTypeノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/2cee00f9-e8da-0266-2eaa-5dac717accf2.png)

**CastToMovieSceneScriptingFloatChannelノード**
キャストノードです。次に紹介するAddKey系のノードが、CastToMovieSceneScripting****Channelノードで実装されている為、操作したい型にキャストする必要があります。

以下の型が存在します。

|型  |キャスト用ノード  |
|---|---|
|Boolean  |CastToMovieSceneScriptingBoolChannel  |
|Int  |CastToMovieSceneScriptingIntgerChannel  |
|Float  |CastToMovieSceneScriptingFloatChannel  |
|String  |CastToMovieSceneScriptingStringChannel  |
|Byte  |CastToMovieSceneScriptingByteChannel  |
|String  |CastToMovieSceneScriptingStringChannel  |
|Event  |CastToMovieSceneScriptingEventChannel  |
|ActorReference  |CastToMovieSceneScriptingActorReferenceChannel  |
|ObjectPath  |CastToMovieSceneScriptingObjectPathChannel  |

:::note info
以降に紹介する**AddKeyノード**、**GetKeysノード**、**GetNumKeysノード**、**RemoveKeyノード**、**SetDefault / GetDefault / RemoveDefault / HasDefaultノード**は、型ごとに専用のノードが用意されています
:::

![CastToMovieSceneScriptingFloatChannelノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/495d54a1-b001-fd3b-ffe3-727b525313a3.png)

**AddKeyノード**
実際にキーフレームを入力するノードです。

![AddKeyノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/a51e7629-501b-c632-857b-af425bf0ad50.png)

下の画像は、Boolean型のトラックのキーフレーム追加する際に使用します。

![CastToMovieSceneScriptingBoolChannelノードからのAddKey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/dfed4a92-ff80-1e6e-2b91-6cbbed92c606.png)


#### その他のノード

その他、Keyで検索すると、下の画像のようにいくつかのノードが引っ掛かります。

![CastToMovieSceneScriptingFloatChannelノードのその他のノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/08567a2d-a450-e270-dbe9-4b9e72bce983.png)

検証出来た物だけ載せておきます。

**SetDefault / GetDefault / RemoveDefault / HasDefaultノード**
![Default系ノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/b72ee8f6-b81a-df60-10c9-479e1bea94cf.png)

MovieSceneScriptingFloatChannelに**キャストしたオブジェクト**から引けます。MovieSceneScriptingChannelオブジェクトのままだと、ノードが出てきません。
下の画像の赤枠で示した箇所が、**何もキーフレームが打たれていない場合の値が、デフォルトの値**となります。
このノードは、そのデフォルトの値の設定操作できるノードとなります。
それぞれ、SetDefault(設定) / GetDefault(取得) / RemoveDefault(削除) / HasDefault(設定の可否をチェック) できるノードとなります。

![Defult系ノード説明用の図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/b3f67e27-247e-06b1-5cf1-e52a23ffd1ae.png)

**GetKeysノード**
![GetKeysノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/eaf3f0e0-fc8f-dddc-f9aa-623f00aac59c.png)

MovieSceneScriptingFloatChannelにキャストしたオブジェクトから引けます。
名前の通り、チャンネルが持っているキーのリストを取得できます。

**GetNumKeysノード**
![GetNumKeysノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/5923f935-8b47-a7ec-d800-3d76fcf3f302.png)

MovieSceneScriptingFloatChannelにキャストしたオブジェクトから引けます。
名前の通り、チャンネルが持っているキー数を返します。

**RemoveKeyノード**
![RemoveKeyノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/4105cdd2-785e-5a0c-5480-08f39f804a58.png)

MovieSceneScriptingFloatChannelにキャストしたオブジェクトから引けます。
MovieSceneScriptingKeyに対して実行するノードで、引数で渡したキーフレームを削除します。

:::note info
削除ノードは、RemoveSectionやRemoveChannelというノードは用意されていませんが、**RemoveTrack**または、**RemoveMasterTrackノード**は用意されており、トラックやマスタートラックの削除に使用します。
:::


# SequenceBindingProxy → Actor / Actor → SequenceBindingProxy

レベルシーケンスに管理されているアクターは、レベルで管理されているアクターと**同一オブジェクトであっても、アクセス方法が違い**ます。
GetAllActorsノードを使用すれば、**レベルシーケンスに管理されているアクターにアクターとしてはアクセスする事が出来ますが、レベルシーケンスに管理されているアクターとしては変更は反映されません**。
『**トラック / セクション / チャンネルへのアクセスについて**』の項目で触れましたが　レベルシーケンスでは、
**SequenceBindingProxyというオブジェクトを使って操作**を行って行きます。
SequenceBindingProxyとActorの変換したいケースは、しばしば必要になってきます。
以降は、その方法についての記載になります。

### SequenceBindingProxy → Actorの変換
SequenceBindingProxyには、GetObjectTemplateノードが実装されています。
これは、Actor型を返す為、目的のアクター型にキャストする事で、Actor関数のアクセスする事が可能です。

### Actor → SequenceBindingProxyの変換

変換用の便利関数が用意されていない為、GetBindings ➡ ForLoop ➡ GetDisplayName で名前のチェックして一致するものを探す必要があります。


# Runtimeと非Runtimeの挙動の違い

最後に、Runtimeと非Runtimeの挙動の違いついて少し説明を入れたいと思います。
この辺りは、エディタの挙動を知っていなければ、ハマるポイントだと思われます。

### Runtime状態と非Runtime状態

この記事では、Runtime状態とは、下の画像のプレイボタンが押されている状態を指しており、非Runtime状態とはプレイボタンが押されていない状態となります。
**ムービーレンダーキューやムービーシーンレンダーキューを実行時は、Runtime状態**となって実行されています。

![再生ボタン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/28e06032-2ce5-bc68-4aa5-f02e93755483.png)

### Runtime状態から非Runtime状態ではオブジェクト名が変わってしまう

非Runtime状態がRuntime状態になった際にレベルシーケンサーで管理されているアクターは、**スポーンされ直さ**れてしまいます。
再スポーンが行われている為、一見同じ型の同じアクターで合っても、新規にインスタンスとしては違ったオブジェクトになっています。
その為、以下の問題が発生してきます。

* オブジェクト名前(GetObjectName や GetDisplayName)が違う
* Guidが違う

### オブジェクト名前が違う

非Runtime状態からRuntime状態でオブジェクト名前が変わってしまうのは、かなり困りものです。
何故なら、GetDisplayNameやGetObjectNameを見て、名前からそのアクターの正体を探らなければならない場面も多いからです。
その為、オブジェクトの名前がころころ変わってしまうのは、嫌な挙動となっています。
取れそうな対応策として、非Runtime状態でのみ操作する前提でブループリントを作成するか、スポーンされ直しても変化しない情報を持たせておくなどの対策をする必要がありそうです。

### Guidについて

Guidは、レベルシーケンサーに管理されているアクターやコンポーネント、トラックの等に振られている**ユニークな番号**となります。
このGuidも再生ボタンを押すと再取得され変わってしまいます。

:::note info
このGuidは、レベルシーケンサーに管理されているオブジェクトが使用可能になっているかチェックする際に役に立ちます。
IsValidでのチェックノードが用意されていますが、このチェックではまだ初期化前のオブジェクトでもtrueで返って来る事がある為、
『00000000000000000000000000000000(34文字)』との比較を行方が確実です。
:::

# 終わりに

作業効率を考える際には、レベルシーケンスの自動的な構築は、視野に入ってくる作業だと思います。
しかし、ブループリントからレベルシーケンサへのアクセスのワークフローは、あまり見かけません。
これから、レベルシーケンスをブループリントからさわる必要がある人の助けになれば幸いです。
また。Actorクラスから継承したブループリントについては、罠が結構ある為、気を付ける点などの記事を、
今後、いくつか投稿していこうと思います。


明日は、[ねむ](https://qiita.com/nemu90kWw)さんになります。
よろしくお願いいたします。