How-To: Hold the Loading Screen from Game Code
Goal: keep the loading screen up past the built-in conditions - until the server's data has replicated, an async request finished, or anything else your game defines as 'ready'. The screen hides only when every vote clears.
1. Hold with a process task (the Blueprint path)
UCrimsonLoadingProcessTask is a handle: creating one immediately holds the screen; calling Unregister on it releases the hold. Use it for any wait with a clear end moment.
In e.g. your PlayerController: Event BeginPlay -> Create Loading Screen Process Task (Show Loading Screen Reason = "Waiting for inventory data") -> promote the return value to a variable LoadingTask (right-click the pin -> Promote to Variable). Later, when your data arrives: Get LoadingTask -> Unregister.
Unregister when done. (The upside: a forgotten task can never wedge the screen forever.)Unregister releases it (plus the hold delay in packaged builds).2. Hold with the process interface (C++)
For a vote tied to an object's state rather than a one-shot wait, implement ICrimsonLoadingProcessInterface and return true while not ready. The manager automatically polls the GameState, every local PlayerController, and all of their components every frame - no registration needed for those. Always fill OutReason: it is what the debug tools show when someone asks why the screen is stuck.
// MyReadinessComponent.h - add this component to your GameState or PlayerController#include "Components/ActorComponent.h"#include "ICrimsonLoadingProcessInterface.h"#include "MyReadinessComponent.generated.h"UCLASS()class UMyReadinessComponent : public UActorComponent, public ICrimsonLoadingProcessInterface{GENERATED_BODY()public:void MarkReady() { bIsReady = true; }virtual bool ShouldShowLoadingScreen(FString& OutReason) const override{if (!bIsReady){OutReason = TEXT("Waiting for initial player data sync");return true;}return false;}private:bool bIsReady = false;};
ShouldShowLoadingScreen(FString&) is a plain C++ virtual, not a BlueprintNativeEvent - adding the interface to a Blueprint class gives you nothing to override. In Blueprint, use the process task from step 1; it covers the same need.GetDebugReasonForShowingOrHidingLoadingScreen (or the heartbeat log) shows your OutReason text.ShouldShowLoadingScreen is polled every frame and the screen stays up while yours returns true, so a condition that can never become false is a permanently black screen rather than a wait. Ask what happens when the thing you are waiting for never arrives: a pawn a front-end or cinematic map never spawns, a component the possessed pawn does not have, a replicated value the server never sends.Give the wait a deadline, release the vote when it expires, and log what was missing - a screen that gives up loudly is debuggable, one that hangs silently is not.
ACrimsonCorePlayerCameraManager does exactly this with its MaxWaitForCameraSecs property (CrimsonCore - requires all Crimson plugins).PCOwner->IsLocalPlayerController() (or the equivalent for your owner) and return false otherwise.3. Register objects outside GameState/PlayerController (C++)
Objects the manager does not poll automatically (actors, subsystems, plain UObjects) register themselves. Always pair register/unregister - the manager stores a weak reference, so a destroyed object can never crash it, but explicit unregistration is deterministic. This is exactly how CrimsonCore (CrimsonCore - requires all Crimson plugins) has its ACrimsonCorePlayerCameraManager hold the screen until the player camera reaches its default pose - a real-world example, not a requirement of this plugin.
#include "CrimsonLoadingScreenManager.h"#include "ICrimsonLoadingProcessInterface.h"// In a class that implements ICrimsonLoadingProcessInterface:void AMyActor::BeginPlay(){Super::BeginPlay();if (UCrimsonLoadingScreenManager* Manager = UCrimsonLoadingScreenManager::Get(this)){Manager->RegisterLoadingProcessor(this);}}void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason){if (UCrimsonLoadingScreenManager* Manager = UCrimsonLoadingScreenManager::Get(this)){Manager->UnregisterLoadingProcessor(this);}Super::EndPlay(EndPlayReason);}
4. Diagnose a stuck screen
The screen stays up while any one vote is true, so 'it never hides' always has a single findable cause:
- Run
CrimsonLoadingScreen.LogLoadingScreenReasonEveryFrame 1in the console - the current reason prints every frame. - Or read
Get Debug Reason For Showing Or Hiding Loading Screen(pure node) and print it on screen. - Prefer a periodic line to per-frame spam? Set Project Settings -> Crimson -> Crimson Loading Screen -> Timing -> Log Loading Screen Heartbeat Interval (default
5.0seconds,0disables) -LogCrimsonLoadingScreenthen printsLoading screen showing: 1. Reason: <reason>on that interval for as long as the screen is up. - If the reason never changes, check whether that voter can release. A vote waiting on something the current map never produces holds forever - that is a bug in the voter, not in the loading screen.
See also
- Concept: Lifecycle & Show Conditions - where your votes sit among the 18 built-in conditions.
- How-To: React to Loading Screen Events - get notified when the screen actually shows/hides.