Files
BlueRoseNote/03-UnrealEngine/Gameplay/Lyra/UE5 Lyra学习笔记(7)—CommonLoadingScreen(UE5.5).md
2025-08-02 12:09:34 +08:00

9.3 KiB
Raw Permalink Blame History

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。
    • FCommonStartupLoadingScreenModuleStartupModule()、FPreLoadScreenManager::OnPreLoadScreenManagerCleanUp()绑定OnPreLoadScreenManagerCleanUp()。
    • SCommonPreLoadingScreenWidgetLoadingScreen 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()


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();
	}
}

FPreLoadScreenManager