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.

Prerequisites
Quick Start is done. Pick your path below: Blueprint uses the process task; C++ can also implement the process interface.

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.

Store the task in a variable or the hold vanishes
The manager holds the task weakly. If you drop the return value on the floor, garbage collection destroys the task and the hold silently releases at some random later moment. Always promote the return value to a variable, and call Unregister when done. (The upside: a forgotten task can never wedge the screen forever.)
Verify
With the task alive, the screen stays up after the map is ready; the Output Log heartbeat shows your reason string. 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.

cpp
// 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;
};
The interface is C++-only
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.
Verify
While your component returns true, GetDebugReasonForShowingOrHidingLoadingScreen (or the heartbeat log) shows your OutReason text.
A vote must be able to end
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).
Only vote for this machine
A listen server owns an object for every player, not just the local one - a component on each player controller, or a camera manager per controller. If yours votes on behalf of a remote player, the host's screen waits on state that is not the host's, and every player who joins restarts the wait. Gate the vote on 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.

cpp
#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:

  1. Run CrimsonLoadingScreen.LogLoadingScreenReasonEveryFrame 1 in the console - the current reason prints every frame.
  2. Or read Get Debug Reason For Showing Or Hiding Loading Screen (pure node) and print it on screen.
  3. Prefer a periodic line to per-frame spam? Set Project Settings -> Crimson -> Crimson Loading Screen -> Timing -> Log Loading Screen Heartbeat Interval (default 5.0 seconds, 0 disables) - LogCrimsonLoadingScreen then prints Loading screen showing: 1. Reason: <reason> on that interval for as long as the screen is up.
  4. 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.