9.3 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	title, date, excerpt, tags, rating
| title | date | excerpt | tags | rating | 
|---|---|---|---|---|
| Untitled | 2025-07-25 14:49:41 | ⭐ | 
前言
社区文章:
相关CVar参数
- CommonLoadingScreen.AlwaysShow:总是显示
 - CommonLoadingScreen.LogLoadingScreenReasonEveryFrame
 - CommonLoadingScreen.HoldLoadingScreenAdditionalSecs:载入完成后的等待时间。
 
Lyra引用逻辑
ULyraExperienceManagerComponent
PreLoadMapWithContext委托会在UEngine::LoadMap()中被调用。
相关逻辑
- CommonLoadingScreen:常规LoadingScreen。
- ULoadingScreenManager:核心逻辑。
 - UCommonLoadingScreenSettings:各种设置参数。
 - ULoadingProcessTask:继承ILoadingProcessInterface接口,只需覆盖ShouldShowLoadingScreen()。
 
 - CommonStartupLoadingScreen模块:游戏启动时的LoadingScreen。
- FCommonStartupLoadingScreenModule:StartupModule()、FPreLoadScreenManager::OnPreLoadScreenManagerCleanUp()绑定OnPreLoadScreenManagerCleanUp()。
 - SCommonPreLoadingScreenWidget:LoadingScreen Slate控件。
 
 
ULoadingScreenManager(UGameInstanceSubsystem)
- 
Initialize():绑定PreLoadMapWithContext与PostLoadMapWithWorld委托。
 - 
Deinitialize():移除BlockInput、移除Widget、移除委托、关闭Tickable。
 - 
ShouldCreateSubsystem():覆盖接口函数,对于非Server端都会加载LoadingScreen。
 - 
FTickableGameObject
- Tick():调用ShouldCreateSubsystem(),计算TimeUntilNextLogHeartbeatSeconds。
 - GetTickableTickType()
 - IsTickable():如果GameInstance有效且拥有游戏窗口就可以进行Tick。
 - GetStatId():STATGROUP_Tickables
 - GetTickableGameObjectWorld():GetGameInstance()->GetWorld();
 
 - 
RegisterLoadingProcessor():用于坐车任务,在ULoadingProcessTask::CreateLoadingScreenProcessTask()被调用。
 - 
UnregisterLoadingProcessor():用于卸载任务,在ULoadingProcessTask::Unregister()被调用。
 - 
HandlePreLoadMap():修改bCurrentlyInLoadMap为true,之后调用UpdateLoadingScreen()。
 - 
HandlePostLoadMap():修改bCurrentlyInLoadMap为false。
 - 
UpdateLoadingScreen():通过ShouldShowLoadingScreen()来判断是ShowLoadingScreen()还是HideLoadingScreen()。并且中间使用FThreadHeartBeat::Get().MonitorCheckpointStart() / MonitorCheckpointEnd()来检查线程心跳?
 - 
CheckForAnyNeedToShowLoadingScreen():
 - 
ShouldShowLoadingScreen():主要通过CheckForAnyNeedToShowLoadingScreen() 判断是否应该显示LoadingScreen。如果HoldLoadingScreenAdditionalSecs大于0,且当前经过时间依然小于HoldLoadingScreenAdditionalSecs,也会显示LoadingScreen。
 - 
IsShowingInitialLoadingScreen():判断是否正在显示初始化LoadingScreen。
 - 
RemoveWidgetFromViewport():从Viewport上移除LoadingScreenWidget。
 - 
StartBlockingInput():通过FSlateApplication::Get().RegisterInputPreProcessor(MakeShareable< FLoadingScreenInputPreProcessor >(new FLoadingScreenInputPreProcessor()))来Block所有输入。
 - 
StopBlockingInput():FSlateApplication::Get().UnregisterInputPreProcessor()接触Block。
 - 
#ChangePerformanceSettings():修改性能相关设置。
 - 
TODO
- @TODO: Why can GetLocalPlayers() have nullptr entries? Can it really?
 - @TODO: Test with PIE mode set to simulate and decide how much (if any) loading screen action should occur
 - @TODO: Allow other things implementing ILoadingProcessInterface besides GameState/PlayerController (and owned components) to register as interested parties
 - @TODO: ChangeMusicSettings (either here or using the LoadingScreenVisibilityChanged delegate)
 - @TODO: Studio analytics (FireEvent_PIEFinishedLoading / tracking PIE startup time for regressions, either here or using the LoadingScreenVisibilityChanged delegate)
 
 
