M.MultiWorld|游戏中打开仓库
大约 5 分钟
导言
当你试图在游戏中切换英雄或打开背包时,你面临的第一个问题就是怎么打开这些带有独立光照、背景的场景,并且不影响原本的世界。


问题
- 场景切换问题。
- UI切换问题。
- 控制器切换问题
- 守望先锋

注
这里只是简单的演示,远没有我们想要的效果。

- 永劫无间
- 主关卡不再存东西,而是动态子关卡代替
- 子关卡则包括角色选择关卡、游戏地图关卡、背包关卡等

重识UWorld

官方定义

编辑器之所以可以切换显示图层,也是因为代码中Layers部分是只编译在编辑器中的
WITH_EDITORONLY_DATA

当然官方也用了专门的子系统维护关卡的图层。



ULevel & ULevelStreamingDynamic && ULevelStream

LoadLevelInstanceBySoftObjectPtr,通过世界上下文,拿到具体的世界。早在GamePlay开篇我们就对WorldContextObject 做了简单的介绍,它是由Engine维护的一个方法,用于获取世界指针,再深入就有点不礼貌了。
LoadLevelInstance_Internal ,则是LoadLevelInstanceBySoftObjectPtr中调用的私有工具函数,主要是用于包体PackagePath检查、实例化ULevelStreamingDynamic
最终通过World->AddStreamingLevel(StreamingLevel);
下面是老生常谈的通过上下文拿到UWorld的方法
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UWorld* UEngine::GetWorldFromContextObject(const UObject* Object, EGetWorldErrorMode ErrorMode) const
{
if (Object == nullptr)
{
switch (ErrorMode)
{
case EGetWorldErrorMode::Assert:
check(Object);
break;
case EGetWorldErrorMode::LogAndReturnNull:
FFrame::KismetExecutionMessage(TEXT("A null object was passed as a world context object to UEngine::GetWorldFromContextObject()."), ELogVerbosity::Warning);
//UE_LOG(LogEngine, Warning, TEXT("UEngine::GetWorldFromContextObject() passed a nullptr"));
break;
case EGetWorldErrorMode::ReturnNull:
break;
}
return nullptr;
}
bool bSupported = true;
UWorld* World = (ErrorMode == EGetWorldErrorMode::Assert) ? Object->GetWorldChecked(/*out*/ bSupported) : Object->GetWorld();
if (bSupported && (World == nullptr) && (ErrorMode == EGetWorldErrorMode::LogAndReturnNull))
{
FFrame::KismetExecutionMessage(*FString::Printf(TEXT("No world was found for object (%s) passed in to UEngine::GetWorldFromContextObject()."), *GetPathNameSafe(Object)), ELogVerbosity::Warning);
}
return (bSupported ? World : GWorld);
}

void UExorcistFunctionLibrary::SetHiddenFromStreamLevel(bool bHidden, ULevelStreaming* LevelStreaming)
{
if (!LevelStreaming) return;
//从LevelStreaming中拿到ULevel
ULevel* LoadedLevel = LevelStreaming->GetLoadedLevel();
if (!LoadedLevel) return;
// ULevel拿到所有的Actor
for(auto Actor : LoadedLevel->Actors)
{
Actor->SetActorHiddenInGame(bHidden);
}
}
所以要从世界拿到所有的Actor的流程是UWorld->ULevelStream->ULevel->Actor,而ULevelStreamingDynamic : public ULevelStreaming,本质还是ULevelStreaming
UI|Viewport


void UUserWidget::AddToViewport(int32 ZOrder)
{
if (UGameViewportSubsystem* Subsystem = UGameViewportSubsystem::Get(GetWorld()))
{
FGameViewportWidgetSlot ViewportSlot;
if (bIsManagedByGameViewportSubsystem)
{
ViewportSlot = Subsystem->GetWidgetSlot(this);
}
ViewportSlot.ZOrder = ZOrder;
Subsystem->AddWidget(this, ViewportSlot);
}
}
日常子系统维护,UGameViewportSubsystem : public UEngineSubsystem
