关卡与 World 流转

自动关联目录:关卡与 World 流转

"切场景"在 UE 里不是一个动作,而是两类机制:换 World(Travel)与在当前 World 里加载/卸载子关卡(Level Streaming)。选错会出现"玩家数据丢了"或"卡顿"。

一句话定位:要换规则换玩家 → Travel;要无感地拼接大世界 → Streaming。而 Travel 又分"无缝"和"硬切",区别是玩家对象是否保留。

三种手段

手段用途玩家对象
OpenLevel / ServerTravel(非无缝)换关卡、回主菜单全部重建
无缝 Travel(Seamless)换关卡但保留玩家PlayerController / PlayerState 保留
Level Streaming大世界按需加载不变

非无缝 Travel

UGameplayStatics::OpenLevel(GetWorld(), FName("/Game/Maps/Forest"));     // 单机/客户端
GetWorld()->ServerTravel(TEXT("/Game/Maps/Forest?listen"));              // 服务端带所有客户端走

流程:断开 → 卸载 World → 加载新 World → 重新登录(走一遍 PreLogin / PostLogin)。

所有玩家状态都会丢,除非你自己写到 GameInstance 或存档里。

无缝 Travel

// GameMode 开启
AMyGameMode::AMyGameMode()
{
    bUseSeamlessTravel = true;
}

// 触发
GetWorld()->ServerTravel(TEXT("/Game/Maps/Forest"), /*bAbsolute=*/false, /*bShouldSkipGameNotify=*/false);
保留不保留
PlayerControllerPawn(会重新生成)
PlayerState关卡里的 AGameMode(新关卡用新的)
UGameInstance关卡 Actor
已加载的 Subsystem(GameInstance 级)UWorldSubsystem

关键回调:

// GameMode:玩家在新关卡就位
virtual void HandleSeamlessTravelPlayer(AController*& C) override;

// PlayerController:跨关卡保留的数据
virtual void GetSeamlessTravelActorList(bool bToEntry, TArray<AActor*>& ActorList) override;

默认除了 Controller 和 PlayerState,其它 Actor 都不会跟着走。想带上某个 Actor(比如玩家的背包实体),要在 GetSeamlessTravelActorList 里显式加入。

Transition Map

无缝 Travel 中间会加载一个过渡关卡(TransitionMap,默认是个空场景)来顶住加载时间。

[/Script/EngineSettings.GameMapsSettings]
TransitionMap=/Game/Maps/Transition

忘了配 TransitionMap,无缝 Travel 会退化成硬切,表现为"说好的保留玩家数据怎么没了"。

Level Streaming

// 蓝图/C++ 加载子关卡
UGameplayStatics::LoadStreamLevel(GetWorld(), FName("Forest_East"), true, false, FLatentActionInfo());
UGameplayStatics::UnloadStreamLevel(GetWorld(), FName("Forest_East"), FLatentActionInfo());
机制说明
LevelStreamingVolume玩家进入体积时自动加载
蓝图 Load Stream Level手动控制
World Partition(UE5)自动按格子流式,超大场景首选
AWorldSettings 的 Streaming 距离按距离自动

Streaming 是异步的,加载完成前不能假设关卡里的 Actor 存在——要等回调或 OnLevelLoaded。

后台加载与卡顿

手段说明
预加载提前 LoadStreamLevel 但先不显示
分帧预算s.LevelStreamingActorsUpdateTimeLimit
过渡关卡用 Travel 的 TransitionMap 顶住
Loading 界面最朴素也最有效
bShouldBlockOnLoad强制同步加载,会卡,只在必须时用
UGameplayStatics::LoadStreamLevel(World, Name, /*bMakeVisibleAfterLoad=*/true,
                                  /*bShouldBlockOnLoad=*/false, LatentInfo);

客户端-服务端一致性

事项说明
ServerTravel 由服务端发起所有连接的客户端跟着走
ClientTravel单个客户端换关卡(回主菜单、换服务器)
断线重连需要自己实现,UE 不自动保存
PIE 下的多端编辑器里每个端是独立的 World,调试时注意
// 客户端回到主菜单
PC->ClientTravel(TEXT("/Game/Maps/MainMenu"), ETravelType::TRAVEL_Absolute);

排查

现象原因
切场景后玩家数据丢了用了非无缝,或数据放在 Pawn/World
无缝 Travel 还是丢了没配 TransitionMap
切场景卡顿Streaming 同步加载 / 没预加载
子关卡里的 Actor 找不到还没加载完就用(见 资源加载)
客户端没跟着换关卡用了 OpenLevel 而非 ServerTravel

常见坑

坑说明
用 OpenLevel 期望保留玩家非无缝会全部重建
开 bUseSeamlessTravel 但没配 TransitionMap退化成硬切
跨关卡数据放 UWorldSubsystem切场景即销毁
在 ServerTravel 后立刻访问新关卡 Actor还没加载完
Streaming 关卡没等回调就用异步未就绪
把过渡关卡打成很大的包过渡反而变慢
切场景不清理 GameInstance 里的临时状态内存与逻辑逐渐腐败
在客户端调 ServerTravel无效,必须由服务端发起