ShowLoadingScreen()
void ULoadingScreenManager::ShowLoadingScreen()
{
	if (bCurrentlyShowingLoadingScreen)
	{
		return;
	}
	//在Engine载入阶段不会执行后续ShowLoadingScreen逻辑
	if (FPreLoadScreenManager::Get() && FPreLoadScreenManager::Get()->HasActivePreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen))
	{
		return;
	}
	TimeLoadingScreenShown = FPlatformTime::Seconds();
	bCurrentlyShowingLoadingScreen = true;
	CSV_EVENT(LoadingScreen, TEXT("Show"));
	const UCommonLoadingScreenSettings* Settings = GetDefault<UCommonLoadingScreenSettings>();
	if (IsShowingInitialLoadingScreen())//已经显示LoadingScreen
	{
		UE_LOG(LogLoadingScreen, Log, TEXT("Showing loading screen when 'IsShowingInitialLoadingScreen()' is true."));
		UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen);
	}
	else
	{
		UE_LOG(LogLoadingScreen, Log, TEXT("Showing loading screen when 'IsShowingInitialLoadingScreen()' is false."));
		UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen);
		UGameInstance* LocalGameInstance = GetGameInstance();
		// LoadingScreen显示时Block所有Input
		StartBlockingInput();
		LoadingScreenVisibilityChanged.Broadcast(/*bIsVisible=*/ true);
		// 创建LoadingScreen Widget
		TSubclassOf<UUserWidget> LoadingScreenWidgetClass = Settings->LoadingScreenWidget.TryLoadClass<UUserWidget>();
		if (UUserWidget* UserWidget = UUserWidget::CreateWidgetInstance(*LocalGameInstance, LoadingScreenWidgetClass, NAME_None))
		{
			LoadingScreenWidget = UserWidget->TakeWidget();
		}
		else
		{
			UE_LOG(LogLoadingScreen, Error, TEXT("Failed to load the loading screen widget %s, falling back to placeholder."), *Settings->LoadingScreenWidget.ToString());
			LoadingScreenWidget = SNew(SThrobber);
		}
		// Add to the viewport at a high ZOrder to make sure it is on top of most things
		// 将LoadingScreenWidget显示到Viewport上。
		UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient();
		GameViewportClient->AddViewportWidgetContent(LoadingScreenWidget.ToSharedRef(), Settings->LoadingScreenZOrder);
		ChangePerformanceSettings(/*bEnableLoadingScreen=*/ true);
		if (!GIsEditor || Settings->ForceTickLoadingScreenEvenInEditor)
		{
			// Tick Slate to make sure the loading screen is displayed immediately
			FSlateApplication::Get().Tick();
		}
	}
}
HideLoadingScreen()
void ULoadingScreenManager::HideLoadingScreen()
{
	if (!bCurrentlyShowingLoadingScreen)
	{
		return;
	}
	//取消Block Input
	StopBlockingInput();
	if (IsShowingInitialLoadingScreen())
	{
		UE_LOG(LogLoadingScreen, Log, TEXT("Hiding loading screen when 'IsShowingInitialLoadingScreen()' is true."));
		UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen);
	}
	else
	{
		UE_LOG(LogLoadingScreen, Log, TEXT("Hiding loading screen when 'IsShowingInitialLoadingScreen()' is false."));
		UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen);
		UE_LOG(LogLoadingScreen, Log, TEXT("Garbage Collecting before dropping load screen"));
		//更新垃圾回收之间的计时器,以便在下一次机会时运行垃圾回收。
		GEngine->ForceGarbageCollection(true);
		RemoveWidgetFromViewport();
	
		ChangePerformanceSettings(/*bEnableLoadingScreen=*/ false);
		// Let observers know that the loading screen is done
		LoadingScreenVisibilityChanged.Broadcast(/*bIsVisible=*/ false);
	}
	CSV_EVENT(LoadingScreen, TEXT("Hide"));
	const double LoadingScreenDuration = FPlatformTime::Seconds() - TimeLoadingScreenShown;
	UE_LOG(LogLoadingScreen, Log, TEXT("LoadingScreen was visible for %.2fs"), LoadingScreenDuration);
	bCurrentlyShowingLoadingScreen = false;
}
ChangePerformanceSettings()
void ULoadingScreenManager::ChangePerformanceSettings(bool bEnabingLoadingScreen)
{
	UGameInstance* LocalGameInstance = GetGameInstance();
	UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient();
	//设置Shader编译模式,默认为后台编译,显示LoadingScreen时切换成Fast模式。
	FShaderPipelineCache::SetBatchMode(bEnabingLoadingScreen ? FShaderPipelineCache::BatchMode::Fast : FShaderPipelineCache::BatchMode::Background);
	//在LoadingScreen显示阶段阶段关闭世界渲染。
	GameViewportClient->bDisableWorldRendering = bEnabingLoadingScreen;
	//如果加载界面显示,请确保优先级为流式传输。
	if (UWorld* ViewportWorld = GameViewportClient->GetWorld())
	{
		if (AWorldSettings* WorldSettings = ViewportWorld->GetWorldSettings(false, false))
		{
			WorldSettings->bHighPriorityLoadingLocal = bEnabingLoadingScreen;
		}
	}
	if (bEnabingLoadingScreen)
	{
		// 当加载屏幕可见时,设置新的挂起检测超时倍数
		double HangDurationMultiplier;
		if (!GConfig || !GConfig->GetDouble(TEXT("Core.System"), TEXT("LoadingScreenHangDurationMultiplier"), /*out*/ HangDurationMultiplier, GEngineIni))
		{
			HangDurationMultiplier = 1.0;
		}
		FThreadHeartBeat::Get().SetDurationMultiplier(HangDurationMultiplier);
		// 在加载界面显示时,请勿报告任何问题。
		FGameThreadHitchHeartBeat::Get().SuspendHeartBeat();
	}
	else
	{
		// Restore the hang detector timeout when we hide the loading screen
		FThreadHeartBeat::Get().SetDurationMultiplier(1.0);
		// 现在加载屏幕已关闭,简历报告出现故障。
		FGameThreadHitchHeartBeat::Get().ResumeHeartBeat();
	}
}