--- title: Untitled date: 2025-07-25 14:49:41 excerpt: tags: rating: ⭐ --- # 前言 社区文章: - [虚幻杂记4 PreLoadScreen与_LoadingScreen_](https://zhuanlan.zhihu.com/p/608502007) - [UE5 _Lyra_项目的解析与学习三:加载屏幕与RootLayoutUI创建流程](https://zhuanlan.zhihu.com/p/18123648655) - 其他相关 - [UE4 MoviePlayer](https://zhuanlan.zhihu.com/p/346492104) # 相关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。 - [[#ShowLoadingScreen()]] - [[#HideLoadingScreen()]] - 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() ```c++ 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(); 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 LoadingScreenWidgetClass = Settings->LoadingScreenWidget.TryLoadClass(); 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() ```c++ 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() ```c++ 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(); } } ``` ### FPreLoadScreenManager