Added CommonLoadingScreen
This commit is contained in:
parent
799cfa4303
commit
6814732c4b
29
Plugins/CommonLoadingScreen/CommonLoadingScreen.uplugin
Normal file
29
Plugins/CommonLoadingScreen/CommonLoadingScreen.uplugin
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
Plugins/CommonLoadingScreen/Resources/Icon128.png
Normal file
BIN
Plugins/CommonLoadingScreen/Resources/Icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -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 ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FDefaultModuleImpl, CommonLoadingScreen)
|
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "CommonLoadingScreenSettings.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonLoadingScreenSettings)
|
||||||
|
|
||||||
|
UCommonLoadingScreenSettings::UCommonLoadingScreenSettings()
|
||||||
|
{
|
||||||
|
CategoryName = TEXT("Game");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Engine/DeveloperSettingsBackedByCVars.h"
|
||||||
|
#include "UObject/SoftObjectPath.h"
|
||||||
|
|
||||||
|
#include "CommonLoadingScreenSettings.generated.h"
|
||||||
|
|
||||||
|
class UObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, meta=(MetaClass="/Script/UMG.UserWidget"))
|
||||||
|
FSoftClassPath LoadingScreenWidget;
|
||||||
|
|
||||||
|
// The z-order of the loading screen widget in the viewport stack
|
||||||
|
UPROPERTY(config, EditAnywhere, Category=Display)
|
||||||
|
int32 LoadingScreenZOrder = 10000;
|
||||||
|
|
||||||
|
// 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 = 2.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;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,647 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "LoadingScreenManager.h"
|
||||||
|
|
||||||
|
#include "HAL/ThreadHeartBeat.h"
|
||||||
|
|
||||||
|
#include "Engine/GameInstance.h"
|
||||||
|
#include "Engine/GameViewportClient.h"
|
||||||
|
#include "Engine/Engine.h"
|
||||||
|
#include "GameFramework/GameStateBase.h"
|
||||||
|
#include "GameFramework/WorldSettings.h"
|
||||||
|
#include "Misc/CommandLine.h"
|
||||||
|
#include "Misc/ConfigCacheIni.h"
|
||||||
|
|
||||||
|
#include "LoadingProcessInterface.h"
|
||||||
|
|
||||||
|
#include "Framework/Application/IInputProcessor.h"
|
||||||
|
#include "Framework/Application/SlateApplication.h"
|
||||||
|
|
||||||
|
#include "PreLoadScreen.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<ILoadingProcessInterface>(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 = 2.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<ICursor> Cursor) override { }
|
||||||
|
|
||||||
|
virtual bool HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override { return CanEatInput(); }
|
||||||
|
virtual bool HandleKeyUpEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override { return CanEatInput(); }
|
||||||
|
virtual bool HandleAnalogInputEvent(FSlateApplication& SlateApp, const FAnalogInputEvent& InAnalogInputEvent) override { return CanEatInput(); }
|
||||||
|
virtual bool HandleMouseMoveEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
|
||||||
|
virtual bool HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
|
||||||
|
virtual bool HandleMouseButtonUpEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
|
||||||
|
virtual bool HandleMouseButtonDoubleClickEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
|
||||||
|
virtual bool HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent, const FPointerEvent* InGestureEvent) override { return CanEatInput(); }
|
||||||
|
virtual bool HandleMotionDetectedEvent(FSlateApplication& SlateApp, const FMotionEvent& MotionEvent) override { return 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 = GetGameInstance();
|
||||||
|
check(LocalGameInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::Deinitialize()
|
||||||
|
{
|
||||||
|
StopBlockingInput();
|
||||||
|
|
||||||
|
RemoveWidgetFromViewport();
|
||||||
|
|
||||||
|
FCoreUObjectDelegates::PreLoadMap.RemoveAll(this);
|
||||||
|
FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this);
|
||||||
|
|
||||||
|
// We are done, so do not attempt to tick us again
|
||||||
|
SetTickableTickType(ETickableTickType::Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ULoadingScreenManager::ShouldCreateSubsystem(UObject* Outer) const
|
||||||
|
{
|
||||||
|
// Only clients have loading screens
|
||||||
|
const UGameInstance* GameInstance = CastChecked<UGameInstance>(Outer);
|
||||||
|
const bool bIsServerWorld = GameInstance->IsDedicatedServerInstance();
|
||||||
|
return !bIsServerWorld;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
UpdateLoadingScreen();
|
||||||
|
|
||||||
|
TimeUntilNextLogHeartbeatSeconds = FMath::Max(TimeUntilNextLogHeartbeatSeconds - DeltaTime, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ETickableTickType ULoadingScreenManager::GetTickableTickType() const
|
||||||
|
{
|
||||||
|
if (IsTemplate())
|
||||||
|
{
|
||||||
|
return ETickableTickType::Never;
|
||||||
|
}
|
||||||
|
return ETickableTickType::Conditional;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ULoadingScreenManager::IsTickable() const
|
||||||
|
{
|
||||||
|
// Don't tick if we don't have a game viewport client, this catches cases that ShouldCreateSubsystem does not
|
||||||
|
UGameInstance* GameInstance = GetGameInstance();
|
||||||
|
return (GameInstance && GameInstance->GetGameViewportClient());
|
||||||
|
}
|
||||||
|
|
||||||
|
TStatId ULoadingScreenManager::GetStatId() const
|
||||||
|
{
|
||||||
|
RETURN_QUICK_DECLARE_CYCLE_STAT(ULoadingScreenManager, STATGROUP_Tickables);
|
||||||
|
}
|
||||||
|
|
||||||
|
UWorld* ULoadingScreenManager::GetTickableGameObjectWorld() const
|
||||||
|
{
|
||||||
|
return GetGameInstance()->GetWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::RegisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface)
|
||||||
|
{
|
||||||
|
ExternalLoadingProcessors.Add(Interface.GetObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::UnregisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface)
|
||||||
|
{
|
||||||
|
ExternalLoadingProcessors.Remove(Interface.GetObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::HandlePreLoadMap(const FWorldContext& WorldContext, const FString& MapName)
|
||||||
|
{
|
||||||
|
if (WorldContext.OwningGameInstance == GetGameInstance())
|
||||||
|
{
|
||||||
|
bCurrentlyInLoadMap = true;
|
||||||
|
|
||||||
|
// Update the loading screen immediately if the engine is initialized
|
||||||
|
if (GEngine->IsInitialized())
|
||||||
|
{
|
||||||
|
UpdateLoadingScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::HandlePostLoadMap(UWorld* World)
|
||||||
|
{
|
||||||
|
if ((World != nullptr) && (World->GetGameInstance() == GetGameInstance()))
|
||||||
|
{
|
||||||
|
bCurrentlyInLoadMap = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::UpdateLoadingScreen()
|
||||||
|
{
|
||||||
|
bool bLogLoadingScreenStatus = LoadingScreenCVars::LogLoadingScreenReasonEveryFrame;
|
||||||
|
|
||||||
|
if (ShouldShowLoadingScreen())
|
||||||
|
{
|
||||||
|
const UCommonLoadingScreenSettings* Settings = GetDefault<UCommonLoadingScreenSettings>();
|
||||||
|
|
||||||
|
// If we don't make it to the specified checkpoint in the given time will trigger the hang detector so we can better determine where progress stalled.
|
||||||
|
FThreadHeartBeat::Get().MonitorCheckpointStart(GetFName(), Settings->LoadingScreenHeartbeatHangDuration);
|
||||||
|
|
||||||
|
ShowLoadingScreen();
|
||||||
|
|
||||||
|
if ((Settings->LogLoadingScreenHeartbeatInterval > 0.0f) && (TimeUntilNextLogHeartbeatSeconds <= 0.0))
|
||||||
|
{
|
||||||
|
bLogLoadingScreenStatus = true;
|
||||||
|
TimeUntilNextLogHeartbeatSeconds = Settings->LogLoadingScreenHeartbeatInterval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HideLoadingScreen();
|
||||||
|
|
||||||
|
FThreadHeartBeat::Get().MonitorCheckpointEnd(GetFName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bLogLoadingScreenStatus)
|
||||||
|
{
|
||||||
|
UE_LOG(LogLoadingScreen, Log, TEXT("Loading screen showing: %d. Reason: %s"), bCurrentlyShowingLoadingScreen ? 1 : 0, *DebugReasonForShowingOrHidingLoadingScreen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ULoadingScreenManager::CheckForAnyNeedToShowLoadingScreen()
|
||||||
|
{
|
||||||
|
// Start out with 'unknown' reason in case someone forgets to put a reason when changing this in the future.
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = TEXT("Reason for Showing/Hiding LoadingScreen is unknown!");
|
||||||
|
|
||||||
|
const UGameInstance* LocalGameInstance = GetGameInstance();
|
||||||
|
|
||||||
|
if (LoadingScreenCVars::ForceLoadingScreenVisible)
|
||||||
|
{
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("CommonLoadingScreen.AlwaysShow is true"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FWorldContext* Context = LocalGameInstance->GetWorldContext();
|
||||||
|
if (Context == nullptr)
|
||||||
|
{
|
||||||
|
// We don't have a world context right now... better show a loading screen
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("The game instance has a null WorldContext"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UWorld* World = Context->World();
|
||||||
|
if (World == nullptr)
|
||||||
|
{
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We have no world (FWorldContext's World() is null)"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AGameStateBase* GameState = World->GetGameState<AGameStateBase>();
|
||||||
|
if (GameState == nullptr)
|
||||||
|
{
|
||||||
|
// The game state has not yet replicated.
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("GameState hasn't yet replicated (it's null)"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bCurrentlyInLoadMap)
|
||||||
|
{
|
||||||
|
// Show a loading screen if we are in LoadMap
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("bCurrentlyInLoadMap is true"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Context->TravelURL.IsEmpty())
|
||||||
|
{
|
||||||
|
// Show a loading screen when pending travel
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We have pending travel (the TravelURL is not empty)"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Context->PendingNetGame != nullptr)
|
||||||
|
{
|
||||||
|
// Connecting to another server
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We are connecting to another server (PendingNetGame != nullptr)"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!World->HasBegunPlay())
|
||||||
|
{
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("World hasn't begun play"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (World->IsInSeamlessTravel())
|
||||||
|
{
|
||||||
|
// Show a loading screen during seamless travel
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We are in seamless travel"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask the game state if it needs a loading screen
|
||||||
|
if (ILoadingProcessInterface::ShouldShowLoadingScreen(GameState, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask any game state components if they need a loading screen
|
||||||
|
for (UActorComponent* TestComponent : GameState->GetComponents())
|
||||||
|
{
|
||||||
|
if (ILoadingProcessInterface::ShouldShowLoadingScreen(TestComponent, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask any of the external loading processors that may have been registered. These might be actors or components
|
||||||
|
// that were registered by game code to tell us to keep the loading screen up while perhaps something finishes
|
||||||
|
// streaming in.
|
||||||
|
for (const TWeakInterfacePtr<ILoadingProcessInterface>& Processor : ExternalLoadingProcessors)
|
||||||
|
{
|
||||||
|
if (ILoadingProcessInterface::ShouldShowLoadingScreen(Processor.GetObject(), /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each local player
|
||||||
|
bool bFoundAnyLocalPC = false;
|
||||||
|
bool bMissingAnyLocalPC = false;
|
||||||
|
|
||||||
|
for (ULocalPlayer* LP : LocalGameInstance->GetLocalPlayers())
|
||||||
|
{
|
||||||
|
if (LP != nullptr)
|
||||||
|
{
|
||||||
|
if (APlayerController* PC = LP->PlayerController)
|
||||||
|
{
|
||||||
|
bFoundAnyLocalPC = true;
|
||||||
|
|
||||||
|
// Ask the PC itself if it needs a loading screen
|
||||||
|
if (ILoadingProcessInterface::ShouldShowLoadingScreen(PC, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask any PC components if they need a loading screen
|
||||||
|
for (UActorComponent* TestComponent : PC->GetComponents())
|
||||||
|
{
|
||||||
|
if (ILoadingProcessInterface::ShouldShowLoadingScreen(TestComponent, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bMissingAnyLocalPC = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient();
|
||||||
|
const bool bIsInSplitscreen = GameViewportClient->GetCurrentSplitscreenConfiguration() != ESplitScreenType::None;
|
||||||
|
|
||||||
|
// In splitscreen we need all player controllers to be present
|
||||||
|
if (bIsInSplitscreen && bMissingAnyLocalPC)
|
||||||
|
{
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("At least one missing local player controller in splitscreen"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And in non-splitscreen we need at least one player controller to be present
|
||||||
|
if (!bIsInSplitscreen && !bFoundAnyLocalPC)
|
||||||
|
{
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("Need at least one local player controller"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Victory! The loading screen can go away now
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = TEXT("(nothing wants to show it anymore)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ULoadingScreenManager::ShouldShowLoadingScreen()
|
||||||
|
{
|
||||||
|
const UCommonLoadingScreenSettings* Settings = GetDefault<UCommonLoadingScreenSettings>();
|
||||||
|
|
||||||
|
// Check debugging commands that force the state one way or another
|
||||||
|
#if !UE_BUILD_SHIPPING
|
||||||
|
static bool bCmdLineNoLoadingScreen = FParse::Param(FCommandLine::Get(), TEXT("NoLoadingScreen"));
|
||||||
|
if (bCmdLineNoLoadingScreen)
|
||||||
|
{
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("CommandLine has 'NoLoadingScreen'"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Can't show a loading screen if there's no game viewport
|
||||||
|
UGameInstance* LocalGameInstance = GetGameInstance();
|
||||||
|
if (LocalGameInstance->GetGameViewportClient() == nullptr)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a need to show the loading screen
|
||||||
|
const bool bNeedToShowLoadingScreen = CheckForAnyNeedToShowLoadingScreen();
|
||||||
|
|
||||||
|
// Keep the loading screen up a bit longer if desired
|
||||||
|
bool bWantToForceShowLoadingScreen = false;
|
||||||
|
if (bNeedToShowLoadingScreen)
|
||||||
|
{
|
||||||
|
// Still need to show it
|
||||||
|
TimeLoadingScreenLastDismissed = -1.0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Don't *need* to show the screen anymore, but might still want to for a bit
|
||||||
|
const double CurrentTime = FPlatformTime::Seconds();
|
||||||
|
const bool bCanHoldLoadingScreen = (!GIsEditor || Settings->HoldLoadingScreenAdditionalSecsEvenInEditor);
|
||||||
|
const double HoldLoadingScreenAdditionalSecs = bCanHoldLoadingScreen ? LoadingScreenCVars::HoldLoadingScreenAdditionalSecs : 0.0;
|
||||||
|
|
||||||
|
if (TimeLoadingScreenLastDismissed < 0.0)
|
||||||
|
{
|
||||||
|
TimeLoadingScreenLastDismissed = CurrentTime;
|
||||||
|
}
|
||||||
|
const double TimeSinceScreenDismissed = CurrentTime - TimeLoadingScreenLastDismissed;
|
||||||
|
|
||||||
|
// hold for an extra X seconds, to cover up streaming
|
||||||
|
if ((HoldLoadingScreenAdditionalSecs > 0.0) && (TimeSinceScreenDismissed < HoldLoadingScreenAdditionalSecs))
|
||||||
|
{
|
||||||
|
// Make sure we're rendering the world at this point, so that textures will actually stream in
|
||||||
|
//@TODO: If bNeedToShowLoadingScreen bounces back true during this window, we won't turn this off again...
|
||||||
|
UGameViewportClient* GameViewportClient = GetGameInstance()->GetGameViewportClient();
|
||||||
|
GameViewportClient->bDisableWorldRendering = false;
|
||||||
|
|
||||||
|
DebugReasonForShowingOrHidingLoadingScreen = FString::Printf(TEXT("Keeping loading screen up for an additional %.2f seconds to allow texture streaming"), HoldLoadingScreenAdditionalSecs);
|
||||||
|
bWantToForceShowLoadingScreen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bNeedToShowLoadingScreen || bWantToForceShowLoadingScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ULoadingScreenManager::IsShowingInitialLoadingScreen() const
|
||||||
|
{
|
||||||
|
FPreLoadScreenManager* PreLoadScreenManager = FPreLoadScreenManager::Get();
|
||||||
|
return (PreLoadScreenManager != nullptr) && PreLoadScreenManager->HasValidActivePreLoadScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::ShowLoadingScreen()
|
||||||
|
{
|
||||||
|
if (bCurrentlyShowingLoadingScreen)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unable to show loading screen if the engine is still loading with its loading screen.
|
||||||
|
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())
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Eat input while the loading screen is displayed
|
||||||
|
StartBlockingInput();
|
||||||
|
|
||||||
|
LoadingScreenVisibilityChanged.Broadcast(/*bIsVisible=*/ true);
|
||||||
|
|
||||||
|
// Create the loading screen 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
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::HideLoadingScreen()
|
||||||
|
{
|
||||||
|
if (!bCurrentlyShowingLoadingScreen)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::RemoveWidgetFromViewport()
|
||||||
|
{
|
||||||
|
UGameInstance* LocalGameInstance = GetGameInstance();
|
||||||
|
if (LoadingScreenWidget.IsValid())
|
||||||
|
{
|
||||||
|
if (UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient())
|
||||||
|
{
|
||||||
|
GameViewportClient->RemoveViewportWidgetContent(LoadingScreenWidget.ToSharedRef());
|
||||||
|
}
|
||||||
|
LoadingScreenWidget.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::StartBlockingInput()
|
||||||
|
{
|
||||||
|
if (!InputPreProcessor.IsValid())
|
||||||
|
{
|
||||||
|
InputPreProcessor = MakeShareable<FLoadingScreenInputPreProcessor>(new FLoadingScreenInputPreProcessor());
|
||||||
|
FSlateApplication::Get().RegisterInputPreProcessor(InputPreProcessor, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::StopBlockingInput()
|
||||||
|
{
|
||||||
|
if (InputPreProcessor.IsValid())
|
||||||
|
{
|
||||||
|
FSlateApplication::Get().UnregisterInputPreProcessor(InputPreProcessor);
|
||||||
|
InputPreProcessor.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingScreenManager::ChangePerformanceSettings(bool bEnabingLoadingScreen)
|
||||||
|
{
|
||||||
|
UGameInstance* LocalGameInstance = 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "LoadingProcessTask.h"
|
||||||
|
|
||||||
|
#include "Engine/Engine.h"
|
||||||
|
#include "Engine/GameInstance.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "LoadingScreenManager.h"
|
||||||
|
#include "UObject/ScriptInterface.h"
|
||||||
|
|
||||||
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(LoadingProcessTask)
|
||||||
|
|
||||||
|
/*static*/ ULoadingProcessTask* ULoadingProcessTask::CreateLoadingScreenProcessTask(UObject* WorldContextObject, const FString& ShowLoadingScreenReason)
|
||||||
|
{
|
||||||
|
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
||||||
|
UGameInstance* GameInstance = World ? World->GetGameInstance() : nullptr;
|
||||||
|
ULoadingScreenManager* LoadingScreenManager = GameInstance ? GameInstance->GetSubsystem<ULoadingScreenManager>() : nullptr;
|
||||||
|
|
||||||
|
if (LoadingScreenManager)
|
||||||
|
{
|
||||||
|
ULoadingProcessTask* NewLoadingTask = NewObject<ULoadingProcessTask>(LoadingScreenManager);
|
||||||
|
NewLoadingTask->SetShowLoadingScreenReason(ShowLoadingScreenReason);
|
||||||
|
|
||||||
|
LoadingScreenManager->RegisterLoadingProcessor(NewLoadingTask);
|
||||||
|
|
||||||
|
return NewLoadingTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingProcessTask::Unregister()
|
||||||
|
{
|
||||||
|
ULoadingScreenManager* LoadingScreenManager = Cast<ULoadingScreenManager>(GetOuter());
|
||||||
|
LoadingScreenManager->UnregisterLoadingProcessor(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULoadingProcessTask::SetShowLoadingScreenReason(const FString& InReason)
|
||||||
|
{
|
||||||
|
Reason = InReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ULoadingProcessTask::ShouldShowLoadingScreen(FString& OutReason) const
|
||||||
|
{
|
||||||
|
OutReason = Reason;
|
||||||
|
return true;
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LoadingProcessInterface.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
|
||||||
|
#include "LoadingProcessTask.generated.h"
|
||||||
|
|
||||||
|
struct FFrame;
|
||||||
|
|
||||||
|
UCLASS(BlueprintType)
|
||||||
|
class COMMONLOADINGSCREEN_API ULoadingProcessTask : public UObject, public ILoadingProcessInterface
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UFUNCTION(BlueprintCallable, meta=(WorldContext = "WorldContextObject"))
|
||||||
|
static ULoadingProcessTask* CreateLoadingScreenProcessTask(UObject* WorldContextObject, const FString& ShowLoadingScreenReason);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ULoadingProcessTask() { }
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
void Unregister();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
void SetShowLoadingScreenReason(const FString& InReason);
|
||||||
|
|
||||||
|
virtual bool ShouldShowLoadingScreen(FString& OutReason) const override;
|
||||||
|
|
||||||
|
FString Reason;
|
||||||
|
};
|
@ -0,0 +1,127 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Subsystems/GameInstanceSubsystem.h"
|
||||||
|
#include "Tickable.h"
|
||||||
|
#include "UObject/WeakInterfacePtr.h"
|
||||||
|
|
||||||
|
#include "LoadingScreenManager.generated.h"
|
||||||
|
|
||||||
|
template <typename InterfaceType> class TScriptInterface;
|
||||||
|
|
||||||
|
class FSubsystemCollectionBase;
|
||||||
|
class IInputProcessor;
|
||||||
|
class ILoadingProcessInterface;
|
||||||
|
class SWidget;
|
||||||
|
class UObject;
|
||||||
|
class UWorld;
|
||||||
|
struct FFrame;
|
||||||
|
struct FWorldContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles showing/hiding the loading screen
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class COMMONLOADINGSCREEN_API ULoadingScreenManager : public UGameInstanceSubsystem, public FTickableGameObject
|
||||||
|
{
|
||||||
|
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
|
||||||
|
|
||||||
|
//~FTickableObjectBase interface
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
virtual ETickableTickType GetTickableTickType() const override;
|
||||||
|
virtual bool IsTickable() const override;
|
||||||
|
virtual TStatId GetStatId() const override;
|
||||||
|
virtual UWorld* GetTickableGameObjectWorld() const override;
|
||||||
|
//~End of FTickableObjectBase interface
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category=LoadingScreen)
|
||||||
|
FString GetDebugReasonForShowingOrHidingLoadingScreen() const
|
||||||
|
{
|
||||||
|
return DebugReasonForShowingOrHidingLoadingScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns True when the loading screen is currently being shown */
|
||||||
|
bool GetLoadingScreenDisplayStatus() const
|
||||||
|
{
|
||||||
|
return bCurrentlyShowingLoadingScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when the loading screen visibility changes */
|
||||||
|
DECLARE_MULTICAST_DELEGATE_OneParam(FOnLoadingScreenVisibilityChangedDelegate, bool);
|
||||||
|
FORCEINLINE FOnLoadingScreenVisibilityChangedDelegate& OnLoadingScreenVisibilityChangedDelegate() { return LoadingScreenVisibilityChanged; }
|
||||||
|
|
||||||
|
void RegisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface);
|
||||||
|
void UnregisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void HandlePreLoadMap(const FWorldContext& WorldContext, const FString& MapName);
|
||||||
|
void HandlePostLoadMap(UWorld* World);
|
||||||
|
|
||||||
|
/** Determines if we should show or hide the loading screen. Called every frame. */
|
||||||
|
void UpdateLoadingScreen();
|
||||||
|
|
||||||
|
/** Returns true if we need to be showing the loading screen. */
|
||||||
|
bool CheckForAnyNeedToShowLoadingScreen();
|
||||||
|
|
||||||
|
/** Returns true if we want to be showing the loading screen (if we need to or are artificially forcing it on for other reasons). */
|
||||||
|
bool ShouldShowLoadingScreen();
|
||||||
|
|
||||||
|
/** Returns true if we are in the initial loading flow before this screen should be used */
|
||||||
|
bool IsShowingInitialLoadingScreen() const;
|
||||||
|
|
||||||
|
/** Shows the loading screen. Sets up the loading screen widget on the viewport */
|
||||||
|
void ShowLoadingScreen();
|
||||||
|
|
||||||
|
/** Hides the loading screen. The loading screen widget will be destroyed */
|
||||||
|
void HideLoadingScreen();
|
||||||
|
|
||||||
|
/** 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) */
|
||||||
|
TSharedPtr<SWidget> LoadingScreenWidget;
|
||||||
|
|
||||||
|
/** Input processor to eat all input while the loading screen is shown */
|
||||||
|
TSharedPtr<IInputProcessor> InputPreProcessor;
|
||||||
|
|
||||||
|
/** External loading processors, components maybe actors that delay the loading. */
|
||||||
|
TArray<TWeakInterfacePtr<ILoadingProcessInterface>> ExternalLoadingProcessors;
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
};
|
@ -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 ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "CommonPreLoadScreen.h"
|
||||||
|
|
||||||
|
#include "Misc/App.h"
|
||||||
|
#include "SCommonPreLoadingScreenWidget.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "CommonPreLoadingScreen"
|
||||||
|
|
||||||
|
void FCommonPreLoadScreen::Init()
|
||||||
|
{
|
||||||
|
if (!GIsEditor && FApp::CanEverRender())
|
||||||
|
{
|
||||||
|
EngineLoadingWidget = SNew(SCommonPreLoadingScreenWidget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "PreLoadScreenBase.h"
|
||||||
|
|
||||||
|
class SWidget;
|
||||||
|
|
||||||
|
class FCommonPreLoadScreen : public FPreLoadScreenBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/*** IPreLoadScreen Implementation ***/
|
||||||
|
virtual void Init() override;
|
||||||
|
virtual EPreLoadScreenTypes GetPreLoadScreenType() const override { return EPreLoadScreenTypes::EngineLoadingScreen; }
|
||||||
|
virtual TSharedPtr<SWidget> GetWidget() override { return EngineLoadingWidget; }
|
||||||
|
private:
|
||||||
|
|
||||||
|
TSharedPtr<SWidget> EngineLoadingWidget;
|
||||||
|
};
|
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "CommonPreLoadScreen.h"
|
||||||
|
#include "Misc/App.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
#include "PreLoadScreenManager.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<FCommonPreLoadScreen> 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<FCommonPreLoadScreen>();
|
||||||
|
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)
|
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "SCommonPreLoadingScreenWidget.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
|
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "UObject/GCObject.h"
|
||||||
|
#include "Widgets/Accessibility/SlateWidgetAccessibleTypes.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:
|
||||||
|
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user