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.
Images/CrimsonLoadingScreen/howto-processtask-bp.pngUnregister 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.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.
#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. - In a PIE session with the editor MCP connected, call the
crimson.loading.get_statetool 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.