From c04bc2149bf55c66382db92c1dc82ff7e628aede Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Tue, 14 Apr 2026 14:26:43 +0200 Subject: [PATCH] Loading screen overlay + camera fade on sublevel load - Add loading overlay (black background + "Loading..." text) behind UI panels - Overlay starts visible by default, hidden in NativeConstruct if no sublevel - CameraFade to black before sublevel load, with deferred fallback for BeginPlay - 2s delay after OnLevelShown before starting 2s fade-in from black - ShowLoadingScreen helper on PlayerController via HUD->MainWidget Co-Authored-By: Claude Opus 4.6 (1M context) --- .../GameMode/PS_Editor_PlayerController.cpp | 50 +++++++++++++++++++ .../GameMode/PS_Editor_PlayerController.h | 9 ++++ .../UI/Widgets/PS_Editor_MainWidget.cpp | 42 ++++++++++++++++ .../UI/Widgets/PS_Editor_MainWidget.h | 7 +++ 4 files changed, 108 insertions(+) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp index e94ef3a..8d3e009 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp @@ -11,6 +11,9 @@ #include "Engine/LevelStreamingDynamic.h" #include "Engine/PostProcessVolume.h" #include "PS_Editor_CameraStart.h" +#include "PS_Editor_HUD.h" +#include "PS_Editor_MainWidget.h" +#include "Camera/PlayerCameraManager.h" #include "PS_Editor_GameInstanceSubsystem.h" #include "GameFramework/GameModeBase.h" #include "GameFramework/GameStateBase.h" @@ -136,6 +139,14 @@ void APS_Editor_PlayerController::Tick(float DeltaTime) { Super::Tick(DeltaTime); + // Deferred fade-to-black: CameraManager wasn't ready at BeginPlay + if (bPendingFadeToBlack && PlayerCameraManager) + { + PlayerCameraManager->StartCameraFade(0.0f, 1.0f, 0.0f, FLinearColor::Black, true, true); + bPendingFadeToBlack = false; + ShowLoadingScreen(true); + } + if (SpawnManager) { SpawnManager->TickEditorMode(DeltaTime); @@ -253,6 +264,17 @@ void APS_Editor_PlayerController::CancelPointPlacement() FinishPointPlacement(); } +void APS_Editor_PlayerController::ShowLoadingScreen(bool bShow) +{ + if (APS_Editor_HUD* EditorHUD = Cast(GetHUD())) + { + if (UPS_Editor_MainWidget* Widget = EditorHUD->GetMainWidget()) + { + Widget->ShowLoadingScreen(bShow); + } + } +} + void APS_Editor_PlayerController::LoadBaseLevelAsSublevel(const FString& LevelName, const FString& MapPath) { // Unload previous sublevel first @@ -294,6 +316,19 @@ void APS_Editor_PlayerController::LoadBaseLevelAsSublevel(const FString& LevelNa return; } + // Fade to black + show loading overlay + bWaitingForSublevelFadeIn = true; + if (PlayerCameraManager) + { + PlayerCameraManager->StartCameraFade(0.0f, 1.0f, 0.0f, FLinearColor::Black, true, true); + bPendingFadeToBlack = false; + } + else + { + bPendingFadeToBlack = true; + } + ShowLoadingScreen(true); + // Use MapPath if provided, otherwise fall back to LevelName (legacy behavior) const FString& LevelToLoad = MapPath.IsEmpty() ? LevelName : MapPath; @@ -362,6 +397,21 @@ void APS_Editor_PlayerController::DisableSublevelPostProcessMaterials() *CameraStart->GetActorLocation().ToString()); } } + + // Delay 2s then fade in from black over 2 seconds + if (bWaitingForSublevelFadeIn) + { + bWaitingForSublevelFadeIn = false; + FTimerHandle FadeTimer; + GetWorldTimerManager().SetTimer(FadeTimer, [this]() + { + ShowLoadingScreen(false); + if (PlayerCameraManager) + { + PlayerCameraManager->StartCameraFade(1.0f, 0.0f, 2.0f, FLinearColor::Black, false, false); + } + }, 2.0f, false); + } } void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bAdditive) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h index bd0c49f..63db99c 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h @@ -136,9 +136,18 @@ private: UPROPERTY() TObjectPtr CurrentSublevel; + /** True when a sublevel is loading and needs fade-in when ready. */ + bool bWaitingForSublevelFadeIn = false; + + /** True if we need to apply the fade-to-black once CameraManager is available. */ + bool bPendingFadeToBlack = false; + /** Disable custom post-process materials in the loaded sublevel to avoid stencil conflicts. */ UFUNCTION() void DisableSublevelPostProcessMaterials(); + /** Show/hide loading screen overlay on the HUD widget. */ + void ShowLoadingScreen(bool bShow); + void HandleEditorClick(FVector2D ScreenPos, bool bAdditive); }; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp index d89fb70..180cea2 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp @@ -45,6 +45,27 @@ void UPS_Editor_MainWidget::SetSceneSerializer(UPS_Editor_SceneSerializer* InSer TSharedRef UPS_Editor_MainWidget::RebuildWidget() { return SNew(SOverlay) + // Loading screen (behind everything — first slot = bottom layer) + + SOverlay::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SAssignNew(LoadingOverlay, SBorder) + .Visibility(EVisibility::Visible) + .BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush")) + .BorderBackgroundColor(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f)) + [ + SNew(SBox) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Loading..."))) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 18)) + .ColorAndOpacity(FLinearColor(0.7f, 0.7f, 0.7f, 1.0f)) + ] + ] + ] // Top-left: Title + Toolbar + SOverlay::Slot() .HAlign(HAlign_Left) @@ -999,6 +1020,19 @@ void UPS_Editor_MainWidget::NativeConstruct() } PopulateSceneList(); + + // Hide loading overlay if no sublevel is being loaded + if (APS_Editor_PlayerController* PC = Cast(GetOwningPlayer())) + { + if (PC->CurrentBaseLevel.IsEmpty()) + { + ShowLoadingScreen(false); + } + } + else + { + ShowLoadingScreen(false); + } } void UPS_Editor_MainWidget::PopulateSceneList() @@ -2184,6 +2218,14 @@ void UPS_Editor_MainWidget::OnModeButtonClicked(EPS_Editor_TransformMode Mode) } } +void UPS_Editor_MainWidget::ShowLoadingScreen(bool bShow) +{ + if (LoadingOverlay.IsValid()) + { + LoadingOverlay->SetVisibility(bShow ? EVisibility::Visible : EVisibility::Collapsed); + } +} + void UPS_Editor_MainWidget::OnCloseClicked() { if (CloseConfirmOverlay.IsValid()) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h index a2ee4a5..6ef141d 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h @@ -110,6 +110,13 @@ private: bool bSceneDirty = false; int32 LastKnownSpawnedCount = 0; + // Loading screen + TSharedPtr LoadingOverlay; + +public: + /** Show/hide the loading overlay. */ + void ShowLoadingScreen(bool bShow); + // ---- Helpers ---- TSharedRef BuildToolbar(); TSharedRef BuildPropertiesPanel();