UE5 DataLayerとLevelInstanceについてのメモ

# 概要
UnrealEngine5 のDataLayerとLevelInstanceについてのメモです。
DataLayerのステート切り替えと、LevelInstanceの動的作成について検証しました。


## 更新履歴
| 日付 | 内容 |
|:-----|:----|
|2023/04/24| 初版 |

## 参考
以下の記事を参考にいたしました、ありがとうございます。
[UE公式：World Partition - データ レイヤー](https://docs.unrealengine.com/5.1/ja/world-partition---data-layers-in-unreal-engine/)
[UE公式：レベルのインスタンス化](https://docs.unrealengine.com/5.1/ja/level-instancing-in-unreal-engine/)
[【UE5】WorldPartitionのDataLayerについてあれやらこれやら](https://kinnaji.com/2022/12/24/worldpartition-datalayer/)
[【UE5】Level Instance の使い方](https://metatimecg.com/ue5-levelinstance/)
[LevelInstanceActorのLevelStreamingモードと動的レベルインスタンス生成](https://qiita.com/EGJ-Takashi_Suzuki/items/91c5ff9570b583a8a0fb)


## 環境
Windows10
Visual Studio 2022
UnrealEngine 5.1.1

## 関連ソース
"Engine\Source\Runtime\Engine\Public\WorldPartition\DataLayer\DataLayer.h"
"Engine\Source\Runtime\Engine\Public\LevelInstance\LevelInstanceActor.h"


# データレイヤー(DataLayer)について
WorldPartitionレベルにて指定のアクターをまとめてロード/アンロード管理をするための仕組みです。

## BPでのステート切り替え
データレイヤーアウトライナーにてデータレイヤーを作成します。
![DataLayerOutliner.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/0625f1ba-de71-e921-f707-790d73c377bc.png)

BPでは`DataLayerSubsystem`から`SetDataLayerInstanceRuntimeState`でステートを切り替えができます。`Activate`はロードして表示、`Loaded`はロードはするが表示しない、`Unload`はアンロードとなります。
![DataLayerBP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/a4dff7e5-75a3-8923-bb7b-75308cc0260c.png)


## C++でのステート切り替え
C++でも `UDataLayerSubsystem`からステートを切り替えることができます。
以下のサンプルコード。

:::note warn
このコードはストリーミング切り替え時の処理を登録して、まだ完了していない場合は強引に再登録してストリーミング完了を確認していますが、本来は別途完了待ちタスク等を作成して監視するようにすべきかと思います。
:::

```C++:データレイヤーステート切り替え
#include "WorldPartition/WorldpartitionSubsystem.h"
#include "WorldPartition/WorldPartitionRuntimeCell.h"
#include "WorldPartition/DataLayer/DataLayerSubsystem.h"
#include "WorldPartition/DataLayer/DataLayerAsset.h"


UDataLayerSubsystem* _DLSubsystem = GetWorld()->GetSubsystem<UDataLayerSubsystem>();
UDataLayerAsset* _DLAsset = LoadObject<UDataLayerAsset>(nullptr, TEXT("/Game/NewDataLayerAsset.NewDataLayerAsset"), nullptr, LOAD_None, nullptr);


if (_DLSubsystem) {
	// ステート切り替え時の処理登録 ＃登録メソッド宣言にはUFUNCTION()が必須＃
	_DLSubsystem->OnDataLayerRuntimeStateChanged.AddDynamic(this, &UMyActorComponent::OnDataLayerRuntimeStateChanged);

	// ステート切り替え
	_DLSubsystem->SetDataLayerInstanceRuntimeState(_DLAsset, EDataLayerRuntimeState::Activated);
}


// ステート切り替え時の処理
void UMyActorComponent::OnDataLayerRuntimeStateChanged(const UDataLayerInstance* _DataLayer, EDataLayerRuntimeState _State)
{
	UDataLayerSubsystem* _DLSubsystem = GetWorld()->GetSubsystem<UDataLayerSubsystem>();
	UWorldPartitionSubsystem* _WPSubsystem = GetWorld()->GetSubsystem<UWorldPartitionSubsystem>();
	TArray<FWorldPartitionStreamingQuerySource> _QuerySources;

	FWorldPartitionStreamingQuerySource _Item; _Item.DataLayers.Add(_DataLayer->GetDataLayerFName());
	_QuerySources.Add(_Item);

	// ストリーミング完了チェック
	bool _IsCompleted = _WPSubsystem->IsStreamingCompleted(EWorldPartitionRuntimeCellState::Activated, _QuerySources, true);

	if (!_IsCompleted) {
		// まだ完了していない

		// タイマーで無理やり再設定して確認する
		FTimerHandle    _Handle;
		FTimerDelegate  _TimerDelegate;

		_TimerDelegate.BindUFunction(this, FName("OnDataLayerRuntimeStateChanged"), _DataLayer, _State);
		GetWorld()->GetTimerManager().SetTimer(_Handle, _TimerDelegate, 0.001f, false);
	}
	else {
		// ストリーミング完了
	}
}
```




# レベルインスタンス(LevelInstance)について
レベルをインスタンス化し、同じものを複数配置することができる仕組み。WorldPartitionレベルに置くとストリーミングの対応も行われるようです（埋め込みモード）。

## BPでの動的作成
インスタンス化するレベルを作成し、次に`LevelInstance`を継承したBPを作成し、[Level]に設定します。

![LevelInstanceBP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/a151022d-ae71-3a2d-53cc-e57f80a54416.png)

このアクターは通常のアクターのようにスポーンができるので以下のように生成/破棄が可能です。

![LevelBP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/57270a38-6245-f402-32f3-20e6e122ce00.png)


## C++での動的作成
BPで作成済のレベルインスタンスアクターはアクターとして生成ができます。
以下コード例。

```c++:.h
// レベルインスタンスアクター
UPROPERTY()
TObjectPtr<ALevelInstance> LvInstActor;
```
```c++:レベルインスタンスアクター生成
#include "LevelInstance/LevelInstanceActor.h"
#include "LevelInstance/LevelInstanceSubsystem.h"

ULevelInstanceSubsystem* _LISubsystem = GetWorld()->GetSubsystem<ULevelInstanceSubsystem>();
	
// レベルインスタンスアクターをスポーン
if (!::IsValid(LvInstActor)) {
	UBlueprint* _Bp = FindObject<UBlueprint>(NULL, TEXT("/Game/LevelInstanceBP.LevelInstanceBP"));
	if (::IsValid(_Bp)) {
		LvInstActor = GetWorld()->SpawnActor<ALevelInstance>(_Bp->GeneratedClass, FVector(1280, 1220, 250), FRotator(0, 0, 0));
	}
}

// レベルインスタンスアクターを破棄
if (::IsValid(LvInstActor)) {
	LvInstActor->Destroy();
}

```

## C++での動的作成2
レベルインスタンスアクターなしで(レベルのみで)生成する場合は以下のように。ただし `SetWorldAsset`がエディタオンリーなメソッドなのでランタイムで使うには `ALevelInstanceクラス`を継承して自前で改造が必要だと思われます。

```c++:
UWorld* _LevelData = LoadObject<UWorld>(nullptr, TEXT("/Script/Engine.World'/Game/LevelInstanceTest.LevelInstanceTest'"), nullptr, LOAD_None, nullptr);
LvInstActor = GetWorld()->SpawnActor<ALevelInstance>(ALevelInstance::StaticClass(), FVector(1280, 1220, 250), FRotator(0, 0, 0));
LvInstActor->SetWorldAsset(_LevelData);		// Ediorのみ
LvInstActor->LoadLevelInstance();
```


## レベルインスタンスの動的生成時の注意点
動的生成が可能なレベルインスタンスは非ワールドパーティションレベル(**OneFilePerActorが設定されていない**)である必要があるようです。
レベルの設定だとワールドセッティングの詳細設定[外部アクタを使用]にチェックが入っていると動的作成ができません。

![WorldSetting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/71a1517e-8cba-b267-b59b-6f19524d684c.png)

作成済レベルからチェックを外すことはできないので新たにレベルを作成してコピペ等の対応が必要になります。

### 非ワールドパーティションレベルの判別
レベルを右クリックして[パーティション化のストリーミングサポートを追加]というメニューが出た場合は非ワールドパーティションレベルと判別できます。

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/19f4d355-2619-b605-0720-eed2fde1a4c5.png">



# まとめ
任意のロード管理はDataLayerがわかりやすいのでLevelInstanceに置いて使うといいのかと思ったのですが、構造が複雑になってしまいますかね。