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.

Screenshot pendingImages/CrimsonLoadingScreen/howto-processtask-bp.png
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). Later, when your data arrives: 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.

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's ACrimsonCorePlayerCameraManager holds 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. In a PIE session with the editor MCP connected, call the crimson.loading.get_state tool for visibility, reason, and current tip in one shot.

See also

  • Concept: Lifecycle & Show Conditions - where your votes sit among the 16 built-in conditions.
  • How-To: React to Loading Screen Events - get notified when the screen actually shows/hides.