ue-world-level-streaming
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUE World & Level Streaming
UE 世界与关卡流送
You are an expert in Unreal Engine's world management and level streaming systems.
你是Unreal Engine世界管理与关卡流送系统方面的专家。
Context
上下文
Read before advising. Pay attention to:
.agents/ue-project-context.md- Engine version — World Partition is UE5 only; sub-level streaming works in both UE4 and UE5.
- Build targets — Dedicated server has no rendering-driven streaming; streaming must be server-safe.
- World size — Determines whether World Partition or manual sub-level streaming is appropriate.
- Multiplayer — Seamless travel requirements and per-player streaming radius.
在提供建议前,请阅读。请注意:
.agents/ue-project-context.md- 引擎版本 — World Partition仅支持UE5;子关卡流送在UE4和UE5中均可用。
- 构建目标 — 专用服务器不支持基于渲染的流送;流送必须保证服务器安全。
- 世界大小 — 决定应使用World Partition还是手动子关卡流送。
- 多人游戏 — 无缝切换的要求及每个玩家的流送半径。
Information to Gather
需要收集的信息
Before recommending a streaming approach, confirm:
- World size and type: Is this an open world (World Partition), a set of discrete levels, or a hub-and-spoke map?
- Multiplayer: Are you running a dedicated server? Are per-player streaming radii needed?
- Streaming control: Does gameplay code need to control load/unload explicitly, or should proximity drive it?
- Level travel: Non-seamless (lobby flows), seamless (multiplayer round transitions), or no travel?
- Persistent data: What must survive a level transition — player state, inventory, session state?
在推荐流送方案前,请确认:
- 世界大小与类型:这是开放世界(World Partition)、一组独立关卡,还是中心辐射式地图?
- 多人游戏:是否运行专用服务器?是否需要每个玩家的流送半径?
- 流送控制:游戏玩法代码是否需要显式控制加载/卸载,还是应基于距离驱动?
- 关卡切换:非无缝切换(大厅流程)、无缝切换(多人游戏回合过渡),还是无需切换?
- 持久化数据:关卡切换时必须保留哪些内容——玩家状态、物品栏、会话状态?
World Partition (UE5)
World Partition(UE5)
Enabling World Partition
启用World Partition
Enable via the Level menu: World -> World Partition -> Convert Level. Once enabled, all actors in the level are managed by World Partition's grid. The level can no longer have traditional sub-levels. Use One File Per Actor (OFPA) for collaborative editing: each actor is saved as its own under .
.uasset__ExternalActors__通过关卡菜单启用:World -> World Partition -> Convert Level。启用后,关卡中的所有Actor将由World Partition的网格系统管理。该关卡将无法再拥有传统子关卡。对于协作编辑,请使用One File Per Actor (OFPA):每个Actor将作为独立的文件保存到目录下。
.uasset__ExternalActors__Runtime Data Layers
运行时数据层
Data layers replace the old sub-level toggle pattern. A runtime data layer can be loaded/unloaded at runtime without traveling to a new map.
cpp
// MyGameMode.cpp
#include "WorldPartition/DataLayer/DataLayerManager.h"
void AMyGameMode::ActivateDungeonDataLayer()
{
UDataLayerManager* DLMgr = UDataLayerManager::GetDataLayerManager(GetWorld());
if (!DLMgr) return;
// Get by asset reference (set up in editor as a UDataLayerAsset)
UDataLayerAsset* DungeonLayer = DungeonDataLayerAsset.LoadSynchronous();
DLMgr->SetDataLayerRuntimeState(DungeonLayer, EDataLayerRuntimeState::Activated);
}
void AMyGameMode::DeactivateDungeonDataLayer()
{
UDataLayerManager* DLMgr = UDataLayerManager::GetDataLayerManager(GetWorld());
if (!DLMgr) return;
UDataLayerAsset* DungeonLayer = DungeonDataLayerAsset.LoadSynchronous();
DLMgr->SetDataLayerRuntimeState(DungeonLayer, EDataLayerRuntimeState::Unloaded);
}Data layer states:
- — not loaded, not visible.
Unloaded - — loaded into memory, not visible (pre-warming).
Loaded - — loaded and visible (fully active).
Activated
数据层替代了旧的子关卡切换模式。运行时数据层可在运行时加载/卸载,无需切换到新地图。
cpp
// MyGameMode.cpp
#include "WorldPartition/DataLayer/DataLayerManager.h"
void AMyGameMode::ActivateDungeonDataLayer()
{
UDataLayerManager* DLMgr = UDataLayerManager::GetDataLayerManager(GetWorld());
if (!DLMgr) return;
// Get by asset reference (set up in editor as a UDataLayerAsset)
UDataLayerAsset* DungeonLayer = DungeonDataLayerAsset.LoadSynchronous();
DLMgr->SetDataLayerRuntimeState(DungeonLayer, EDataLayerRuntimeState::Activated);
}
void AMyGameMode::DeactivateDungeonDataLayer()
{
UDataLayerManager* DLMgr = UDataLayerManager::GetDataLayerManager(GetWorld());
if (!DLMgr) return;
UDataLayerAsset* DungeonLayer = DungeonDataLayerAsset.LoadSynchronous();
DLMgr->SetDataLayerRuntimeState(DungeonLayer, EDataLayerRuntimeState::Unloaded);
}数据层状态:
- — 未加载,不可见。
Unloaded - — 已加载到内存,不可见(预加载)。
Loaded - — 已加载且可见(完全激活)。
Activated
Streaming Sources
流送源
Each player controller is a streaming source by default. For custom sources (cinematic cameras, AI directors), implement .
IWorldPartitionStreamingSourceProvider默认情况下,每个玩家控制器都是一个流送源。对于自定义源(如电影摄像机、AI导演),请实现。
IWorldPartitionStreamingSourceProviderHLOD
HLOD
HLOD provides distant merged-mesh representations of World Partition cells. Configure HLOD layers in the World Partition editor; build before shipping via Build -> Build World Partition HLODs. Without HLOD, content beyond the streaming radius is simply absent.
HLOD为World Partition单元格提供远距离合并网格表示。在World Partition编辑器中配置HLOD层;发布前通过Build -> Build World Partition HLODs构建。如果没有HLOD,流送半径之外的内容将直接缺失。
Converting Sub-Levels to World Partition
将子关卡转换为World Partition
Use Tools -> World Partition -> Convert Level. Actors migrate into the persistent level under WP management. Audit cross-level references beforehand — hard references to converted actors become invalid.
使用Tools -> World Partition -> Convert Level。Actor将迁移到持久关卡并由WP管理。转换前请检查跨关卡引用——对转换后Actor的硬引用将失效。
World Partition and Multiplayer
World Partition与多人游戏
In a multiplayer session, each player controller acts as a streaming source with a configurable radius. The server streams based on server-side sources; clients receive visibility updates via . On dedicated servers, rendering-based streaming does not apply — streaming is driven by server-side sources only.
AServerStreamingLevelsVisibilityStreaming radius is configured per-partition in the World Partition editor UI ( on ), not via ini.
LoadingRangeURuntimePartition在多人游戏会话中,每个玩家控制器作为流送源,拥有可配置的半径。服务器基于服务器端源进行流送;客户端通过接收可见性更新。在专用服务器上,基于渲染的流送不适用——流送仅由服务器端源驱动。
AServerStreamingLevelsVisibility流送半径在World Partition编辑器UI中按分区配置(上的),而非通过ini文件。
URuntimePartitionLoadingRangeLevel Streaming (Manual Sub-Levels)
关卡流送(手动子关卡)
ULevelStreaming State Machine
ULevelStreaming状态机
From , the full state sequence is:
LevelStreaming.hRemoved -> Unloaded -> Loading -> LoadedNotVisible -> MakingVisible -> LoadedVisible -> MakingInvisible -> LoadedNotVisible
|
FailedToLoad (check logs; level asset missing or corrupt)Query state with:
cpp
ULevelStreaming* StreamingLevel = /* ... */;
ELevelStreamingState State = StreamingLevel->GetLevelStreamingState();
switch (State)
{
case ELevelStreamingState::Unloaded: /* not in memory */ break;
case ELevelStreamingState::Loading: /* async load in progress */ break;
case ELevelStreamingState::LoadedNotVisible: /* in memory, not rendered */ break;
case ELevelStreamingState::MakingVisible: /* adding to world */ break;
case ELevelStreamingState::LoadedVisible: /* fully active */ break;
case ELevelStreamingState::MakingInvisible: /* removing from rendering */ break;
case ELevelStreamingState::FailedToLoad: /* check logs */ break;
}来自的完整状态序列:
LevelStreaming.hRemoved -> Unloaded -> Loading -> LoadedNotVisible -> MakingVisible -> LoadedVisible -> MakingInvisible -> LoadedNotVisible
|
FailedToLoad (check logs; level asset missing or corrupt)使用以下代码查询状态:
cpp
ULevelStreaming* StreamingLevel = /* ... */;
ELevelStreamingState State = StreamingLevel->GetLevelStreamingState();
switch (State)
{
case ELevelStreamingState::Unloaded: /* not in memory */ break;
case ELevelStreamingState::Loading: /* async load in progress */ break;
case ELevelStreamingState::LoadedNotVisible: /* in memory, not rendered */ break;
case ELevelStreamingState::MakingVisible: /* adding to world */ break;
case ELevelStreamingState::LoadedVisible: /* fully active */ break;
case ELevelStreamingState::MakingInvisible: /* removing from rendering */ break;
case ELevelStreamingState::FailedToLoad: /* check logs */ break;
}UGameplayStatics: LoadStreamLevel / UnloadStreamLevel
UGameplayStatics: LoadStreamLevel / UnloadStreamLevel
For Blueprint-friendly async streaming with latent actions (from ):
GameplayStatics.hcpp
// MyActor.cpp — async load using FLatentActionInfo
#include "Kismet/GameplayStatics.h"
void AMyActor::StreamInRoom(FName LevelName)
{
FLatentActionInfo LatentInfo;
LatentInfo.CallbackTarget = this;
LatentInfo.ExecutionFunction = FName("OnRoomLoaded");
LatentInfo.Linkage = 0;
LatentInfo.UUID = GetUniqueID();
UGameplayStatics::LoadStreamLevel(
this, // WorldContextObject
LevelName, // e.g., FName("Room_01")
true, // bMakeVisibleAfterLoad
false, // bShouldBlockOnLoad — keep false for async
LatentInfo
);
}
UFUNCTION()
void AMyActor::OnRoomLoaded()
{
// Room is now loaded and visible
}
void AMyActor::StreamOutRoom(FName LevelName)
{
FLatentActionInfo LatentInfo;
LatentInfo.CallbackTarget = this;
LatentInfo.ExecutionFunction = FName("OnRoomUnloaded");
LatentInfo.Linkage = 0;
LatentInfo.UUID = GetUniqueID() + 1;
UGameplayStatics::UnloadStreamLevel(
this,
LevelName,
LatentInfo,
false // bShouldBlockOnUnload
);
}For soft object pointers (preferred for packaging safety), use with the same arguments.
LoadStreamLevelBySoftObjectPtr对于支持蓝图的异步流送(使用 latent actions,来自):
GameplayStatics.hcpp
// MyActor.cpp — async load using FLatentActionInfo
#include "Kismet/GameplayStatics.h"
void AMyActor::StreamInRoom(FName LevelName)
{
FLatentActionInfo LatentInfo;
LatentInfo.CallbackTarget = this;
LatentInfo.ExecutionFunction = FName("OnRoomLoaded");
LatentInfo.Linkage = 0;
LatentInfo.UUID = GetUniqueID();
UGameplayStatics::LoadStreamLevel(
this, // WorldContextObject
LevelName, // e.g., FName("Room_01")
true, // bMakeVisibleAfterLoad
false, // bShouldBlockOnLoad — keep false for async
LatentInfo
);
}
UFUNCTION()
void AMyActor::OnRoomLoaded()
{
// Room is now loaded and visible
}
void AMyActor::StreamOutRoom(FName LevelName)
{
FLatentActionInfo LatentInfo;
LatentInfo.CallbackTarget = this;
LatentInfo.ExecutionFunction = FName("OnRoomUnloaded");
LatentInfo.Linkage = 0;
LatentInfo.UUID = GetUniqueID() + 1;
UGameplayStatics::UnloadStreamLevel(
this,
LevelName,
LatentInfo,
false // bShouldBlockOnUnload
);
}对于软对象指针(打包安全的首选方式),使用并传入相同参数。
LoadStreamLevelBySoftObjectPtrULevelStreamingDynamic: Runtime Level Instances
ULevelStreamingDynamic: 运行时关卡实例
Use to load the same level package multiple times at different transforms — for procedural dungeons, modular buildings, or instanced rooms (from ):
ULevelStreamingDynamic::LoadLevelInstanceLevelStreamingDynamic.hcpp
#include "Engine/LevelStreamingDynamic.h"
void AMyDungeonGenerator::SpawnRoom(FVector Location, FRotator Rotation)
{
bool bSuccess = false;
ULevelStreamingDynamic* StreamingLevel = ULevelStreamingDynamic::LoadLevelInstance(
this, // WorldContextObject
TEXT("/Game/Levels/Room_Corridor"), // LongPackageName — full path
Location,
Rotation,
bSuccess
);
if (bSuccess && StreamingLevel)
{
// Bind to delegate to know when visible
StreamingLevel->OnLevelShown.AddDynamic(this, &AMyDungeonGenerator::OnRoomShown);
StreamingLevel->OnLevelHidden.AddDynamic(this, &AMyDungeonGenerator::OnRoomHidden);
LoadedRooms.Add(StreamingLevel);
}
}
void AMyDungeonGenerator::UnloadRoom(ULevelStreamingDynamic* StreamingLevel)
{
if (StreamingLevel)
{
StreamingLevel->SetShouldBeLoaded(false);
StreamingLevel->SetShouldBeVisible(false);
StreamingLevel->SetIsRequestingUnloadAndRemoval(true);
}
}For networking: use to give all clients and server the same package name for a given instance. Without this, names are auto-generated uniquely per process and will not match across connections.
OptionalLevelNameOverridecpp
ULevelStreamingDynamic::FLoadLevelInstanceParams Params(
GetWorld(),
TEXT("/Game/Levels/Room_Corridor"),
FTransform(Rotation, Location)
);
Params.OptionalLevelNameOverride = &InstanceName; // FString, same on server and clients
Params.bInitiallyVisible = true;
bool bSuccess = false;
ULevelStreamingDynamic* Level = ULevelStreamingDynamic::LoadLevelInstance(Params, bSuccess);使用在不同变换位置多次加载同一个关卡包——适用于程序化地牢、模块化建筑或实例化房间(来自):
ULevelStreamingDynamic::LoadLevelInstanceLevelStreamingDynamic.hcpp
#include "Engine/LevelStreamingDynamic.h"
void AMyDungeonGenerator::SpawnRoom(FVector Location, FRotator Rotation)
{
bool bSuccess = false;
ULevelStreamingDynamic* StreamingLevel = ULevelStreamingDynamic::LoadLevelInstance(
this, // WorldContextObject
TEXT("/Game/Levels/Room_Corridor"), // LongPackageName — full path
Location,
Rotation,
bSuccess
);
if (bSuccess && StreamingLevel)
{
// Bind to delegate to know when visible
StreamingLevel->OnLevelShown.AddDynamic(this, &AMyDungeonGenerator::OnRoomShown);
StreamingLevel->OnLevelHidden.AddDynamic(this, &AMyDungeonGenerator::OnRoomHidden);
LoadedRooms.Add(StreamingLevel);
}
}
void AMyDungeonGenerator::UnloadRoom(ULevelStreamingDynamic* StreamingLevel)
{
if (StreamingLevel)
{
StreamingLevel->SetShouldBeLoaded(false);
StreamingLevel->SetShouldBeVisible(false);
StreamingLevel->SetIsRequestingUnloadAndRemoval(true);
}
}对于网络场景:使用为所有客户端和服务器指定同一个实例包名。如果不设置,每个进程会自动生成唯一名称,导致跨连接不匹配。
OptionalLevelNameOverridecpp
ULevelStreamingDynamic::FLoadLevelInstanceParams Params(
GetWorld(),
TEXT("/Game/Levels/Room_Corridor"),
FTransform(Rotation, Location)
);
Params.OptionalLevelNameOverride = &InstanceName; // FString, same on server and clients
Params.bInitiallyVisible = true;
bool bSuccess = false;
ULevelStreamingDynamic* Level = ULevelStreamingDynamic::LoadLevelInstance(Params, bSuccess);OnLevelShown / OnLevelHidden Delegates
OnLevelShown / OnLevelHidden委托
From — four delegates: , , , . Bind with :
LevelStreaming.hBlueprintAssignableOnLevelLoadedOnLevelUnloadedOnLevelShownOnLevelHiddenAddDynamiccpp
StreamingLevel->OnLevelShown.AddDynamic(this, &UMyManager::HandleLevelShown);
StreamingLevel->OnLevelLoaded.AddDynamic(this, &UMyManager::HandleLevelLoaded);来自的四个委托:、、、。使用绑定:
LevelStreaming.hBlueprintAssignableOnLevelLoadedOnLevelUnloadedOnLevelShownOnLevelHiddenAddDynamiccpp
StreamingLevel->OnLevelShown.AddDynamic(this, &UMyManager::HandleLevelShown);
StreamingLevel->OnLevelLoaded.AddDynamic(this, &UMyManager::HandleLevelLoaded);Streaming Volumes
流送体积
ALevelStreamingVolumeLevelStreamingVolume.hcpp
// EStreamingVolumeUsage — set on the volume in editor
SVB_Loading // load but do not make visible
SVB_LoadingAndVisibility // load and make visible (most common)
SVB_VisibilityBlockingOnLoad // force blocking load when entering
SVB_BlockingOnLoad // block load of associated levels
SVB_LoadingNotVisible // load, keep invisible (pre-warm)Volumes are assigned to a sub-level via its array. Disable volume-driven streaming for a level with when you want code-only control.
EditorStreamingVolumesULevelStreaming::bDisableDistanceStreaming = trueALevelStreamingVolumeLevelStreamingVolume.hcpp
// EStreamingVolumeUsage — set on the volume in editor
SVB_Loading // load but do not make visible
SVB_LoadingAndVisibility // load and make visible (most common)
SVB_VisibilityBlockingOnLoad // force blocking load when entering
SVB_BlockingOnLoad // block load of associated levels
SVB_LoadingNotVisible // load, keep invisible (pre-warm)体积通过子关卡的数组分配给该子关卡。当你希望仅通过代码控制时,可设置来禁用体积驱动的流送。
EditorStreamingVolumesULevelStreaming::bDisableDistanceStreaming = trueManual Visibility Control
手动可见性控制
cpp
// Get streaming level reference from world
const TArray<ULevelStreaming*>& Levels = GetWorld()->GetStreamingLevels();
for (ULevelStreaming* Level : Levels)
{
if (Level->GetWorldAssetPackageFName() == FName("/Game/Levels/MySubLevel"))
{
Level->SetShouldBeLoaded(true);
Level->SetShouldBeVisible(true);
break;
}
}Force flush all streaming (blocks until complete — use sparingly):
cpp
UGameplayStatics::FlushLevelStreaming(this);cpp
// Get streaming level reference from world
const TArray<ULevelStreaming*>& Levels = GetWorld()->GetStreamingLevels();
for (ULevelStreaming* Level : Levels)
{
if (Level->GetWorldAssetPackageFName() == FName("/Game/Levels/MySubLevel"))
{
Level->SetShouldBeLoaded(true);
Level->SetShouldBeVisible(true);
break;
}
}强制刷新所有流送(会阻塞直到完成——谨慎使用):
cpp
UGameplayStatics::FlushLevelStreaming(this);Level Instances
关卡实例
ALevelInstanceULevelStreamingDynamicPacked Level Actors merge instance meshes into a single static mesh for performance. Enable via right-click on Level Instance → Pack Level Actor.
Per-instance property overrides (UE5.1+): Each placed can override individual actor properties (materials, gameplay values) without modifying the source level. Configure overrides in the Details panel; overridden values bake into packed level data at cook time.
ALevelInstanceALevelInstanceULevelStreamingDynamic打包关卡Actor会将实例网格合并为单个静态网格以提升性能。通过右键点击Level Instance → Pack Level Actor启用。
每个实例的属性覆盖(UE5.1+): 每个放置的可覆盖单个Actor的属性(材质、游戏玩法数值),无需修改源关卡。在细节面板中配置覆盖;覆盖的值会在打包时烘焙到关卡数据中。
ALevelInstanceLevel Travel
关卡切换
Non-Seamless: UGameplayStatics::OpenLevel
非无缝切换:UGameplayStatics::OpenLevel
Destroys the current world and loads a new one; all clients disconnect. From :
GameplayStatics.hcpp
UGameplayStatics::OpenLevel(this, FName("/Game/Maps/MainMenu"), true);
UGameplayStatics::OpenLevel(this, FName("/Game/Maps/GameLevel"), true, TEXT("?Difficulty=Hard"));
UGameplayStatics::OpenLevelBySoftObjectPtr(this, GameLevelAsset, true); // packaging-safe销毁当前世界并加载新世界;所有客户端断开连接。来自:
GameplayStatics.hcpp
UGameplayStatics::OpenLevel(this, FName("/Game/Maps/MainMenu"), true);
UGameplayStatics::OpenLevel(this, FName("/Game/Maps/GameLevel"), true, TEXT("?Difficulty=Hard"));
UGameplayStatics::OpenLevelBySoftObjectPtr(this, GameLevelAsset, true); // packaging-safeServer Travel (Multiplayer, Non-Seamless)
Server Travel(多人游戏,非无缝)
Initiated on the server; all connected clients follow ():
World.hcpp
GetWorld()->ServerTravel(TEXT("/Game/Maps/Level02?listen"), /*bAbsolute=*/false);由服务器发起;所有连接的客户端跟随(来自):
World.hcpp
GetWorld()->ServerTravel(TEXT("/Game/Maps/Level02?listen"), /*bAbsolute=*/false);Seamless Travel
无缝切换
Seamless travel loads the destination map in the background via a transition (midpoint) map. Clients stay connected. From :
World.hcpp
void UWorld::SeamlessTravel(const FString& InURL, bool bAbsolute);
bool UWorld::IsInSeamlessTravel() const;
void UWorld::SetSeamlessTravelMidpointPause(bool bNowPaused);Setup requirements:
- Set on
bUseSeamlessTravel = true:AGameModeBase
cpp
// bUseSeamlessTravel is already declared in AGameModeBase — do NOT redeclare it.
// Just set it in the constructor:
// MyGameMode.cpp constructor
bUseSeamlessTravel = true;- Set a transition map in :
DefaultEngine.ini
ini
[/Script/Engine.GameMapsSettings]
TransitionMap=/Game/Maps/Transition- Override to control which actors persist:
GetSeamlessTravelActorList
cpp
// GameMode — called on server side during transition
void AMyGameMode::GetSeamlessTravelActorList(bool bToTransition, TArray<AActor*>& ActorList)
{
Super::GetSeamlessTravelActorList(bToTransition, ActorList);
if (!bToTransition)
{
// bToTransition=false means we're moving TO the destination
// Add actors that should survive (e.g., GameState, custom managers)
ActorList.Add(MyPersistentManager);
}
}
// GameMode — called after destination map is loaded
void AMyGameMode::PostSeamlessTravel()
{
Super::PostSeamlessTravel();
// Re-initialize any post-travel systems
}
// GameMode — handle re-possessing players after travel
void AMyGameMode::HandleSeamlessTravelPlayer(AController*& C)
{
Super::HandleSeamlessTravelPlayer(C);
// Restore player-specific state here
}- Trigger on server:
cpp
// From GameMode, server-only
GetWorld()->ServerTravel(TEXT("/Game/Maps/Level02?listen"));
// Seamless travel is automatic because bUseSeamlessTravel is trueTravel sequence: current world -> transition map -> destination world. Use to pause at midpoint for pre-loading.
SetSeamlessTravelMidpointPause(true)无缝切换通过过渡(中点)地图在后台加载目标地图。客户端保持连接。来自:
World.hcpp
void UWorld::SeamlessTravel(const FString& InURL, bool bAbsolute);
bool UWorld::IsInSeamlessTravel() const;
void UWorld::SetSeamlessTravelMidpointPause(bool bNowPaused);设置要求:
- 在上设置
AGameModeBase:bUseSeamlessTravel = true
cpp
// bUseSeamlessTravel is already declared in AGameModeBase — do NOT redeclare it.
// Just set it in the constructor:
// MyGameMode.cpp constructor
bUseSeamlessTravel = true;- 在中设置过渡地图:
DefaultEngine.ini
ini
[/Script/Engine.GameMapsSettings]
TransitionMap=/Game/Maps/Transition- 重写以控制哪些Actor可以保留:
GetSeamlessTravelActorList
cpp
// GameMode — called on server side during transition
void AMyGameMode::GetSeamlessTravelActorList(bool bToTransition, TArray<AActor*>& ActorList)
{
Super::GetSeamlessTravelActorList(bToTransition, ActorList);
if (!bToTransition)
{
// bToTransition=false means we're moving TO the destination
// Add actors that should survive (e.g., GameState, custom managers)
ActorList.Add(MyPersistentManager);
}
}
// GameMode — called after destination map is loaded
void AMyGameMode::PostSeamlessTravel()
{
Super::PostSeamlessTravel();
// Re-initialize any post-travel systems
}
// GameMode — handle re-possessing players after travel
void AMyGameMode::HandleSeamlessTravelPlayer(AController*& C)
{
Super::HandleSeamlessTravelPlayer(C);
// Restore player-specific state here
}- 在服务器上触发:
cpp
// From GameMode, server-only
GetWorld()->ServerTravel(TEXT("/Game/Maps/Level02?listen"));
// Seamless travel is automatic because bUseSeamlessTravel is true切换流程: 当前世界 -> 过渡地图 -> 目标世界。使用可在中点暂停以进行预加载。
SetSeamlessTravelMidpointPause(true)Client Travel
客户端切换
For client-initiated travel (join server, change options), call from the player controller.
APlayerController::ClientTravel(URL, ETravelType::TRAVEL_Absolute)对于客户端发起的切换(加入服务器、更改选项),从玩家控制器调用。
APlayerController::ClientTravel(URL, ETravelType::TRAVEL_Absolute)World Subsystems
世界子系统
UWorldSubsystemSubsystems/WorldSubsystem.hUWorldcpp
// MyStreamingManager.h
UCLASS()
class MYGAME_API UMyStreamingManager : public UWorldSubsystem
{
GENERATED_BODY()
public:
virtual void PostInitialize() override; // after all subsystems init
virtual void OnWorldBeginPlay(UWorld& InWorld) override; // after all BeginPlay
virtual void PreDeinitialize() override; // cleanup hook
virtual bool ShouldCreateSubsystem(UObject* Outer) const override; // filter world type
void RequestLoadZone(FName ZoneName);
void RequestUnloadZone(FName ZoneName);
private:
TMap<FName, TWeakObjectPtr<ULevelStreaming>> ActiveZones;
};Access from anywhere with a world context:
cpp
UMyStreamingManager* Manager = GetWorld()->GetSubsystem<UMyStreamingManager>();
if (Manager)
{
Manager->RequestLoadZone(FName("Zone_A"));
}UWorldSubsystemSubsystems/WorldSubsystem.hUWorldcpp
// MyStreamingManager.h
UCLASS()
class MYGAME_API UMyStreamingManager : public UWorldSubsystem
{
GENERATED_BODY()
public:
virtual void PostInitialize() override; // after all subsystems init
virtual void OnWorldBeginPlay(UWorld& InWorld) override; // after all BeginPlay
virtual void PreDeinitialize() override; // cleanup hook
virtual bool ShouldCreateSubsystem(UObject* Outer) const override; // filter world type
void RequestLoadZone(FName ZoneName);
void RequestUnloadZone(FName ZoneName);
private:
TMap<FName, TWeakObjectPtr<ULevelStreaming>> ActiveZones;
};通过世界上下文从任何位置访问:
cpp
UMyStreamingManager* Manager = GetWorld()->GetSubsystem<UMyStreamingManager>();
if (Manager)
{
Manager->RequestLoadZone(FName("Zone_A"));
}UTickableWorldSubsystem
UTickableWorldSubsystem
For per-frame updates (distance checks, zone detection). Inherit from . Must call and to enable/disable ticking. Implement returning a .
UTickableWorldSubsystemSuper::InitializeSuper::DeinitializeGetStatIdRETURN_QUICK_DECLARE_CYCLE_STAT用于每帧更新(距离检查、区域检测)。继承自。必须调用和来启用/禁用tick。实现并返回。
UTickableWorldSubsystemSuper::InitializeSuper::DeinitializeGetStatIdRETURN_QUICK_DECLARE_CYCLE_STATPersistent Data Across Level Transitions
关卡切换间的持久化数据
| Mechanism | Lifetime | Use Case |
|---|---|---|
| Entire application session | Cross-level player state, session config |
| Entire application session | Services that outlive any world |
| Seamless travel actor list | Transition only | Actors that physically cross (GameState, managers) |
| Disk-persistent | Long-term saves, progression |
| Per world | World-scoped cache; push data to |
| 机制 | 生命周期 | 使用场景 |
|---|---|---|
| 整个应用会话 | 跨关卡玩家状态、会话配置 |
| 整个应用会话 | 比任何世界生命周期都长的服务 |
| 无缝切换Actor列表 | 仅切换过程中 | 物理跨关卡的Actor(GameState、管理器) |
| 磁盘持久化 | 长期保存、进度记录 |
| 每个世界 | 世界范围的缓存;在切换清除前,在 |
GameInstance Pattern
GameInstance模式
Store cross-level data in properties (survives all level travel). Access from anywhere with a world context:
UGameInstancecpp
UMyGameInstance* GI = GetGameInstance<UMyGameInstance>();
if (GI) GI->PlayerScore += 100;将跨关卡数据存储在属性中(可在所有关卡切换中保留)。通过世界上下文从任何位置访问:
UGameInstancecpp
UMyGameInstance* GI = GetGameInstance<UMyGameInstance>();
if (GI) GI->PlayerScore += 100;Common Mistakes and Anti-Patterns
常见错误与反模式
Loading everything at once. Setting on many sub-levels causes hitches. Use async loading and the latent action pattern. Only block on load when the game is behind a loading screen.
bShouldBlockOnLoad = trueStreaming volume gaps. Overlapping volumes cause spurious unload/reload cycles. Use on the streaming level to add a cooldown and prevent flickering.
MinTimeBetweenVolumeUnloadRequestsBroken seamless travel in multiplayer. If is true but no transition map is set, seamless travel silently falls back to non-seamless. Always set in .
bUseSeamlessTravelTransitionMapDefaultEngine.iniCross-level hard references. Hard object references () between actors in different streaming levels cause the entire referenced level to stay loaded. Always use or across level boundaries.
UPROPERTY() UObject*TSoftObjectPtrTSoftClassPtrDynamic streaming level names not matching server and client. When using , each process generates a unique name. In multiplayer, supply with the same name on server and all clients.
ULevelStreamingDynamic::LoadLevelInstanceOptionalLevelNameOverrideWorld Partition on dedicated server. The server does not use rendering-driven streaming. Streaming sources must be explicitly added server-side (e.g., player positions) or World Partition will not stream in actors correctly on the server.
Modifying directly. Do not add to directly. Use , , and (from ) which handle internal bookkeeping and .
StreamingLevelsUWorld::StreamingLevelsAddStreamingLevelsAddUniqueStreamingLevelsRemoveStreamingLevelsWorld.hStreamingLevelsToConsiderForgetting to call / in . These calls enable and disable ticking respectively. Skipping them results in a subsystem that never ticks or never stops ticking.
Super::InitializeSuper::DeinitializeUTickableWorldSubsystem一次性加载所有内容。在多个子关卡上设置会导致卡顿。使用异步加载和latent action模式。仅在加载屏幕后方块加载。
bShouldBlockOnLoad = true流送体积间隙。重叠体积会导致不必要的卸载/重新加载循环。在流送关卡上使用添加冷却时间,防止闪烁。
MinTimeBetweenVolumeUnloadRequests多人游戏中无缝切换失效。如果为true但未设置过渡地图,无缝切换会自动回退到非无缝切换。务必在中设置。
bUseSeamlessTravelDefaultEngine.iniTransitionMap跨关卡硬引用。不同流送关卡中Actor之间的硬对象引用()会导致整个被引用关卡保持加载。跨关卡边界时请始终使用或。
UPROPERTY() UObject*TSoftObjectPtrTSoftClassPtr动态流送关卡名称在服务器与客户端不匹配。使用时,每个进程会生成唯一名称。在多人游戏中,请为服务器和所有客户端提供相同的。
ULevelStreamingDynamic::LoadLevelInstanceOptionalLevelNameOverride专用服务器上的World Partition。服务器不使用基于渲染的流送。必须在服务器端显式添加流送源(如玩家位置),否则World Partition无法在服务器上正确流送Actor。
直接修改。不要直接向添加内容。请使用、和(来自),它们会处理内部记录和。
StreamingLevelsUWorld::StreamingLevelsAddStreamingLevelsAddUniqueStreamingLevelsRemoveStreamingLevelsWorld.hStreamingLevelsToConsider在中忘记调用 / 。这些调用分别用于启用和禁用tick。跳过它们会导致子系统永远不会tick或永远不会停止tick。
UTickableWorldSubsystemSuper::InitializeSuper::DeinitializeQuick Reference: Key APIs
快速参考:关键API
| API | Header | Notes |
|---|---|---|
| | Async latent load of named sub-level |
| | Async latent unload |
| | Blocking flush — use behind loading screens |
| | Non-seamless level travel |
| | Runtime level instancing |
| | Query current stream state |
| | Drive load state |
| | Drive visibility |
| | Remove level from world |
| | Multiplayer level transition |
| | Background seamless transition |
| | Iterate all streaming levels |
| | World Partition data layer control (use |
| | Post-BeginPlay init hook |
| | Control actor persistence |
| API | 头文件 | 说明 |
|---|---|---|
| | 命名子关卡的异步latent加载 |
| | 异步latent卸载 |
| | 阻塞式刷新——仅在加载屏幕后使用 |
| | 非无缝关卡切换 |
| | 运行时关卡实例化 |
| | 查询当前流送状态 |
| | 驱动加载状态 |
| | 驱动可见性 |
| | 从世界中移除关卡 |
| | 多人游戏关卡切换 |
| | 后台无缝切换 |
| | 遍历所有流送关卡 |
| | World Partition数据层控制(使用 |
| | BeginPlay后的初始化钩子 |
| | 控制Actor持久化 |
Related Skills
相关技能
- — GameMode travel callbacks,
ue-gameplay-framework, actor persistence rules.PostSeamlessTravel - — async asset loading patterns that complement level streaming.
ue-data-assets-tables - — net visibility transactions, server streaming authority.
ue-networking-replication - — subsystem patterns,
ue-cpp-foundationslifetime.UGameInstance
- — GameMode切换回调、
ue-gameplay-framework、Actor持久化规则。PostSeamlessTravel - — 与关卡流送互补的异步资产加载模式。
ue-data-assets-tables - — 网络可见性事务、服务器流送权限。
ue-networking-replication - — 子系统模式、
ue-cpp-foundations生命周期。UGameInstance