ブループリントからレベルシーケンスでコンポーネントを扱う

この記事はUnreal Engine 4 (UE4) その3 Advent Calendar 2022の21日目の記事です。


[Unreal Engine 4 (UE4) その1 Advent Calendar 2022の4日目の記事](https://qiita.com/_akoto_/items/3e675252fa95f3ffa7d6)では、ブループリントを使ってレベルシーケンスにキーフレームを打つまでの手順について記載しました。
この記事では、4日目の記事に書き切れなかった、コンポーネントについての扱いを説明していきます。

# 記事の内容
* レベルシーケンスの構造について / コンポーネントの扱いについて
* コンポーネントを追加する
* C++作業の説明
* C++から行う理由
* 自作ブループリントのコンポーネントを追加する


# 確認環境
Windows 11 Home
UnrealEngine 4.27.2


# レベルシーケンスの構造について / コンポーネントの扱いについて

レベルシーケンスにおいて、コンポーネントの扱いについて説明していきます。
コンポーネントは、オブジェクトではなく、アクターが持っている機能です。
ですが、[こちら](https://qiita.com/_akoto_/items/3e675252fa95f3ffa7d6#トラック--セクション--チャンネルへのアクセスについて)での説明にも出てきていますが、シーケンスではBindされた(**Spawnableではない**)アクターに親子付けされたオブジェクトのような形で管理されています。
その為、レベルシーケンスに配置されているコンポーネントにアクセスする場合は、**GetBindingsノードからアクセス**を行うことを記載しました。

![コンポーネントの構造の説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/3c0ddd99-ad62-5b76-e2c0-7626f6d09905.png)

上記の画像では、<font color="red">**PointLightのアクター(赤枠)**</font>が**LightComponent0**という名前の<font color="#00bfff">**ポイントライトコンポーネント(水色の枠)**</font>を持っています。
**LightComponent0**は、<font color="green">**IntensityとLightColorというプロパティ(緑の枠)**</font>を持っています。
コンポーネントがプロパティを持っている場合は、アクターにプロパティのトラックを追加しても、正しく反映されません。
**コンポーネントをレベルシーケンスに追加し、そのコンポーネントについて、プロパティのトラックを追加**して行く必要があります。

:::note warn
レベルシーケンスで、スポーンされていないオブジェクトや正しくないトラック情報は、<font color="red">**名前が赤く**</font>なります。
作業しているレベルシーケンスで、名前が赤い物を見つけた際は、**問題のあるデータ**という事になりますので、注意してください。
:::


# コンポーネントを追加する

ポイントライトコンポーネント追加する手順を元に説明していきます。
ブループリントのサンプルを用意しましたのでこちらを使って説明していきます。
コンポーネントを追加作業には、一部**C++を書く**必要があります。

説明用に作成したブループリントのサンプルとなります。ユーティリティウィジェットで作成しています。
『Intensity』は、float型の変数で、ポイントライトのIntensityの設定する値です。
『LightColorR』『LightColorG』『LightColorB』『LightColorA』は、int型で0～255の範囲の数字が入ります。
![ポイントライトを追加するブループリントのサンプル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/d619f400-91b2-97c1-5594-a685f6ca61da.png)

サンプルの流れとしては、PointLightを『AddSpanablefromClassノード』で追加し、GetObjectTemplateノードでPointLightのアクターにキャストして、
アクターの情報は、そこから取得しています。
**トラックやセクションの追加は、AddPointLightComponentノード内**で行っています。
AddPointLightComponentの実装は、C++で行っており、説明は後述します。
簡単に使用したノードの説明も入れます。

### GetMovieSceneノード

レベルシーケンのMovieSceneが必要な為、それを取得しています。
![GetMovieSceneノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/c5cbe793-7218-5be2-c6cb-b3a360119478.png)

### GetObjectTemplateノード

[前回の記事](https://qiita.com/_akoto_/items/3e675252fa95f3ffa7d6#sequencebindingproxy--actor--actor--sequencebindingproxy)に名前だけ登場しましたが、説明がされていませんでした。
SequenceBindingProxyをActorに変換してくれるノードです。但し、**この逆を行うノードはありません**。
![GetObjectTemplateノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/34614966-5374-d49f-30b2-bb95354ab751.png)

### GetIdノード

SequenceBindingProxyのGuidを取得します。
![GetIdノード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/815433cb-7ada-2dd2-93f6-a4025a9211ca.png)

:::note info
[前の記事](https://qiita.com/_akoto_/items/3e675252fa95f3ffa7d6#runtime%E7%8A%B6%E6%85%8B%E3%81%8B%E3%82%89%E9%9D%9Eruntime%E7%8A%B6%E6%85%8B%E3%81%A7%E3%81%AF%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E5%90%8D%E3%81%8C%E5%A4%89%E3%82%8F%E3%81%A3%E3%81%A6%E3%81%97%E3%81%BE%E3%81%86)の補足説明になりますが、C++内でアクターやコンポーネントやトラックの追加や親子関係を付ける際には、**Guidが必要になる**為、
GuidがRuntimeと非Runtimeで変化すると、再生ボタンを押して…『あれっ？動いていない？？』っという状況も出てきます。
:::

# C++作業の説明

ここからは、C++部分の説明になります。

### ヘッダーファイル
ヘッダーでは、BlueprintCallableな関数AddPointLightComponentを宣言しています。
LightColorValueを、int型で0～255の範囲で渡しているのは、FColor作成する際に、float型だとbyte型に直接変換できなかった為です。

```c++:MyBlueprintFunctionLibrary.h

#pragma once

// Engine
#include "Components/PointLightComponent.h"
// MovieScene
#include "MovieScene.h"
// Sequencer
#include "LevelSequence.h"
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"

UCLASS()
class QITA_ADDCOMPONENT_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

    /*
     * 引数の説明
     * 
     * LevelSequenceAsset・・・レベルシーケンスのオブジェクト
     * LevelMovieScene・・・・レベルシーケンスのMovieSceneオブジェクト
     * ObjectActor・・・・・・親となるアクターのオブジェクト
     * ActorGuid・・・・・・・親となるアクターのGuid
     * PointLightComponent・・追加するポイントライトコンポーネントのオブジェクト
     * IntensityValue・・・・・Intensityプロパティに設定する値
     * LightColorValue・・・・LightColorプロパティに設定する値(※ 引数では255で渡している)
     * /
	UFUNCTION(BlueprintCallable, meta = (Keywords = "Associate the PointLight Guid with the PointLightComponent Guid and place it in the sequencer."), Category = "Qita")
	static void AddPointLightComponent(ULevelSequence* LevelSequenceAsset, UMovieScene* LevelMovieScene, AActor* ObjectActor, const FGuid& ActorGuid, UPointLightComponent* PointLightComponent, const float IntensityValue, const FColor LightColorValue);
};
```

### ソースファイル

ソースファイルでは、AddPointLightComponent関数を実装しています。

```c++:MyBlueprintFunctionLibrary.cpp

#include "MyBlueprintFunctionLibrary.h"
// MovieScene
#include "Channels/MovieSceneFloatChannel.h"
#include "Channels/MovieSceneChannelProxy.h"
#include "Sections/MovieSceneFloatSection.h"
#include "Sections/MovieSceneColorSection.h"
#include "Tracks/MovieSceneFloatTrack.h"
#include "Tracks/MovieSceneColorTrack.h"
#include "Tracks/MovieSceneObjectPropertyTrack.h"


void UMyBlueprintFunctionLibrary::AddPointLightComponent(ULevelSequence* LevelSequenceAsset, UMovieScene* LevelMovieScene, AActor* ObjectActor, const FGuid& ActorGuid, UPointLightComponent* PointLightComponent,
														 const float IntensityValue, const FColor LightColorValue)
{
	if (PointLightComponent != nullptr)
	{
        // コンポーネントのGuidを取得
		FGuid componentGuid = LevelMovieScene->AddPossessable(TEXT("LightComponent0"), PointLightComponent->GetClass());
        // FMovieScenePossessableを作成する
		FMovieScenePossessable* pointLightComponentPossesable = LevelMovieScene->FindPossessable(componentGuid);
        // レベルシーケンスにBindする
		LevelSequenceAsset->BindPossessableObject(componentGuid, *PointLightComponent, ObjectActor);
        // アクターとコンポーネントを親子付けする
		if (pointLightComponentPossesable)
		{
			pointLightComponentPossesable->SetParent(ActorGuid);
		}
		FMovieSceneSpawnable* Parent = LevelMovieScene->FindSpawnable(ActorGuid);
		if (Parent)
		{
			Parent->AddChildPossessable(componentGuid);
		}

		//
		// ポイントライトコンポーネントのIntensityプロパティを追加する
		//
		UMovieSceneFloatTrack* intensityPropertyTrack = Cast<UMovieSceneFloatTrack>(LevelMovieScene->AddTrack(UMovieSceneFloatTrack::StaticClass(), componentGuid));
		// 第一引数は、プロパティのトラックの名前
		// 第二引数は、関連付けられるプロパティ名
		intensityPropertyTrack->SetPropertyNameAndPath(TEXT("Intensity"), TEXT("Intensity"));  // Intensityとして追加する
		UMovieSceneFloatSection* intensityPropertyScetion = Cast<UMovieSceneFloatSection>(intensityPropertyTrack->CreateNewSection());
		FMovieSceneFloatChannel* intensityPropertyChannel = intensityPropertyScetion->GetChannelProxy().GetChannel<FMovieSceneFloatChannel>(0);
		intensityPropertyChannel->SetDefault(IntensityValue);
        // TRange::Allは、タイムラインMaxのセクションに設定できる
		intensityPropertyScetion->SetRange(TRange<FFrameNumber>::All());
		intensityPropertyTrack->AddSection(*intensityPropertyScetion);

		//
		// ポイントライトコンポーネントのLightColorプロパティを追加する
		//
		UMovieSceneColorTrack* lightColorPropertyTrack = Cast<UMovieSceneColorTrack>(LevelMovieScene->AddTrack(UMovieSceneColorTrack::StaticClass(), componentGuid));
		// 【注意】第二引数は、LightColorを"Light Color"というように、正しくプロパティ名と関連付けされていないと認識されないず、赤文字になる
        lightColorPropertyTrack->SetPropertyNameAndPath(TEXT("Light Color"), TEXT("LightColor"));
		UMovieSceneColorSection* lightColorPropertyScetion = Cast<UMovieSceneColorSection>(lightColorPropertyTrack->CreateNewSection());
		lightColorPropertyScetion->SetRange(TRange<FFrameNumber>::All());
		lightColorPropertyTrack->AddSection(*lightColorPropertyScetion);
		int ColorCount = 0;
        // UMovieSceneColorSectionには、4つのRGBAのチャンネルが入っているが、チャンネルの型は、FMovieSceneFloatChannelとなる
		for (FMovieSceneFloatChannel* Channel : lightColorPropertyScetion->GetChannelProxy().GetChannels<FMovieSceneFloatChannel>())
		{
			switch (ColorCount)
			{
				case 0:
                    // チャンネルの値の範囲が0～1である為、『0~255』を『0～1』に変換、
					Channel->SetDefault(LightColorValue.R / 255.0f);
					ColorCount++;
					break;
				case 1:
					Channel->SetDefault(LightColorValue.G / 255.0f);
					ColorCount++;
					break;
				case 2:
					Channel->SetDefault(LightColorValue.B / 255.0f);
					ColorCount++;
					break;
				case 3:
					Channel->SetDefault(LightColorValue.A / 255.0f);
					ColorCount++;
					break;
			}
		}
	}
}
```

### ビルドファイル

ビルドcsには、今回使用した関数の実装に必要なモジュール『LevelSequence』『MovieScene』『MovieSceneTracks』を追加しています。
```c++:MyBlueprintFunctionLibrary.Build.cs

using UnrealBuildTool;

public class Qita_AddComponent : ModuleRules
{
	public Qita_AddComponent(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });

        // 『LevelSequence』『MovieScene』『MovieSceneTracks』のモジュールを追加する
		PrivateDependencyModuleNames.AddRange(new string[] { "LevelSequence", "MovieScene", "MovieSceneTracks" });

		// Uncomment if you are using Slate UI
		// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
		
		// Uncomment if you are using online features
		// PrivateDependencyModuleNames.Add("OnlineSubsystem");

		// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
	}
}

```


# C++から行う理由

**FMovieScenePossessableが、ブループリントからアクセスできない**為、C++での作業が必要になります。
今後のバージョンアップで、ブループリントでも公開されれば、この記事の内容はブループリントだけで完結出来るようになりますが...。


# 自作ブループリントのコンポーネントを追加する

では、次にUEのエディタで作成したActorクラスから継承して作成した自作のブループリントに使って同じ事をやりたいと思います。
自作のブループリントのBP_Actorには、**コンポーネントにポイントライトコンポーネントを追加**してあり、Rootのコンポーネントとして、**DefaultSceneRoot**があります。

![自作のブループリント_BP_Actor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/1ffdf477-3163-1c04-62f9-9b182266e411.png)


『コンポーネントを追加する』で紹介したサンプルを少し変更しています。
変更箇所は赤い丸の中になり、**AddSpanablefromClassでスポーンするクラスをBP_Actorに変更**しているのと、**GetObjectTemplateからのキャストするクラスを変えて**います。

![自作のブループリントにポイントライトを追加するのサンプル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/8bab4cc9-0dac-5ed1-04bf-0213c77315a3.png)

しかし、このサンプルは上手く行きません。
どこで失敗するかと言うと、**ポイントライトコンポーネント(GetPointLight)が取得に失敗**します。

### 原因の調査

ポイントライトのアクターでは、問題無くコンポーネントにアクセスできていました。
また、GetAllActorsClassノードなどで取得したアクターでは、問題無くアクセスできる為、
AddSpanablefromClassで作成したオブジェクトにコンポーネントのインスタンスを持っていない訳では無さそうです。
そうなると、GetObjectTemplateで変換したアクターに問題がありそうです。
また、DefaultSceneRoot(ブループリントの直下のコンポーネント)への取得も失敗しており、**コンポーネントの階層の問題でも無さそうです**。
更に、GetRootComponentで、コンポーネントが取得できませんか試したところ、こちらも上手く行きませんでした。


###  PointLightを継承した自作ブループリントで検証

最初のサンプルでは、PointLightを使ってコンポーネントを追加する分には、上手く行っていた為、
**PointLightのサブクラス**を作成し、そのサブクラスにコンポーネント追加してアクセスできるか、実験してみました。
結果としては、**PointLightのポイントライトコンポーネントは取得できましたが、サブクラスで追加したコンポーネントは取得できません**でした。

### 自作ブループリントの内部関数作成して検証

自作ブループリントに内部関数作成してコンポーネントのインスタンスを返せないかを試してみました。
作成した関数は問題無くアクセス出来ますが、**コンポーネントの取得には失敗**します。

### C++でクラスを宣言して検証

次に試したのが、C++でActorを継承したサブクラスを宣言し、そのサブクラスにポイントライトコンポーネントを持たせた場合の検証です。
こちらは、成功し、GetObjectTemplateで変換したアクターから**ポイントライトコンポーネントの取得が出来ました**。
以下は、作成したサブクラスの実装となります。

```c++:MyActor.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/PointLightComponent.h"
#include "Components/BoxComponent.h"
#include "MyActor.generated.h"

UCLASS()
class QITA_ADDCOMPONENT_API AMyActor : public AActor
{
	GENERATED_BODY()

public:
	// Sets default values for this actor's properties
	AMyActor();

    // SceneComponentとPointLightComponent をコンポーネントに持つ
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Qita")
	USceneComponent* SceneComponent;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Qita")
	UPointLightComponent* PointLight;
};
```

```c++:MyActor.cpp

#include "MyActor.h"

// Sets default values
AMyActor::AMyActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	// USceneComponent* SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
	SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
	SceneComponent->Mobility = EComponentMobility::Movable;
    // rootのコンポーネントには、SceneComponentを設定しておく
	RootComponent = SceneComponent;

	PointLight = CreateDefaultSubobject<UPointLightComponent>("PointLight");
    // ポイントライトコンポーネントは、SceneComponentの子供に設定する
	PointLight->SetupAttachment(SceneComponent);
}
```

レベルシーケンスの配置用に、BP_MyActorクラスのサブクラスを用意しました。(蛇足な気も...)
親子関係が、BP_Actorと同じ構成となっている事が確認できるかと思います。
![自作のブループリント_BP_MyActor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153021/98d5a5d1-fea3-bfd5-881a-dff3acbeaa16.png)



# 最後に

プリミティブな型のアクターとActorのサブクラスで作成した自作のクラスでは、エディタの挙動に違いがあり、結構ハマる箇所がありました。
レベルシーケンスおいては、エディタで作成した自作のクラスの扱いは注意が必要かもしれません。

また、この記事だと、C++を書かないとコンポーネントの追加が出来ない説明になっている為、ハードルが上がっている点であるかと思います。

更に、どのトラックがどのようなチャンネルを持っているかは、目的のトラックを一度、追加してみて中を解析してみないと分かりません。
この記事で紹介した、UMovieSceneColorTrackは、UMovieSceneColorSectionを持っていますが、その中にFMovieSceneFloatChannelを4つ持っています。
同じように、UMovieScene3DTransformTrackも、FMovieSceneFloatChannelを9つ持っていて、キーフレームはFMovieSceneFloatChannelに打ちます。



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