mcpField
U
ue-world-level-streaming

ue-world-level-streaming

github.com/quodsoler開発4191.2k 回インストール4 週間前に更新
リポジトリ

ue-world-level-streaming について

ue-world-level-streaming は、UE 5.8 のワールド構成と実行時のレベル切り替えを扱う開発スキルです。World Partition、データレイヤー、サブレベル、マップ移動などの実装方法を案内します。

主な機能

  1. 01UE 5.8 ストリーミング API ガイド
  2. 02World Partition とストリーミングソース
  3. 03実行時のデータレイヤー状態制御
  4. 04レベルインスタンスとパック済み Actor
  5. 05サブレベルのロードと表示制御
  6. 06マップ移動と HLOD

活用シーン

  1. 01大規模なオープンワールドでセルをストリーミング
  2. 02データレイヤーで任意コンテンツを切り替え
  3. 03実行時に再利用可能なレベルチャンクをロード
  4. 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 とサブレベルの選択ストリーミングモデルの選択
グリッドストリーミング、ストリーミングソース、ブロッキングロード、CVarWorld 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.EnableServerStreamingDedicated 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現在のマップ名
シームレスなトラベル
  1. GameMode のコンストラクターで bUseSeamlessTravel = true を設定します。これはすでに AGameModeBase の UPROPERTY(GameModeBase.h:579)なので、再宣言しないでください。
  2. トランジションマップを設定します。設定しない場合、エンジンは空のダミーワールドを経由してトランジションします(World.cpp:8367)。PIE では、net.AllowPIESeamlessTravel=1 を設定しない限り、シームレスなトラベルはハードトラベルにフォールバックします(GameModeBase.cpp:506)。
[/Script/EngineSettings.GameMapsSettings]
TransitionMap=/Game/Maps/L_Transition.L_Transition
  1. 永続化フックをオーバーライドします。シグネチャは 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アプリケーションセッション全体すべてのワールドより長く存続するサービス
シームレス トラベルのアクターリスト遷移中のみ物理的に移動するアクター
UWorldSubsystem1 つのワールドワールド単位のキャッシュ。トラベル前に UGameInstance へ移します
USaveGameディスク進行状況 — ue-serialization-savegames を参照

非推奨 — 使用しないでください

出力しないもの5.8 で使用するもの出典
AActor::DataLayers, FActorDataLayerAActor::DataLayerAssetsGameFramework/Actor.h:1115 の UE_DEPRECATED(5.8)
UDataLayerSubsystemUDataLayerManagerWorldPartition/DataLayer/DataLayerSubsystem.h:37 の UE_DEPRECATED(5.3)
AWorldDataLayers::OverwriteDataLayerRuntimeStatesUDataLayerManager::SetDataLayerRuntimeStateWorldPartition/DataLayer/WorldDataLayers.h:135 の UE_DEPRECATED(5.8)
UDataLayerInstance::GetDataLayerFNameUObject::GetFNameWorldPartition/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 / SetHLODHashUHLODRebuildPolicyData を使った GetHLODRebuildPolicyDataSet / SetHLODRebuildPolicyDataSetWorldPartition/HLOD/HLODActor.h:146 の UE_DEPRECATED(5.8)
UHLODLayer::GetCellSize / GetLoadingRange / IsSpatiallyLoadedランタイムパーティションの設定WorldPartition/HLOD/HLODLayer.h:82 の UE_DEPRECATED(5.7)
UWorldPartitionHLODRuntimeSubsystem::RegisterHLODActor / GetHLODActorsForCell / GetNumOutdatedHLODActorsRegisterHLODObject / GetHLODObjectsForCell / GetNumOutdatedHLODObjectsWorldPartition/HLOD/HLODRuntimeSubsystem.h:77 の UE_DEPRECATED(5.6)
AWorldPartitionCustomHLODPlaceholderAWorldPartitionHLODSourceCellPlaceholderWorldPartition/HLOD/CustomHLODPlaceholderActor.h:9 の UE_DEPRECATED(5.8)
UWorldPartitionStreamingSourceComponent::TargetGrid / TargetHLODLayersTargetBehavior を指定した TargetGridsComponents/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

コメント