LevelInstanceActorのLevelStreamingモードと動的レベルインスタンス生成

# はじめに
レベルインスタンスアクターはUE5で追加されたアクターです。
大規模なワールドを効率的に作成するための機能ですが、実はいろいろな機能があるので簡単に解説します。

# ざっくりとどんなもの？
LevelInstanceActorは誤解を恐れずにいうとワールドパーティションを使ったレベルで使えるサブレベルに「近い」ものです。実際にアクターのパラメータとしてレベルを指定するので表面上はサブレベルをパーシスタントレベルに置くといった使い道が可能です。
作り方と使い方について簡単に説明すると・・・

パーシスタントレベル上にあるアクターを複数選択して"Create Level Instance" で作ります。レベル名を指定してレベルアセットとして保存されます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/5260915b-8f68-1a96-683b-80a8ed28d55b.png" width="600">

配置する時はそのレベルをドラッグ&ドロップします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/4e006bfd-3e2d-bd07-18e0-501eaecdb198.png" width="600">

## 特徴や利点、制限など
* レベルをコピペすることが可能で、一つを編集するとすべてのレベルインスタンスが同時に変更されます
* パーシスタントレベル上で塊として移動可能
* 軽量な編集用の別のパーシスタントレベル上でレベルインスタンスを編集することが可能
* ワールドパーティションセルの分割に影響をうけます
* クック時にレベルインスタンスは解体されてパーシスタントレベルのワールドパーティションセルに組み込まれるのでランタイムコストがありません
* 基本的にワールドパーティションレベル上で利用します(※後で補足)
* LevelInstanceActorで扱うレベルはOneFilePerActor(OFPA)が有効なレベルである必要があります(※後で補足)そうではないレベルを指定すると警告が表示されます。
 WorldPartitionレベルはデフォルトでOFPAが有効になりますが、WorldPartitionレベルではない通常のレベルでもExperimental機能としてOFPA化できます。
* データレイヤーに登録すると内部のアクター達も同じデータレイヤーに自動的に登録されます。編集中に表示非表示を切り替えたり、ランタイムで任意のタイミングでのロードやアンロードの制御に利用できます。従来のレベルでのサブレベルで行っていた任意のタイミングでのロードの管理はこのデータレイヤーで行います。

## Packed Level Actor について
同じような名前のPacked Level Actorについても簡単に触れます。前述のレベルインスタンスアクターに大変近いですが、StaticMeshなどの一部のタイプのアクターしか含められない代わりに自動インスタンス化を行い**パフォーマンスを改善することができる機能**となっています。
具体的にはStaticMeshComponentが非NaniteメッシュならHierachicalInstancedStaticMeshComponentに、NaniteメッシュならInstancedStaticMeshComponentに変換されるのでコンポーネント数が減少し効率が向上するという寸法です。当然ですがモジュラーアセットのような形式で同じアセットを繰り返し使わないとあまり効果がありません。

## OneFilePerActorについて補足

https://docs.unrealengine.com/5.0/ja/one-file-per-actor-in-unreal-engine/

ワールドパティションではないレベルでもOFPAを有効にしたい場合は上記公式ドキュメントの冒頭の警告にあるようにExperimental機能を有効化したあと、ワールドセッティングのUseExternalActorにチェックを入れてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/58d5dff5-babc-14f7-92e1-92500fd380c3.png)




=============ここまで前置き=================

# 本題のLevelStreamingモード！
さてここからが本題となります。
レベルインスタンスに関する公式ドキュメント

https://docs.unrealengine.com/5.0/ja/level-instancing-in-unreal-engine/#%E3%83%AC%E3%83%99%E3%83%AB%E3%82%B9%E3%83%88%E3%83%AA%E3%83%BC%E3%83%9F%E3%83%B3%E3%82%B0%E3%83%A2%E3%83%BC%E3%83%89

をみるとなにやら二つのモードについて解説があることに気付きます。
* 埋め込みモード (Pertitioned)
* レベルストリーミングモード (LevelStreaming)

です。
ちょっと説明を読んだだけだと「どういうことだってばよ？？？」と思ってしまいますね(個人的感想です)。
そもそもこの二つのモードが一体何なのかということが分かりません。ということで調べました。

```c++:LevelInstanceTypes.h 
UENUM()
enum class ELevelInstanceRuntimeBehavior : uint8
{
	None UMETA(Hidden),
	// Deprecated exists only to avoid breaking Actor Desc serialization
	Embedded_Deprecated UMETA(Hidden),
	// Default behavior is to move Level Instance level actors to the main world partition using World Partition clustering rules
	Partitioned,
	// Behavior only supported through Conversion Commandlet or on non OFPA Level Instances
	LevelStreaming UMETA(Hidden)
};
```
・・・どうやら非OFPAレベルのインスタンスを扱えるらしい。といわけでこのLevelStreamingモードについて解説します！

## 使い方

