ue-world-level-streaming について
ue-world-level-streaming は、UE 5.8 のワールド構成と実行時のレベル切り替えを扱う開発スキルです。World Partition、データレイヤー、サブレベル、マップ移動などの実装方法を案内します。
主な機能
- 01UE 5.8 ストリーミング API ガイド
- 02World Partition とストリーミングソース
- 03実行時のデータレイヤー状態制御
- 04レベルインスタンスとパック済み Actor
- 05サブレベルのロードと表示制御
- 06マップ移動と HLOD
活用シーン
- 01大規模なオープンワールドでセルをストリーミング
- 02データレイヤーで任意コンテンツを切り替え
- 03実行時に再利用可能なレベルチャンクをロード
- 04エリア表示やマップ移動を管理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ quodsoler/ unreal-engine-skills --skill ue-world-level-streamingインストール後、Claude Code で/ue-world-level-streamingを入力して読み込まれたか確認します。
同梱ファイルreferences/streaming-patterns.md
詳細
UE ワールドとレベルのストリーミング
対象エンジン:UE 5.8。以下の API は 5.8 のヘッダーで検証済みです。古い形式は「非推奨 — 使用しないでください」に記載しています。
このスキルでは、UWorld の構成と実行時の切り替えについて説明します。対象は World Partition グリッド、データレイヤー、HLOD、レベルインスタンス、従来型のサブレベルストリーミング、ストリーミングボリューム、マップ移動です。ここで扱う機能はすべて Engine モジュール(Engine/Source/Runtime/Engine)に含まれるため、PublicDependencyModuleNames に Core、CoreUObject、Engine を指定すれば十分です。例外は Level Streaming Persistence プラグインで、このプラグインには LevelStreamingPersistence モジュールが追加されます。
ゲームタイプごとのエンドツーエンド構成(オープンワールド、ハブ&スポーク、プロシージャル、チャプターベース、ボリューム駆動、専用サーバー)については、ストリーミングパターンを参照してください。
コンテキスト
.agents/ue-project-context.md が存在する場合は読み込んでください(モジュール名、規約、有効なプラグイン、GAS/ネットワーク構成)。ファイルがなくても作業を中断しないでください。
依頼内容とコードベースから対象領域を特定してください。妥当な解釈が複数あり、それぞれで異なるコードになる場合に限り、確認してください。
| 依頼の内容… | 参照先 |
|---|---|
| World Partition とサブレベルの選択 | ストリーミングモデルの選択 |
| グリッドストリーミング、ストリーミングソース、ブロッキングロード、CVar | World Partition の実行時処理 |
| パーティション分割されたワールド内のコンテンツの有効化と無効化 | データレイヤー |
| エディターに配置する再利用可能なレベルチャンク、パック済みアクター | レベルインスタンス |
LoadStreamLevel、ULevelStreaming、ボリューム、実行時インスタンス化 | サブレベルストリーミング |
OpenLevel、ServerTravel、ClientTravel、シームレストラベル | レベル移動 |
| ストリーミング解除されたセルの遠景プロキシ | HLOD |
| ストリーミングのサイクルをまたいだアクターの変更の保持 | Level Streaming Persistence |
| ワールドごとのマネージャー、移動後も維持する必要がある状態 | ワールドサブシステムとレベル間の状態 |
ストリーミングモデルの選択
| 状況 | 使用するもの | 理由 |
|---|---|---|
| 1 つの広大で連続したマップ | World Partition グリッド | 空間セルが自動で管理され、サブレベルを手動で管理する必要がありません |
| パーティション分割されたマップ内のオプションコンテンツ | データレイヤー | EDataLayerRuntimeState で移動せずに切り替えられます |
| エディターで同じレベルアセットを何度も配置する | ALevelInstance / APackedLevelActor | ひとまとまりとして編集でき、インスタンス化されたメッシュにパックされます |
| 実行時に同じレベルパッケージを何度もスポーンする | ULevelStreamingDynamic::LoadLevelInstance | インスタンスごとに一意の名前を持つパッケージを作成します |
| パーティション分割されていないマップ内の、手作業で作成した個別ゾーン | ULevelStreaming サブレベル | ゾーンごとに明示的なロードと表示の制御ができます |
| パーティション分割されていないマップ内の、近接性に応じて表示する屋内エリア | ALevelStreamingVolume | カメラ位置に応じて動作し、ゲームプレイコードは不要です |
| 別のマップ全体 | OpenLevel / ServerTravel | ワールドを破棄して再構築します |
World Partition とサブレベルストリーミングは、同じ永続レベルでは併用できません。UWorld::IsPartitionedWorld()(Engine/World.h:2968)でどちらが使用されているかを確認でき、AWorldSettings::GetWorldPartition()(GameFramework/WorldSettings.h:837)はパーティションオブジェクトを返します(WorldPartition メンバーは protected です)。
World Partition の実行時処理
UWorldPartition(WorldPartition/WorldPartition.h)は、アクターをセルに変換する実行時ハッシュを管理します。UWorldPartitionSubsystem はワールドごとのエントリーポイントであり、UTickableWorldSubsystem です。
#include "WorldPartition/WorldPartition.h"
#include "WorldPartition/WorldPartitionSubsystem.h"
UWorldPartitionSubsystem* Subsystem = GetWorld()->GetSubsystem<UWorldPartitionSubsystem>();
if (Subsystem && Subsystem->IsAllStreamingCompleted())
{
// Every cell has finished loading, adding to or removing from the world.
}
Subsystem->ForEachWorldPartition([](UWorldPartition* Partition)
{
return Partition->IsStreamingEnabled(); // return false to stop iterating
});
UWorldPartitionRuntimeCell はハッシュによって生成される単位です。アクターをポーリングする代わりに、クエリーソースを使って任意の位置の準備状態を確認します。
#include "WorldPartition/WorldPartitionRuntimeCell.h"
#include "WorldPartition/WorldPartitionStreamingSource.h"
TArray<FWorldPartitionStreamingQuerySource> QuerySources;
QuerySources.Emplace(TargetLocation); // FVector
const bool bReady = Subsystem->IsStreamingCompleted(
EWorldPartitionRuntimeCellState::Activated, QuerySources, /*bExactState=*/false);
EWorldPartitionRuntimeCellState は Unloaded、Loaded、Activated のいずれかです(WorldPartitionRuntimeCell.h:202)。
ストリーミングソース
IWorldPartitionStreamingSourceProvider(WorldPartitionStreamingSource.h にある通常のインターフェース構造体)を実装し、サブシステムに登録すれば、どのオブジェクトでもストリーミングを制御できます。一般的な「このアクターがセルを引き込む」ケースでは、代わりに UWorldPartitionStreamingSourceComponent を追加してください。このコンポーネントはすでにプロバイダーを実装し、自身を登録します。
オーバーライドする仮想関数は、virtual bool GetStreamingSources(TArray<FWorldPartitionStreamingSource>& StreamingSources) const と virtual const UObject* GetStreamingSourceOwner() const の2つです。BeginPlay で UWorldPartitionSubsystem::RegisterStreamingSourceProvider(this) を呼び出して登録し、EndPlay で UnregisterStreamingSourceProvider(this) を呼び出して解除します。アクターの完全な実装については、ストリーミングのパターンを参照してください。
EStreamingSourceTargetState:Loaded(メモリ内にあるが、ワールドには追加されていない)またはActivated。EStreamingSourcePriority:Highest、High、Normal、Low、Lowest、Default(Normalと同じ)。TargetGridsとEStreamingSourceTargetBehavior(Include/Exclude)を使うと、ソースを名前付きランタイムグリッドに限定できます。bBlockOnSlowLoadingを有効にすると、ストリーミングが遅れた際に、ソースはエンジンのブロッキング処理を利用します。装飾目的のソースでは false のままにしてください。UWorldPartitionSubsystem::GetStreamingSources(const UWorldPartition*, TArray<FWorldPartitionStreamingSource>&)を使うと、現在登録されているすべてのソースを取得できます。
ランタイム CVar
| CVar | 効果 |
|---|---|
wp.Runtime.EnableServerStreaming | Dedicated Server と Listen Server でのサーバー側セルストリーミング |
wp.Runtime.EnableServerStreamingOut | サーバーがセルをストリーミングアウトできるようにします |
wp.Runtime.BlockOnSlowStreaming | ブロッキングソースの読み込みが遅れたときに、ゲームスレッドをブロックします |
wp.Runtime.MaxLoadingStreamingCells | 同時に読み込むセルの上限を設定します |
wp.Runtime.OverrideRuntimeLoadingRange | テスト用にグリッドの読み込み範囲を上書きします |
wp.Runtime.ToggleDrawRuntimeHash2D | セルグリッドを画面に描画します |
wp.Runtime.DumpStreamingSources | 登録されているすべてのストリーミングソースをログに出力します |
wp.Runtime.HLOD | ランタイムで HLOD プロキシを有効または無効にします |
データレイヤー
アクターは AActor::DataLayerAssets(TArray<TSoftObjectPtr<UDataLayerAsset>>、GameFramework/Actor.h:1123)を通じてデータレイヤーを参照します。UDataLayerAsset は作成されたアセット、UDataLayerInstance はワールドごとのインスタンス、UDataLayerManager はランタイムアクセサーで、パーティション化されたワールド内の任意のオブジェクトから取得できます。
// MyDataLayerController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyDataLayerController.generated.h"
class UDataLayerAsset;
UCLASS()
class MYGAME_API AMyDataLayerController : public AActor
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "MyGame|Streaming")
void SetDungeonActive(bool bActive);
UFUNCTION(BlueprintCallable, Category = "MyGame|Streaming")
bool IsDungeonActivated() const;
protected:
UPROPERTY(EditAnywhere, Category = "MyGame|Streaming")
TObjectPtr<const UDataLayerAsset> DungeonLayer;
};
// MyDataLayerController.cpp
#include "MyDataLayerController.h"
#include "WorldPartition/DataLayer/DataLayerAsset.h"
#include "WorldPartition/DataLayer/DataLayerInstance.h"
#include "WorldPartition/DataLayer/DataLayerManager.h"
void AMyDataLayerController::SetDungeonActive(bool bActive)
{
UDataLayerManager* Manager = UDataLayerManager::GetDataLayerManager(this);
if (!Manager || !DungeonLayer)
{
return;
}
const EDataLayerRuntimeState NewState =
bActive ? EDataLayerRuntimeState::Activated : EDataLayerRuntimeState::Unloaded;
Manager->SetDataLayerRuntimeState(DungeonLayer, NewState, /*bInIsRecursive=*/true);
}
bool AMyDataLayerController::IsDungeonActivated() const
{
UDataLayerManager* Manager = UDataLayerManager::GetDataLayerManager(this);
if (!Manager || !DungeonLayer)
{
return false;
}
const UDataLayerInstance* Instance = Manager->GetDataLayerInstanceFromAsset(DungeonLayer);
return Instance && Manager->GetDataLayerInstanceRuntimeState(Instance) == EDataLayerRuntimeState::Activated;
}
EDataLayerRuntimeState(DataLayerInstance.h:23):Unloaded、Loaded(メモリ内にあるが表示されない状態。事前にウォームアップする場合に使用します)、Activated(読み込み済みで表示される状態)。
その他のマネージャーのエントリーポイント:SetDataLayerInstanceRuntimeState(const UDataLayerInstance*, EDataLayerRuntimeState, bool bInIsRecursive)、GetDataLayerInstanceEffectiveRuntimeState、GetDataLayerInstances()、GetDataLayerInstanceFromName(const FName&)、および BlueprintAssignable デリゲートの OnDataLayerInstanceRuntimeStateChanged。すべてのインスタンスを走査するには、次のようにします。
Manager->ForEachDataLayerInstance([](UDataLayerInstance* Instance)
{
return Instance->IsRuntime(); // return false to stop early
});
権限: ロードフィルターのないランタイムデータレイヤーは、サーバーで行われた状態変更にのみ応答し、その結果の状態がレプリケートされます。クライアント専用のレイヤーはクライアントで、サーバー専用のレイヤーはサーバーで変更する必要があります。古いワールドサブシステムアクセサーは非推奨のシムとして引き続き存在します。非推奨 — 使用しないでくださいを参照し、すべての処理を UDataLayerManager 経由にしてください。
レベルインスタンス
ALevelInstance(LevelInstance/LevelInstanceActor.h)は、.umap を再利用可能なチャンクとして配置し、ILevelInstanceInterface を実装します。ULevelInstanceSubsystem は、これらを管理する UWorldSubsystem です。
#include "Engine/World.h"
#include "LevelInstance/LevelInstanceActor.h"
#include "LevelInstance/LevelInstanceSubsystem.h"
void AMyLevelInstanceDriver::ShowRoom(ALevelInstance* RoomInstance)
{
if (!RoomInstance)
{
return;
}
RoomInstance->LoadLevelInstance(); // queued; loads on the subsystem's next streaming update, so IsLoaded() is false this frame
ULevelInstanceSubsystem* Subsystem = GetWorld()->GetSubsystem<ULevelInstanceSubsystem>();
if (Subsystem && Subsystem->IsLoaded(RoomInstance))
{
Subsystem->ForEachActorInLevelInstance(RoomInstance, [](AActor* LevelActor)
{
LevelActor->SetActorTickEnabled(true);
return true;
});
}
}
ILevelInstanceInterfaceのランタイムメンバー:LoadLevelInstance()、UnloadLevelInstance()、IsLoaded()、GetLoadedLevel()、GetLevelStreaming()、GetWorldAsset()、GetLevelInstanceID()、IsInitiallyVisible()。ULevelInstanceSubsystemのランタイムメンバー:GetLevelInstance(const FLevelInstanceID&)、GetOwningLevelInstance(const ULevel*)、RequestLoadLevelInstance(ILevelInstanceInterface*, bool bUpdate)、RequestUnloadLevelInstance(ILevelInstanceInterface*)、IsLoaded、IsLoading、ForEachActorInLevelInstance、GetLevelInstanceLevel。編集用のエントリーポイントはWITH_EDITOR専用です。ELevelInstanceRuntimeBehavior(LevelInstanceTypes.h:56)は、インスタンスをランタイムで読み込む方法を選択します。Partitioned(アクターがメインのパーティショングリッドに移動し、「Embedded」と表示されます)またはLevelStreaming(独自のストリーミングレベルを持ち、「Standalone」と表示されます)を指定します。APackedLevelActor(PackedLevelActor/PackedLevelActor.h)はALevelInstanceを継承し、静的メッシュをインスタンス化メッシュコンポーネントにベイクします。ELevelInstanceRuntimeBehavior::Noneを返し、ランタイムではレベルを読み込みません。
サブレベルのストリーミング
ULevelStreaming の状態
ELevelStreamingState(Engine/LevelStreaming.h:110)は、Removed、Unloaded、FailedToLoad、Loading、LoadedNotVisible、MakingVisible、LoadedVisible、MakingInvisible のいずれかです。GetLevelStreamingState() で読み取ります。要求先の状態は ELevelStreamingTargetState(Unloaded、UnloadedAndRemoved、LoadedNotVisible、LoadedVisible)です。
#include "Engine/LevelStreaming.h"
#include "Kismet/GameplayStatics.h"
ULevelStreaming* Streaming = UGameplayStatics::GetStreamingLevel(this, FName("/Game/Levels/L_Zone_A"));
if (Streaming)
{
Streaming->SetShouldBeLoaded(true);
Streaming->SetShouldBeVisible(true);
const bool bReady = Streaming->IsLevelLoaded() && Streaming->IsLevelVisible();
ULevel* Loaded = Streaming->GetLoadedLevel();
}
パラメーターを持たない BlueprintAssignable デリゲートが 4 つあります:OnLevelLoaded、OnLevelUnloaded、OnLevelShown、OnLevelHidden。パラメーターなしの UFUNCTION() に AddDynamic でバインドします。
ストリーミングレベルオブジェクト自体を破棄するには、SetIsRequestingUnloadAndRemoval(true) を使います。UWorld::StreamingLevels を直接変更してはいけません。StreamingLevelsToConsider を適切に管理する AddStreamingLevel、AddStreamingLevels、RemoveStreamingLevel、RemoveStreamingLevels、UpdateStreamingLevelShouldBeConsidered(Engine/World.h:1073-1109)を使ってください。
遅延読み込みとアンロード
UGameplayStatics::LoadStreamLevel(const UObject* WorldContextObject, FName LevelName, bool bMakeVisibleAfterLoad, bool bShouldBlockOnLoad, FLatentActionInfo LatentInfo) と UnloadStreamLevel(const UObject* WorldContextObject, FName LevelName, FLatentActionInfo LatentInfo, bool bShouldBlockOnUnload) は、FLatentActionInfo::ExecutionFunction を通じて処理を再開します。この関数は、リフレクションシステムを通じて名前で解決されます。そのため、コールバックはヘッダー内のクラス本体で宣言された UFUNCTION() でなければなりません。
// MyStreamingActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyStreamingActor.generated.h"
UCLASS()
class MYGAME_API AMyStreamingActor : public AActor
{
GENERATED_BODY()
public:
void StreamInRoom(FName LevelName);
void StreamOutRoom(FName LevelName);
// UFUNCTION on the declaration is mandatory: FLatentActionInfo::ExecutionFunction
// is looked up by name on CallbackTarget's UClass.
UFUNCTION()
void OnRoomLoaded();
UFUNCTION()
void OnRoomUnloaded();
protected:
bool bRoomReady = false;
};
// MyStreamingActor.cpp
#include "MyStreamingActor.h"
#include "Engine/LatentActionManager.h"
#include "Kismet/GameplayStatics.h"
void AMyStreamingActor::StreamInRoom(FName LevelName)
{
FLatentActionInfo LatentInfo;
LatentInfo.CallbackTarget = this;
LatentInfo.ExecutionFunction = FName("OnRoomLoaded");
LatentInfo.Linkage = 0;
LatentInfo.UUID = 1; // unique per pending latent action on this object
UGameplayStatics::LoadStreamLevel(this, LevelName,
/*bMakeVisibleAfterLoad=*/true, /*bShouldBlockOnLoad=*/false, LatentInfo);
}
void AMyStreamingActor::StreamOutRoom(FName LevelName)
{
FLatentActionInfo LatentInfo;
LatentInfo.CallbackTarget = this;
LatentInfo.ExecutionFunction = FName("OnRoomUnloaded");
LatentInfo.Linkage = 0;
LatentInfo.UUID = 2;
UGameplayStatics::UnloadStreamLevel(this, LevelName, LatentInfo, /*bShouldBlockOnUnload=*/false);
}
void AMyStreamingActor::OnRoomLoaded()
{
bRoomReady = true;
}
void AMyStreamingActor::OnRoomUnloaded()
{
bRoomReady = false;
}
LoadStreamLevelBySoftObjectPtr と UnloadStreamLevelBySoftObjectPtr は FName の代わりに TSoftObjectPtr<UWorld> を受け取り、パッケージ化に安全な形式です。UGameplayStatics::FlushLevelStreaming(const UObject*) は、保留中の処理が完了するまでブロックします。UWorld::FlushLevelStreaming(EFlushLevelStreamingType) を使うと、より細かく制御できます(None、Full、Visibility)。
ランタイムレベルのインスタンス化
ULevelStreamingDynamic は、1つのパッケージを異なるトランスフォームで繰り返し読み込みます。名前を指定するオーバーロードでは FTransform 全体を指定できないため、パラメーター構造体を使うオーバーロードを優先してください。
#include "Engine/LevelStreamingDynamic.h"
#include "Engine/World.h"
ULevelStreamingDynamic* AMyDungeonGenerator::SpawnRoom(const FTransform& RoomTransform, const FString& InstanceName)
{
ULevelStreamingDynamic::FLoadLevelInstanceParams Params(
GetWorld(), TEXT("/Game/Levels/L_Room_Corridor"), RoomTransform);
Params.OptionalLevelNameOverride = &InstanceName; // identical on server and clients
Params.bInitiallyVisible = true;
bool bSuccess = false;
ULevelStreamingDynamic* Level = ULevelStreamingDynamic::LoadLevelInstance(Params, bSuccess);
return bSuccess ? Level : nullptr;
}
インスタンスを破棄するには、SetShouldBeVisible(false)、SetShouldBeLoaded(false) の順に呼び出し、その後 SetIsRequestingUnloadAndRemoval(true) を呼び出します。インスタンスを追跡し、OnLevelShown にバインドするジェネレーターについては、ストリーミングのパターンを参照してください。
FLoadLevelInstanceParams のメンバー:World、LongPackageName、LevelTransform、OptionalLevelNameOverride(const FString*)、OptionalLevelStreamingClass、bLoadAsTempPackage、bInitiallyVisible、bAllowReuseExitingLevelStreaming、EditorPathOwner、LevelStreamingCreatedCallback。
Blueprint で公開されているオーバーロードは、LoadLevelInstance(UObject* WorldContextObject, FString LevelName, FVector Location, FRotator Rotation, bool& bOutSuccess, const FString& OptionalLevelNameOverride, TSubclassOf<ULevelStreamingDynamic> OptionalLevelStreamingClass, bool bLoadAsTempPackage) と LoadLevelInstanceBySoftObjectPtr です。後者は LevelName の代わりに TSoftObjectPtr<UWorld> を受け取り、残りのパラメーターは同じです。C++ 専用の BySoftObjectPtr オーバーロードは FTransform を受け取ります(Engine/LevelStreamingDynamic.h:94)。
ストリーミングボリューム
ALevelStreamingVolume は、カメラの位置に基づいてサブレベルを制御します。EStreamingVolumeUsage の値:SVB_Loading、SVB_LoadingAndVisibility、SVB_VisibilityBlockingOnLoad、SVB_BlockingOnLoad、SVB_LoadingNotVisible。ボリュームの StreamingUsage を設定します。影響を受ける対象は StreamingLevelNames に列挙され、ストリーミングレベルの EditorStreamingVolumes 配列がエディター側のリンクとなります。ULevelStreaming::bDisableDistanceStreaming = true を設定すると、レベルをボリューム制御から外せます。また、ボリューム境界でのちらつきを抑えるには MinTimeBetweenVolumeUnloadRequests を設定します。ワールドがワールドパーティションを使用している場合、ストリーミングボリュームは機能しません。
レベル移動
| 呼び出し | ヘッダー | 効果 |
|---|---|---|
UGameplayStatics::OpenLevel(const UObject*, FName LevelName, bool bAbsolute, FString Options) | Kismet/GameplayStatics.h:340 | ワールドを破棄します。クライアントは切断されます |
UGameplayStatics::OpenLevelBySoftObjectPtr(const UObject*, TSoftObjectPtr<UWorld>, bool, FString) | Kismet/GameplayStatics.h:350 | 同上。パッケージ化に安全な参照を使用します |
UWorld::ServerTravel(const FString& InURL, bool bAbsolute, bool bShouldSkipGameNotify) | Engine/World.h:4231 | サーバーが移動し、クライアントも追従します |
APlayerController::ClientTravel(const FString& URL, ETravelType, bool bSeamless, FGuid) | GameFramework/PlayerController.h:1413 | クライアント起点の移動です。TRAVEL_Absolute、TRAVEL_Partial、TRAVEL_Relative |
UWorld::SeamlessTravel(const FString& InURL, bool bAbsolute) | Engine/World.h:4243 | バックグラウンドで移行し、接続を維持します |
UGameplayStatics::GetCurrentLevelName(const UObject*, bool bRemovePrefixString) | Kismet/GameplayStatics.h:358 | 現在のマップ名 |
シームレスなトラベル
- GameMode のコンストラクターで
bUseSeamlessTravel = trueを設定します。これはすでにAGameModeBaseのUPROPERTY(GameModeBase.h:579)なので、再宣言しないでください。 - トランジションマップを設定します。設定しない場合、エンジンは空のダミーワールドを経由してトランジションします(
World.cpp:8367)。PIE では、net.AllowPIESeamlessTravel=1を設定しない限り、シームレスなトラベルはハードトラベルにフォールバックします(GameModeBase.cpp:506)。
[/Script/EngineSettings.GameMapsSettings]
TransitionMap=/Game/Maps/L_Transition.L_Transition
- 永続化フックをオーバーライドします。シグネチャは
GameFramework/GameModeBase.h:239-268のものと完全に一致します。
// MyGameMode.cpp
void AMyGameMode::GetSeamlessTravelActorList(bool bToTransition, TArray<AActor*>& ActorList)
{
Super::GetSeamlessTravelActorList(bToTransition, ActorList);
if (MyPersistentManager)
{
ActorList.Add(MyPersistentManager); // called for both legs (to transition, then to destination); add on both
}
}
void AMyGameMode::PostSeamlessTravel()
{
Super::PostSeamlessTravel();
}
void AMyGameMode::HandleSeamlessTravelPlayer(AController*& C)
{
Super::HandleSeamlessTravelPlayer(C);
}
APlayerController::GetSeamlessTravelActorList(bool bToEntry, TArray<AActor*>& ActorList) は、クライアント側に対応するフックです。APlayerController::SeamlessTravelTo(APlayerController* NewPC) / SeamlessTravelFrom(APlayerController* OldPC) と APlayerState::SeamlessTravelTo(APlayerState* NewPlayerState) は、プレイヤーごとの状態を新しいオブジェクトにコピーします。UWorld::IsInSeamlessTravel() と UWorld::SetSeamlessTravelMidpointPause(bool bNowPaused) は中間地点を制御します。UWorld::OnSeamlessTravelStart と UWorld::OnSeamlessTravelTransition は、オブザーバー向けの静的マルチキャストデリゲートです。
トラベル中のレプリケーション動作(チャネルの破棄、アクターの再生成、ネットワーク関連性)は ue-networking-replication の担当です。UNetDriver::NotifyActorLevelUnloaded(AActor*) は、ストリーミングレベルがアンロードされたときに使われるフックです。
HLOD
HLOD は、ストリーミングアウトされたセルの代わりとなるプロキシを生成します。UHLODLayer(WorldPartition/HLOD/HLODLayer.h)はティアを設定します。グリッド配置はレイヤーではなく、ランタイムパーティションの設定で決まります。ハッシュと再構築の管理は UHLODRebuildPolicy と UHLODRebuildPolicyData を通じて行われ、ビルダー設定(UHLODBuilderSettings)は ComputeHLODHash(FHLODHashBuilder&) を実装します。
ランタイムでは、UWorldPartitionHLODRuntimeSubsystem(UWorldSubsystem)はアクターではなく IWorldPartitionHLODObject としてプロキシを追跡します。利用できる機能は RegisterHLODObject、UnregisterHLODObject、GetHLODObjectsForCell(const UWorldPartitionRuntimeCell*)、OnHLODObjectRegisteredEvent()、OnHLODObjectUnregisteredEvent()、IsHLODEnabled()、IsWarmupEnabled() です。AWorldPartitionHLOD と AWorldPartitionCustomHLOD(WorldPartition/HLOD/CustomHLODActor.h)はどちらもこのインターフェースを実装し、AWorldPartitionHLODSourceCellPlaceholder はビルド中にソースセルの代わりを務めます。
出荷前に HLOD をビルドしてください。HLOD がない場合、読み込み範囲の外側はすべて単純に表示されません。
レベルストリーミングの永続化
Level Streaming Persistence プラグイン(5.8 では Experimental)は、プロパティ値、破棄されたアクターの記録、再スポーンデータをストリーミングレベルに保持するため、レベルがストリーミングアウトして再び読み込まれると、変更が反映された状態に戻ります。Build.cs に LevelStreamingPersistence モジュールを追加します。
ULevelStreamingPersistenceManager は UWorldSubsystem です。Blueprint から呼び出せる機能は SerializeTo(TArray<uint8>& OutPayload, bool bForceUpdate)、InitializeFrom(const TArray<uint8>& InPayload)、EjectPlacedActor(AActor*)、RecreateActorInLevel(AActor*, ULevel* NewOwningLevel)、RecreateActorInPersistentLevel(AActor*) です。テンプレートアクセサー SetPropertyValue、TrySetPropertyValue、GetPropertyValue は、オブジェクトパスとプロパティ名を指定して、個々の永続化プロパティの読み書きを行います。どのプロパティを永続化するかは、Engine の設定を持つ UDeveloperSettings、ULevelStreamingPersistenceSettings で宣言します。
InitializeFrom は、マップごとに一度、早い段階で呼び出してください。カスタムの UWorldSubsystem::Initialize から呼び出すことで、アクターの Begin Play が始まる前に値を適用できます。生成されるペイロードは USaveGame に保存するものです。詳しくは ue-serialization-savegames を参照してください。
ワールドサブシステムとレベルをまたぐ状態
UWorldSubsystem(Subsystems/WorldSubsystem.h)は UWorld ごとに一度作成され、トラベル時を含め、そのワールドとともに破棄されます。ストリーミングマネージャーやゾーントラッカーを置くのに適しています。サブシステムの型の一覧は ue-cpp-foundations にあります。
オーバーライド可能なフックは、Subsystems/WorldSubsystem.h:34-66 からの引用です。virtual bool ShouldCreateSubsystem(UObject* Outer) const、virtual void PostInitialize()、virtual void OnWorldBeginPlay(UWorld& InWorld)、virtual void OnWorldEndPlay(UWorld& InWorld)、virtual void OnWorldComponentsUpdated(UWorld& World)、virtual void PreDeinitialize()、virtual bool DoesSupportWorldType(const EWorldType::Type WorldType) const。GetWorld()->GetSubsystem<UMyStreamingManager>() でアクセスします。ゾーンマネージャーサブシステムの実例はストリーミングのパターンにあります。
フレームごとの処理には UTickableWorldSubsystem を継承し、Tick(float DeltaTime) と(純粋仮想関数の)GetStatId() const を実装します。ティックの有効化と無効化は Initialize と Deinitialize のオーバーライドで行われるため、どちらでも必ず Super:: を呼び出してください。
| 仕組み | ライフタイム | 用途 |
|---|---|---|
UGameInstance | アプリケーションセッション全体 | マップをまたぐプレイヤーとセッションのデータ |
UGameInstanceSubsystem | アプリケーションセッション全体 | すべてのワールドより長く存続するサービス |
| シームレス トラベルのアクターリスト | 遷移中のみ | 物理的に移動するアクター |
UWorldSubsystem | 1 つのワールド | ワールド単位のキャッシュ。トラベル前に UGameInstance へ移します |
USaveGame | ディスク | 進行状況 — ue-serialization-savegames を参照 |
非推奨 — 使用しないでください
| 出力しないもの | 5.8 で使用するもの | 出典 |
|---|---|---|
AActor::DataLayers, FActorDataLayer | AActor::DataLayerAssets | GameFramework/Actor.h:1115 の UE_DEPRECATED(5.8) |
UDataLayerSubsystem | UDataLayerManager | WorldPartition/DataLayer/DataLayerSubsystem.h:37 の UE_DEPRECATED(5.3) |
AWorldDataLayers::OverwriteDataLayerRuntimeStates | UDataLayerManager::SetDataLayerRuntimeState | WorldPartition/DataLayer/WorldDataLayers.h:135 の UE_DEPRECATED(5.8) |
UDataLayerInstance::GetDataLayerFName | UObject::GetFName | WorldPartition/DataLayer/DataLayerInstance.h:190 の UE_DEPRECATED(5.8) |
エディター外での UDataLayerManager::GetDataLayerInstanceNames | 実行時にアセットまたはインスタンスのポインターを渡す | WorldPartition/DataLayer/DataLayerManager.h:121 の UE_DEPRECATED(5.8) |
ULevelStreaming::ECurrentState, GetCurrentState() | ELevelStreamingState, GetLevelStreamingState() | Engine/LevelStreaming.h:367 の UE_DEPRECATED(5.2) |
UWorldPartitionLevelStreamingDynamic::LoadInEditor / UnloadFromEditor | サポートされている代替手段なし | WorldPartition/WorldPartitionLevelStreamingDynamic.h:63 の UE_DEPRECATED(5.7) |
UHLODBuilderSettings::GetCRC() | ComputeHLODHash(FHLODHashBuilder&) | WorldPartition/HLOD/HLODBuilder.h:43 の UE_DEPRECATED(5.7) |
UWorldPartitionHLODSourceActors::GetHLODHash() | ComputeHLODHash(FHLODHashBuilder&) | WorldPartition/HLOD/HLODSourceActors.h:27 の UE_DEPRECATED(5.7) |
AWorldPartitionHLOD::GetHLODHash / ComputeHLODHash / SetHLODHash | UHLODRebuildPolicyData を使った GetHLODRebuildPolicyDataSet / SetHLODRebuildPolicyDataSet | WorldPartition/HLOD/HLODActor.h:146 の UE_DEPRECATED(5.8) |
UHLODLayer::GetCellSize / GetLoadingRange / IsSpatiallyLoaded | ランタイムパーティションの設定 | WorldPartition/HLOD/HLODLayer.h:82 の UE_DEPRECATED(5.7) |
UWorldPartitionHLODRuntimeSubsystem::RegisterHLODActor / GetHLODActorsForCell / GetNumOutdatedHLODActors | RegisterHLODObject / GetHLODObjectsForCell / GetNumOutdatedHLODObjects | WorldPartition/HLOD/HLODRuntimeSubsystem.h:77 の UE_DEPRECATED(5.6) |
AWorldPartitionCustomHLODPlaceholder | AWorldPartitionHLODSourceCellPlaceholder | WorldPartition/HLOD/CustomHLODPlaceholderActor.h:9 の UE_DEPRECATED(5.8) |
UWorldPartitionStreamingSourceComponent::TargetGrid / TargetHLODLayers | TargetBehavior を指定した TargetGrids | Components/WorldPartitionStreamingSourceComponent.h:63 の DeprecatedProperty |
よくある間違い
インラインではない定義の上にある UFUNCTION()。 .cpp 内に UFUNCTION() void AMyActor::OnRoomLoaded() {} として書かれた潜在コールバックは UHT から認識されないため、FLatentActionInfo::ExecutionFunction が解決されず、継続処理が通知されないままになります。ヘッダー内のクラス本体で宣言してください。
複数の FLatentActionInfo::UUID の再利用。 同じ CallbackTarget 上で保留中の2つのアクションが同じ UUID を共有すると衝突し、一方が破棄されます。呼び出し箇所ごとに固有の定数を割り当ててください。
ロード画面以外でのブロッキングロード。 bShouldBlockOnLoad = true、FlushLevelStreaming、wp.Runtime.BlockOnSlowStreaming はいずれもゲームスレッドを停止させます。サーバーでは、すべてのクライアントが停止します。
クライアントでのデータレイヤー状態の設定。 ロードフィルターのないランタイムデータレイヤーはサーバー上でのみ反応し、その状態はレプリケーションされます。クライアント側で呼び出しても何も起きないように見えます。
マルチプレイヤーでの動的レベル名の不一致。 ULevelStreamingDynamic::LoadLevelInstance はプロセスごとに一意のパッケージ名を生成します。OptionalLevelNameOverride にサーバーとクライアントで同じ文字列を設定しないと、インスタンスは別々のパッケージになり、何も一致しません。
レベル境界をまたぐハード参照。 アンロード時には、レベルパッケージ内のすべてのオブジェクトがガベージとしてマークされます(LevelStreamingGCHelper.cpp:201-208)。そのため、永続アクターからストリーミングレベルへの UPROPERTY() ポインターは null にされ(UPROPERTY ではない生ポインターはダングリングし)、レベルが再びストリーミングされても参照先は更新されません。境界をまたぐ参照には TSoftObjectPtr または TWeakObjectPtr を使用してください。
ストリーミングボリュームが World Partition で機能すると期待すること。 機能しません。代わりにデータレイヤーまたはストリーミングソースを使用してください。
専用サーバーがクライアントと同じようにストリーミングすると考えること。 サーバーのセルストリーミングは有効化しない限り無効であり、ストリーミングソースは登録したものだけです。サーバー上でアクターが「見つからない」問題をデバッグする前に、UWorldPartition::IsServerStreamingEnabled() を確認してください。
UWorld::StreamingLevels の直接変更。 StreamingLevelsToConsider を迂回するため、レベルが評価されません。AddStreamingLevel / RemoveStreamingLevel 系の関数を使用してください。
UTickableWorldSubsystem での Super::Initialize / Super::Deinitialize の呼び出し忘れ。 ティックを有効化および無効化するのは、これらの呼び出しです。
関連スキル
ue-cpp-foundations— サブシステム型の完全な一覧、TObjectPtr、TSoftObjectPtr、GC、オブジェクトのライフタイムに関するルール。ue-networking-replication— トラベル中のレプリケーション、ネット関連性、DOREPLIFETIME、専用サーバーの権限。ue-gameplay-framework— GameMode と GameState のトラベルコールバック、ログインフロー、UGameInstanceの役割。ue-data-assets-tables—UDataAsset(UDataLayerAssetの基底クラス)、アセットの非同期ロード、Asset Manager。ue-procedural-generation— 分割されたレベルにコンテンツを配置する PCG 駆動の生成。ue-serialization-savegames—USaveGame、Level Streaming Persistence のペイロードのディスクへの書き込み。ue-actor-component-architecture— ストリーミングソースアクターとワールドマネージャーのコンポーネント設計。ue-game-features— Game Feature プラグイン、GameFeatureAction、モジュラーコンポーネントマネージャー。ue-sequencer-cinematics— Level Sequence、再生、シネカメラ、Movie Render Graph