222 lines
9.3 KiB
Markdown
222 lines
9.3 KiB
Markdown
|
---
|
|||
|
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<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()
|
|||
|
```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
|