まずエンジンの標準機能では任意にこのLevelStreamingモードを使うことはできません。というわけでALevelInstanceを継承したアクターを自プロジェクト内に追加します。
```c++:MyStreamingLevelInstanceActor
#pragma once
#include "CoreMinimal.h"
#include "LevelInstance/LevelInstanceActor.h"
#include "MyStreamingLevelInstanceActor.generated.h"

UCLASS()
class AMyStreamingLevelInstanceActor : public ALevelInstance
{
	GENERATED_BODY()
	
#if WITH_EDITOR
	// Begin ILevelInstanceInterface
	virtual ELevelInstanceRuntimeBehavior GetDesiredRuntimeBehavior() const override { return ELevelInstanceRuntimeBehavior::LevelStreaming; }
	virtual ELevelInstanceRuntimeBehavior GetDefaultRuntimeBehavior() const override { return ELevelInstanceRuntimeBehavior::LevelStreaming; }
	// End ILevelInstanceInterface
#endif
};
```

WITH_EDITORマクロで囲われているので、ILevelIsntaceInterfaceの一部のAPIはパッケージビルドでは使えないことに注意してください。
レベルアセット(ワールドアセット)を設定するAPIもパッケージビルドではなくなります。(※自分のクラスに追加することは可能です)
```c++
class ENGINE_API ILevelInstanceInterface
{
...
#if WITH_EDITOR
	virtual bool SetWorldAsset(TSoftObjectPtr<UWorld> WorldAsset) = 0;

```

さてビルドしてエディタが立ち上がったら、まずレベルを作ります。 **ワールドパーティションではない Empty Level を選びます。** このレベルはOFPAが有効化されていません。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/111a63c5-5b4d-921f-36a4-0be41ac32b54.png" width=400 >
適当に球メッシュを並べて保存します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/9919af9f-09cd-2b31-c0e9-95a36a8605f2.png" width=400 >
このワールドパーティション+OFPAではないレベルをワールドパーティションレベルにLevelInstanceとしてドラッグアンドドロップしてPIEを動かすと。。。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/fb9b3491-b7c3-d46b-89d1-8453a9718335.png" width=400 >
```
△ Level Instance HundredSpheres (/Game/WPLevel) is not using external actors /Game/HundredSpheres.HundredSpheres
```
と怒られてしまいます。前述の通りLevelInstanceActorはOFPA(+ワールドパーティション)のレベルが想定されているからです。

さて問題点が分かったところで、新しく作った**LevelStreamingモード**のLevelInstanceActorを使ってみましょう
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/a7382302-aa7e-8c58-7c05-c2de8202447e.png" width=400 >
アクターを追加したらそのアクターのプロパティを開いて **OFPAではないレベル**を設定してPIEを起動！
100個の球がLevelStreamingモード+非OFPAレベルで、立方体はPartitionedモード+OFPAレベルです。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Level Streaming Mode <a href="https://t.co/mJaqEMlgPr">pic.twitter.com/mJaqEMlgPr</a></p>&mdash; Takashi.Suzuki (@wankotank) <a href="https://twitter.com/wankotank/status/1599432621880012800?ref_src=twsrc%5Etfw">December 4, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

立方体の方がワールドパーティションセルに分割されてストリーミングされているのに対して球の方は一括で出現したり消えたりします。
**つまりワールドパーティションへの埋め込まれていないことがわかります**。
公式ドキュメントで警告されているのはこのモードを用いてレベルストリーミングを行うと埋め込まれないため「ランタイムで制御するレベルストリーミングの数が増加する」ということです。

## ブループリント化
さて次にこのアクターをブループリント化してみましょう。
新規ブループリント作成でMyStreamingLevelInstanceActorを親として指定して、ブループリントアセットを作成し、プロパティに先ほどの非WPレベルを指定します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/5bcdcef1-0784-267e-b32c-770267bafbc4.png" width=400 >
このBPをレベルに配置しても問題無く動作します。

# 動的レベルインスタンスの作成と削除
このブループリントはアクターなのでSpawnActorで任意の位置に実体化することができます。個々のLevelInstanceActorはULevelStreamingLevelInstanceとして処理されるので動的にレベルインスタンス作成が可能です。
またアクターを削除することでレベルをストリームアウトさせることももちろん可能です。
テスト用にブループリントを書いてみます。SpawnActorでLevelInstanceActorを作り、DestroyActorしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/e0c7407f-7072-17a5-cf10-9f4fc3a0e50a.png)

動かしてみるとこのように任意のタイミングで動的にレベルインスタンスを生成したり、アクターの削除によってレベルインスタンスをストリームアウトさせることができることが確認できます。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Dynamic Level Instance creation using ILA <a href="https://t.co/BBOepTU19w">pic.twitter.com/BBOepTU19w</a></p>&mdash; Takashi.Suzuki (@wankotank) <a href="https://twitter.com/wankotank/status/1599442863196020736?ref_src=twsrc%5Etfw">December 4, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

**この機能はLoadLevelInstanceを代替することが可能で、パーシスタントレベルがワールドパティションレベルか非ワールドパティションレベルかを問わずに動作します**

https://qiita.com/donbutsu17/items/e129d638285d4b77df30



# まとめ
* LevelInstanceActorをBP化して動的インスタンスを作ることできます
* LevelInstanceActorで非OFPAレベルを扱いたい場合はLevelStreamingモードを使う。これにはプロジェクト側でのクラスの宣言が必要です
* 非OFPAレベルがソースになっているなら、非ワールドパティションおよびワールドパティションレベルの両方のレベルで動的インスタンス生成が可能です。
* 非OFPAレベルはワールドパーティションセルに埋め込まれ「ません」
* 非WPレベルにWPレベルをロードすることはできません。クラッシュの原因になります