commit cebe626ec40c7005470054bc20ac8c5e071acebd Author: Jamie Greunbaum Date: Fri Mar 10 00:54:58 2023 -0500 A modified CommonLoadingScreen based on the plugin from the Lyra sample project. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfbcb6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Binaries/ +Intermediate/ diff --git a/CommonLoadingScreen.uplugin b/CommonLoadingScreen.uplugin new file mode 100644 index 0000000..69f4f10 --- /dev/null +++ b/CommonLoadingScreen.uplugin @@ -0,0 +1,29 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "CommonLoadingScreen", + "Description": "Loading screen manager handling creation and display of a project-specified loading screen widget", + "Category": "Gameplay", + "CreatedBy": "Epic Games, Inc.", + "CreatedByURL": "https://www.epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": false, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "CommonLoadingScreen", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "CommonStartupLoadingScreen", + "Type": "ClientOnly", + "LoadingPhase": "PreLoadingScreen" + } + ] +} \ No newline at end of file diff --git a/Resources/Icon128.png b/Resources/Icon128.png new file mode 100644 index 0000000..1231d4a Binary files /dev/null and b/Resources/Icon128.png differ diff --git a/Source/CommonLoadingScreen/CommonLoadingScreen.Build.cs b/Source/CommonLoadingScreen/CommonLoadingScreen.Build.cs new file mode 100644 index 0000000..5c9fc07 --- /dev/null +++ b/Source/CommonLoadingScreen/CommonLoadingScreen.Build.cs @@ -0,0 +1,57 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class CommonLoadingScreen : ModuleRules +{ + public CommonLoadingScreen(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "InputCore", + "PreLoadScreen", + "RenderCore", + "DeveloperSettings", + "UMG" + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Source/CommonLoadingScreen/Private/CommonLoadingScreenModule.cpp b/Source/CommonLoadingScreen/Private/CommonLoadingScreenModule.cpp new file mode 100644 index 0000000..7a55970 --- /dev/null +++ b/Source/CommonLoadingScreen/Private/CommonLoadingScreenModule.cpp @@ -0,0 +1,5 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Modules/ModuleManager.h" + +IMPLEMENT_MODULE(FDefaultModuleImpl, CommonLoadingScreen) \ No newline at end of file diff --git a/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.cpp b/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.cpp new file mode 100644 index 0000000..6638810 --- /dev/null +++ b/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.cpp @@ -0,0 +1,13 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "CommonLoadingScreenSettings.h" + +#include "UObject/NameTypes.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonLoadingScreenSettings) + +UCommonLoadingScreenSettings::UCommonLoadingScreenSettings() +{ + CategoryName = TEXT("Game"); +} + diff --git a/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.h b/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.h new file mode 100644 index 0000000..cb6217c --- /dev/null +++ b/Source/CommonLoadingScreen/Private/CommonLoadingScreenSettings.h @@ -0,0 +1,68 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Engine/DeveloperSettingsBackedByCVars.h" +#include "HAL/Platform.h" +#include "UObject/SoftObjectPath.h" +#include "UObject/UObjectGlobals.h" + +#include "CommonLoadingScreenSettings.generated.h" + + +/** + * Settings for a loading screen system. + */ +UCLASS(config=Game, defaultconfig, meta=(DisplayName="Common Loading Screen")) +class UCommonLoadingScreenSettings : public UDeveloperSettingsBackedByCVars +{ + GENERATED_BODY() + +public: + UCommonLoadingScreenSettings(); + +public: + + // The widget to load for the loading screen. + UPROPERTY(config, EditAnywhere, Category=Display) + TSoftClassPtr LoadingScreenWidget; + + // The z-order of the loading screen widget in the viewport stack + UPROPERTY(config, EditAnywhere, Category=Display) + int32 LoadingScreenZOrder = 100000; + + // How long to hold the loading screen up after other loading finishes (in seconds) to + // try to give texture streaming a chance to avoid blurriness + // + // Note: This is not normally applied in the editor for iteration time, but can be + // enabled via HoldLoadingScreenAdditionalSecsEvenInEditor + UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s, ConsoleVariable="CommonLoadingScreen.HoldLoadingScreenAdditionalSecs")) + float HoldLoadingScreenAdditionalSecs = 0.0f; + + // The interval in seconds beyond which the loading screen is considered permanently hung (if non-zero). + UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s)) + float LoadingScreenHeartbeatHangDuration = 0.0f; + + // The interval in seconds between each log of what is keeping a loading screen up (if non-zero). + UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s)) + float LogLoadingScreenHeartbeatInterval = 5.0f; + + // When true, the reason the loading screen is shown or hidden will be printed to the log every frame. + UPROPERTY(Transient, EditAnywhere, Category=Debugging, meta=(ConsoleVariable="CommonLoadingScreen.LogLoadingScreenReasonEveryFrame")) + bool LogLoadingScreenReasonEveryFrame = 0; + + // Force the loading screen to be displayed (useful for debugging) + UPROPERTY(Transient, EditAnywhere, Category=Debugging, meta=(ConsoleVariable="CommonLoadingScreen.AlwaysShow")) + bool ForceLoadingScreenVisible = false; + + // Should we apply the additional HoldLoadingScreenAdditionalSecs delay even in the editor + // (useful when iterating on loading screens) + UPROPERTY(Transient, EditAnywhere, Category=Debugging) + bool HoldLoadingScreenAdditionalSecsEvenInEditor = false; + + // Should we apply the additional HoldLoadingScreenAdditionalSecs delay even in the editor + // (useful when iterating on loading screens) + UPROPERTY(config, EditAnywhere, Category=Configuration) + bool ForceTickLoadingScreenEvenInEditor = true; +}; + diff --git a/Source/CommonLoadingScreen/Private/LoadingScreenManager.cpp b/Source/CommonLoadingScreen/Private/LoadingScreenManager.cpp new file mode 100644 index 0000000..1f42fb7 --- /dev/null +++ b/Source/CommonLoadingScreen/Private/LoadingScreenManager.cpp @@ -0,0 +1,312 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "LoadingScreenManager.h" + +#include "HAL/ThreadHeartBeat.h" +#include "ProfilingDebugging/CsvProfiler.h" + +#include "LoadingScreenWidget.h" +#include "Engine/GameInstance.h" +#include "Engine/GameViewportClient.h" +#include "Engine/World.h" +#include "Engine/Engine.h" +#include "Engine/LocalPlayer.h" +#include "GameFramework/GameStateBase.h" +#include "GameFramework/WorldSettings.h" +#include "Misc/ConfigCacheIni.h" + +#include "Framework/Application/IInputProcessor.h" +#include "Framework/Application/SlateApplication.h" + +#include "PreLoadScreenManager.h" + +#include "ShaderPipelineCache.h" +#include "CommonLoadingScreenSettings.h" + +//@TODO: Used as the placeholder widget in error cases, should probably create a wrapper that at least centers it/etc... +#include "Widgets/Images/SThrobber.h" +#include "Blueprint/UserWidget.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(LoadingScreenManager) + +DECLARE_LOG_CATEGORY_EXTERN(LogLoadingScreen, Log, All); +DEFINE_LOG_CATEGORY(LogLoadingScreen); + +//@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) + +// Profiling category for loading screens +CSV_DEFINE_CATEGORY(LoadingScreen, true); + +////////////////////////////////////////////////////////////////////// + +bool ILoadingProcessInterface::ShouldShowLoadingScreen(UObject* TestObject, FString& OutReason) +{ + if (TestObject != nullptr) + { + if (ILoadingProcessInterface* LoadObserver = Cast(TestObject)) + { + FString ObserverReason; + if (LoadObserver->ShouldShowLoadingScreen(/*out*/ ObserverReason)) + { + if (ensureMsgf(!ObserverReason.IsEmpty(), TEXT("%s failed to set a reason why it wants to show the loading screen"), *GetPathNameSafe(TestObject))) + { + OutReason = ObserverReason; + } + return true; + } + } + } + + return false; +} + +////////////////////////////////////////////////////////////////////// + +namespace LoadingScreenCVars +{ + // CVars + static float HoldLoadingScreenAdditionalSecs = 0.0f; + static FAutoConsoleVariableRef CVarHoldLoadingScreenUpAtLeastThisLongInSecs( + TEXT("CommonLoadingScreen.HoldLoadingScreenAdditionalSecs"), + HoldLoadingScreenAdditionalSecs, + TEXT("How long to hold the loading screen up after other loading finishes (in seconds) to try to give texture streaming a chance to avoid blurriness"), + ECVF_Default | ECVF_Preview); + + static bool LogLoadingScreenReasonEveryFrame = false; + static FAutoConsoleVariableRef CVarLogLoadingScreenReasonEveryFrame( + TEXT("CommonLoadingScreen.LogLoadingScreenReasonEveryFrame"), + LogLoadingScreenReasonEveryFrame, + TEXT("When true, the reason the loading screen is shown or hidden will be printed to the log every frame."), + ECVF_Default); + + static bool ForceLoadingScreenVisible = false; + static FAutoConsoleVariableRef CVarForceLoadingScreenVisible( + TEXT("CommonLoadingScreen.AlwaysShow"), + ForceLoadingScreenVisible, + TEXT("Force the loading screen to show."), + ECVF_Default); +} + +////////////////////////////////////////////////////////////////////// +// FLoadingScreenInputPreProcessor + +// Input processor to throw in when loading screen is shown +// This will capture any inputs, so active menus under the loading screen will not interact +class FLoadingScreenInputPreProcessor : public IInputProcessor +{ +public: + FLoadingScreenInputPreProcessor() { } + virtual ~FLoadingScreenInputPreProcessor() { } + + bool CanEatInput() const + { + return !GIsEditor; + } + + //~IInputProcess interface + virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef Cursor) override { } + + virtual bool HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override { return this->CanEatInput(); } + virtual bool HandleKeyUpEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override { return this->CanEatInput(); } + virtual bool HandleAnalogInputEvent(FSlateApplication& SlateApp, const FAnalogInputEvent& InAnalogInputEvent) override { return this->CanEatInput(); } + virtual bool HandleMouseMoveEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return this->CanEatInput(); } + virtual bool HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return this->CanEatInput(); } + virtual bool HandleMouseButtonUpEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return this->CanEatInput(); } + virtual bool HandleMouseButtonDoubleClickEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return this->CanEatInput(); } + virtual bool HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent, const FPointerEvent* InGestureEvent) override { return this->CanEatInput(); } + virtual bool HandleMotionDetectedEvent(FSlateApplication& SlateApp, const FMotionEvent& MotionEvent) override { return this->CanEatInput(); } + //~End of IInputProcess interface +}; + +////////////////////////////////////////////////////////////////////// +// ULoadingScreenManager + + +void ULoadingScreenManager::Initialize(FSubsystemCollectionBase &Collection) +{ + //FCoreUObjectDelegates::PreLoadMapWithContext.AddUObject(this, &ThisClass::HandlePreLoadMap); + //FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &ThisClass::HandlePostLoadMap); + + const UGameInstance *LocalGameInstance = this->GetGameInstance(); + check(LocalGameInstance); +} + +void ULoadingScreenManager::Deinitialize() +{ + this->StopBlockingInput(); + this->RemoveWidgetFromViewport(); + + //FCoreUObjectDelegates::PreLoadMap.RemoveAll(this); + //FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this); +} + +bool ULoadingScreenManager::ShouldCreateSubsystem(UObject *Outer) const +{ + // Only clients have loading screens + const UGameInstance *GameInstance = CastChecked(Outer); + const bool bIsServerWorld = GameInstance->IsDedicatedServerInstance(); + return !bIsServerWorld; +} + +class ULoadingScreenWidget *ULoadingScreenManager::ShowLoadingScreen() +{ + if (this->bCurrentlyShowingLoadingScreen) + { + return nullptr; + } + + // Unable to show loading screen if the engine is still loading with its loading screen. + if (FPreLoadScreenManager::Get() && FPreLoadScreenManager::Get()->HasActivePreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen)) + { + return nullptr; + } + + this->TimeLoadingScreenShown = FPlatformTime::Seconds(); + + this->bCurrentlyShowingLoadingScreen = true; + + CSV_EVENT(LoadingScreen, TEXT("Show")); + + const UCommonLoadingScreenSettings *Settings = GetDefault(); + UGameInstance *LocalGameInstance = this->GetGameInstance(); + + // Eat input while the loading screen is displayed + this->StartBlockingInput(); + + this->LoadingScreenVisibilityChanged.Broadcast(/*bIsVisible=*/ true); + + // Create the loading screen widget + TSubclassOf LoadingScreenWidgetClass = Settings->LoadingScreenWidget.LoadSynchronous(); + if (this->LoadingScreenUMGWidget = CreateWidget(LocalGameInstance, LoadingScreenWidgetClass, NAME_None)) + { + this->LoadingScreenWidget = this->LoadingScreenUMGWidget->TakeWidget(); + this->LoadingScreenUMGWidget->OnLoadScreenClosed.BindUObject(this, &ULoadingScreenManager::RemoveLoadingScreen); + } + else + { + UE_LOG(LogLoadingScreen, Error, TEXT("Failed to load the loading screen widget %s, falling back to placeholder."), *Settings->LoadingScreenWidget.ToString()); + this->LoadingScreenWidget = SNew(SThrobber); + } + + // Add to the viewport at a high ZOrder to make sure it is on top of most things + UGameViewportClient *GameViewportClient = LocalGameInstance->GetGameViewportClient(); + GameViewportClient->AddViewportWidgetContent(this->LoadingScreenWidget.ToSharedRef(), Settings->LoadingScreenZOrder); + + this->ChangePerformanceSettings(/*bEnableLoadingScreen=*/ true); + + if (!GIsEditor || Settings->ForceTickLoadingScreenEvenInEditor) + { + // Tick Slate to make sure the loading screen is displayed immediately + FSlateApplication::Get().Tick(); + } + + return this->LoadingScreenUMGWidget; +} + +void ULoadingScreenManager::HideLoadingScreen() +{ + this->LoadingScreenUMGWidget->BeginFadeOut(); +} + +void ULoadingScreenManager::RemoveLoadingScreen() +{ + if (!this->bCurrentlyShowingLoadingScreen) + { + return; + } + + this->StopBlockingInput(); + this->RemoveWidgetFromViewport(); + this->ChangePerformanceSettings(/*bEnableLoadingScreen=*/ false); + + this->LoadingScreenUMGWidget->OnLoadScreenOpened.Unbind(); + this->LoadingScreenUMGWidget->OnLoadScreenClosed.Unbind(); + + // Let observers know that the loading screen is done + this->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); + + this->bCurrentlyShowingLoadingScreen = false; +} + +void ULoadingScreenManager::RemoveWidgetFromViewport() +{ + UGameInstance *LocalGameInstance = GetGameInstance(); + if (this->LoadingScreenWidget.IsValid()) + { + if (UGameViewportClient *GameViewportClient = LocalGameInstance->GetGameViewportClient()) + { + GameViewportClient->RemoveViewportWidgetContent(this->LoadingScreenWidget.ToSharedRef()); + } + this->LoadingScreenWidget.Reset(); + } +} + +void ULoadingScreenManager::StartBlockingInput() +{ + if (!this->InputPreProcessor.IsValid()) + { + this->InputPreProcessor = MakeShareable(new FLoadingScreenInputPreProcessor()); + FSlateApplication::Get().RegisterInputPreProcessor(this->InputPreProcessor, 0); + } +} + +void ULoadingScreenManager::StopBlockingInput() +{ + if (this->InputPreProcessor.IsValid()) + { + FSlateApplication::Get().UnregisterInputPreProcessor(this->InputPreProcessor); + this->InputPreProcessor.Reset(); + } +} + +void ULoadingScreenManager::ChangePerformanceSettings(bool bEnabingLoadingScreen) +{ + UGameInstance *LocalGameInstance = this->GetGameInstance(); + UGameViewportClient *GameViewportClient = LocalGameInstance->GetGameViewportClient(); + + FShaderPipelineCache::SetBatchMode(bEnabingLoadingScreen ? FShaderPipelineCache::BatchMode::Fast : FShaderPipelineCache::BatchMode::Background); + + // Don't bother drawing the 3D world while we're loading + GameViewportClient->bDisableWorldRendering = bEnabingLoadingScreen; + + // Make sure to prioritize streaming in levels if the loading screen is up + if (UWorld* ViewportWorld = GameViewportClient->GetWorld()) + { + if (AWorldSettings *WorldSettings = ViewportWorld->GetWorldSettings(false, false)) + { + WorldSettings->bHighPriorityLoadingLocal = bEnabingLoadingScreen; + } + } + + if (bEnabingLoadingScreen) + { + // Set a new hang detector timeout multiplier when the loading screen is visible. + double HangDurationMultiplier; + if (!GConfig || !GConfig->GetDouble(TEXT("Core.System"), TEXT("LoadingScreenHangDurationMultiplier"), /*out*/ HangDurationMultiplier, GEngineIni)) + { + HangDurationMultiplier = 1.0; + } + FThreadHeartBeat::Get().SetDurationMultiplier(HangDurationMultiplier); + + // Do not report hitches while the loading screen is up + FGameThreadHitchHeartBeat::Get().SuspendHeartBeat(); + } + else + { + // Restore the hang detector timeout when we hide the loading screen + FThreadHeartBeat::Get().SetDurationMultiplier(1.0); + + // Resume reporting hitches now that the loading screen is down + FGameThreadHitchHeartBeat::Get().ResumeHeartBeat(); + } +} + diff --git a/Source/CommonLoadingScreen/Private/LoadingScreenWidget.cpp b/Source/CommonLoadingScreen/Private/LoadingScreenWidget.cpp new file mode 100644 index 0000000..7d4125d --- /dev/null +++ b/Source/CommonLoadingScreen/Private/LoadingScreenWidget.cpp @@ -0,0 +1,18 @@ +// ©2022 Batty Bovine Productions, LLC. All Rights Reserved. + +#include "LoadingScreenWidget.h" + + +void ULoadingScreenWidget::LoadScreenOpened() +{ +#if !WITH_EDITOR + this->OnLoadScreenOpened.Execute(); +#endif +} + +void ULoadingScreenWidget::LoadScreenClosed() +{ +#if !WITH_EDITOR + this->OnLoadScreenClosed.Execute(); +#endif +} diff --git a/Source/CommonLoadingScreen/Public/LoadingProcessInterface.h b/Source/CommonLoadingScreen/Public/LoadingProcessInterface.h new file mode 100644 index 0000000..ed6cc4e --- /dev/null +++ b/Source/CommonLoadingScreen/Public/LoadingProcessInterface.h @@ -0,0 +1,30 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "LoadingProcessInterface.generated.h" + +/** Interface for things that might cause loading to happen which requires a loading screen to be displayed */ +UINTERFACE(BlueprintType) +class COMMONLOADINGSCREEN_API ULoadingProcessInterface : public UInterface +{ + GENERATED_BODY() +}; + +class COMMONLOADINGSCREEN_API ILoadingProcessInterface +{ + GENERATED_BODY() + +public: + // Checks to see if this object implements the interface, and if so asks whether or not we should + // be currently showing a loading screen + static bool ShouldShowLoadingScreen(UObject* TestObject, FString& OutReason); + + virtual bool ShouldShowLoadingScreen(FString& OutReason) const + { + return false; + } +}; diff --git a/Source/CommonLoadingScreen/Public/LoadingScreenManager.h b/Source/CommonLoadingScreen/Public/LoadingScreenManager.h new file mode 100644 index 0000000..0b8f38e --- /dev/null +++ b/Source/CommonLoadingScreen/Public/LoadingScreenManager.h @@ -0,0 +1,93 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "UObject/WeakInterfacePtr.h" +#include "Subsystems/GameInstanceSubsystem.h" + +#include "LoadingScreenManager.generated.h" + + +/** + * Handles showing/hiding the loading screen + */ +UCLASS() +class COMMONLOADINGSCREEN_API ULoadingScreenManager : public UGameInstanceSubsystem +{ + GENERATED_BODY() + +public: + //~USubsystem interface + virtual void Initialize(FSubsystemCollectionBase &Collection) override; + virtual void Deinitialize() override; + virtual bool ShouldCreateSubsystem(UObject *Outer) const override; + //~End of USubsystem interface + + /** Shows the loading screen. Sets up the loading screen widget on the viewport. */ + class ULoadingScreenWidget *ShowLoadingScreen(); + + /** Hides the loading screen. The loading screen widget will begin to fade out. */ + void HideLoadingScreen(); + + UFUNCTION(BlueprintCallable, Category=LoadingScreen) + FString GetDebugReasonForShowingOrHidingLoadingScreen() const + { + return this->DebugReasonForShowingOrHidingLoadingScreen; + } + + /** Returns True when the loading screen is currently being shown */ + bool GetLoadingScreenDisplayStatus() const + { + return this->bCurrentlyShowingLoadingScreen; + } + + /** Called when the loading screen visibility changes */ + DECLARE_MULTICAST_DELEGATE_OneParam(FOnLoadingScreenVisibilityChangedDelegate, bool); + FORCEINLINE FOnLoadingScreenVisibilityChangedDelegate &OnLoadingScreenVisibilityChangedDelegate() { return LoadingScreenVisibilityChanged; } + +private: + /** Removes the loading screen. The loading screen widget will be destroyed. */ + void RemoveLoadingScreen(); + + /** Removes the widget from the viewport */ + void RemoveWidgetFromViewport(); + + /** Prevents input from being used in-game while the loading screen is visible */ + void StartBlockingInput(); + + /** Resumes in-game input, if blocking */ + void StopBlockingInput(); + + void ChangePerformanceSettings(bool bEnabingLoadingScreen); + +private: + /** Delegate broadcast when the loading screen visibility changes */ + FOnLoadingScreenVisibilityChangedDelegate LoadingScreenVisibilityChanged; + + /** A reference to the loading screen widget we are displaying (if any) */ + TObjectPtr LoadingScreenUMGWidget; + + /** A reference to the loading screen widget we are displaying (if any) */ + TSharedPtr LoadingScreenWidget; + + /** Input processor to eat all input while the loading screen is shown */ + TSharedPtr InputPreProcessor; + + /** The reason why the loading screen is up (or not) */ + FString DebugReasonForShowingOrHidingLoadingScreen; + + /** The time when we started showing the loading screen */ + double TimeLoadingScreenShown = 0.0; + + /** The time the loading screen most recently wanted to be dismissed (might still be up due to a min display duration requirement) **/ + double TimeLoadingScreenLastDismissed = -1.0; + + /** The time until the next log for why the loading screen is still up */ + double TimeUntilNextLogHeartbeatSeconds = 0.0; + + /** True when we are between PreLoadMap and PostLoadMap */ + bool bCurrentlyInLoadMap = false; + + /** True when the loading screen is currently being shown */ + bool bCurrentlyShowingLoadingScreen = false; +}; diff --git a/Source/CommonLoadingScreen/Public/LoadingScreenWidget.h b/Source/CommonLoadingScreen/Public/LoadingScreenWidget.h new file mode 100644 index 0000000..5eedef6 --- /dev/null +++ b/Source/CommonLoadingScreen/Public/LoadingScreenWidget.h @@ -0,0 +1,32 @@ +// ©2022 Batty Bovine Productions, LLC. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" + +#include "LoadingScreenWidget.generated.h" + + +/** + * Loading screen widget base. Uses delegates to indicate when the load screen + * is fully obscuring the game screen, and when it has finished fading back out. + */ +UCLASS() +class COMMONLOADINGSCREEN_API ULoadingScreenWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable) + void LoadScreenOpened(); + UFUNCTION(BlueprintCallable) + void LoadScreenClosed(); + + UFUNCTION(BlueprintImplementableEvent) + void BeginFadeOut(); + + DECLARE_DELEGATE(FOnLoadScreenEvent) + FOnLoadScreenEvent OnLoadScreenOpened; + FOnLoadScreenEvent OnLoadScreenClosed; +}; diff --git a/Source/CommonStartupLoadingScreen/CommonStartupLoadingScreen.Build.cs b/Source/CommonStartupLoadingScreen/CommonStartupLoadingScreen.Build.cs new file mode 100644 index 0000000..72eb553 --- /dev/null +++ b/Source/CommonStartupLoadingScreen/CommonStartupLoadingScreen.Build.cs @@ -0,0 +1,55 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class CommonStartupLoadingScreen : ModuleRules +{ + public CommonStartupLoadingScreen(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "MoviePlayer", + "PreLoadScreen", + "DeveloperSettings" + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.cpp b/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.cpp new file mode 100644 index 0000000..a271d02 --- /dev/null +++ b/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.cpp @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "CommonPreLoadScreen.h" + +#include "CoreGlobals.h" +#include "Misc/App.h" +#include "SCommonPreLoadingScreenWidget.h" +#include "Widgets/DeclarativeSyntaxSupport.h" + +#define LOCTEXT_NAMESPACE "CommonPreLoadingScreen" + +void FCommonPreLoadScreen::Init() +{ + if (!GIsEditor && FApp::CanEverRender()) + { + EngineLoadingWidget = SNew(SCommonPreLoadingScreenWidget); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.h b/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.h new file mode 100644 index 0000000..c5a6340 --- /dev/null +++ b/Source/CommonStartupLoadingScreen/Private/CommonPreLoadScreen.h @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "PreLoadScreen.h" +#include "PreLoadScreenBase.h" +#include "Templates/SharedPointer.h" + +class SWidget; + +class FCommonPreLoadScreen : public FPreLoadScreenBase +{ +public: + + /*** IPreLoadScreen Implementation ***/ + virtual void Init() override; + virtual EPreLoadScreenTypes GetPreLoadScreenType() const override { return EPreLoadScreenTypes::EngineLoadingScreen; } + virtual TSharedPtr GetWidget() override { return EngineLoadingWidget; } +private: + + TSharedPtr EngineLoadingWidget; +}; \ No newline at end of file diff --git a/Source/CommonStartupLoadingScreen/Private/CommonStartupLoadingScreen.cpp b/Source/CommonStartupLoadingScreen/Private/CommonStartupLoadingScreen.cpp new file mode 100644 index 0000000..2f94d8b --- /dev/null +++ b/Source/CommonStartupLoadingScreen/Private/CommonStartupLoadingScreen.cpp @@ -0,0 +1,67 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "CommonPreLoadScreen.h" +#include "CoreGlobals.h" +#include "Delegates/Delegate.h" +#include "Misc/App.h" +#include "Misc/CoreMisc.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" +#include "PreLoadScreenManager.h" +#include "Templates/SharedPointer.h" + +#define LOCTEXT_NAMESPACE "FCommonLoadingScreenModule" + +class FCommonStartupLoadingScreenModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + bool IsGameModule() const override; + +private: + void OnPreLoadScreenManagerCleanUp(); + + TSharedPtr PreLoadingScreen; +}; + + +void FCommonStartupLoadingScreenModule::StartupModule() +{ + // No need to load these assets on dedicated servers. + // Still want to load them in commandlets so cook catches them + if (!IsRunningDedicatedServer()) + { + PreLoadingScreen = MakeShared(); + PreLoadingScreen->Init(); + + if (!GIsEditor && FApp::CanEverRender() && FPreLoadScreenManager::Get()) + { + FPreLoadScreenManager::Get()->RegisterPreLoadScreen(PreLoadingScreen); + FPreLoadScreenManager::Get()->OnPreLoadScreenManagerCleanUp.AddRaw(this, &FCommonStartupLoadingScreenModule::OnPreLoadScreenManagerCleanUp); + } + } +} + +void FCommonStartupLoadingScreenModule::OnPreLoadScreenManagerCleanUp() +{ + //Once the PreLoadScreenManager is cleaning up, we can get rid of all our resources too + PreLoadingScreen.Reset(); + ShutdownModule(); +} + +void FCommonStartupLoadingScreenModule::ShutdownModule() +{ + +} + +bool FCommonStartupLoadingScreenModule::IsGameModule() const +{ + return true; +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FCommonStartupLoadingScreenModule, CommonStartupLoadingScreen) \ No newline at end of file diff --git a/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.cpp b/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.cpp new file mode 100644 index 0000000..7afa44b --- /dev/null +++ b/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.cpp @@ -0,0 +1,40 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "SCommonPreLoadingScreenWidget.h" + +#include "HAL/Platform.h" +#include "Layout/Children.h" +#include "Layout/Margin.h" +#include "Math/Color.h" +#include "Misc/Attribute.h" +#include "Styling/CoreStyle.h" +#include "Styling/ISlateStyle.h" +#include "Styling/SlateColor.h" +#include "Widgets/Layout/SBorder.h" + +class FReferenceCollector; + +#define LOCTEXT_NAMESPACE "SCommonPreLoadingScreenWidget" + +void SCommonPreLoadingScreenWidget::Construct(const FArguments& InArgs) +{ + ChildSlot + [ + SNew(SBorder) + .BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush")) + .BorderBackgroundColor(FLinearColor::Black) + .Padding(0) + ]; +} + +void SCommonPreLoadingScreenWidget::AddReferencedObjects(FReferenceCollector& Collector) +{ + //WidgetAssets.AddReferencedObjects(Collector); +} + +FString SCommonPreLoadingScreenWidget::GetReferencerName() const +{ + return TEXT("SCommonPreLoadingScreenWidget"); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.h b/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.h new file mode 100644 index 0000000..ebfea47 --- /dev/null +++ b/Source/CommonStartupLoadingScreen/Private/SCommonPreLoadingScreenWidget.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Containers/UnrealString.h" +#include "UObject/GCObject.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +class FReferenceCollector; + +class SCommonPreLoadingScreenWidget : public SCompoundWidget, public FGCObject +{ +public: + SLATE_BEGIN_ARGS(SCommonPreLoadingScreenWidget) {} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs); + + //~ Begin FGCObject interface + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; + //~ End FGCObject interface + +private: + +}; \ No newline at end of file