How to avoid the starting black screen.

The screen that lasts for 1-2 seconds before anything happens.
I’ve tried on initialize event in game instance but it doesn’t actually trigger on startup, it happens after a short delay.
I tried minimizing the window through c++ but I can’t find where to put the function such that it triggers on launch, it always triggers after a short delay.
The closest I could get is setting the resolution very small so a smol square appears but that’s not a real solution…
rn am trying to figure out a way to make it so the packaged game launches minimized… but I can’t figure out how… but any solution that would result in u not getting a black screen on startup would work.

this could work, but it will take some research on your side.
you can use the Loading Screen functionality and the “MoviePlayer” (not only to play movies).
you can show an image before anything is rendered.
but as i said it will take some research and digging into ue code since i can’t recall exactly.
I might be wrong and it might not exactly work since what you’re trying to do it’s too fine grained.

i’m in a rush, so i can’t give more details.
but here’s a class i’ve made to show stuff in screens transitions.
i ran into some issues, (i can’t recall what it was), and ended not using it.
but maybe it helps you.
alternative do some internet search, i know there are plugins that do loading screens, maybe one allows to set the start image.

iirc the moviePlayer has some code to show a static image when the engine launches, but again, will take some digging on your side (and i might be hallucinating).

the nice thing about the movie player is that it will move the game thread to the bg and create a new slate thread.
the problem with that is that timers won’t work (unless you use my class) and some other stuff might have issues.

// Copyright (C) 2023 - Jeronimo Barraco-Marmol. All rights reserved.
// SPDX-License-Identifier: MIT

#include "LoadScr.h"

#include <thread>

#include "MoviePlayer.h"
#include "Blueprint/UserWidget.h"

// https://www.youtube.com/watch?app=desktop&v=ON1_dEHoNDg

ULoadScr* ULoadScr::Instance(const UObject* const O) {
	if (UNLIKELY(!O)) return nullptr;
	
	const UWorld* const W = O->GetWorld();
	if (UNLIKELY(!W)) return nullptr;

	const UGameInstance* const Instance = W->GetGameInstance();
	if (UNLIKELY(!Instance)) return nullptr;

	return Instance->GetSubsystem<ULoadScr>();
}

void ULoadScr::Initialize(FSubsystemCollectionBase& Collection) {
	Super::Initialize(Collection);

	const TSoftObjectPtr<UUserWidget> SoftWidget = TSoftObjectPtr<UUserWidget>(
		FSoftObjectPath("/JUtils/UI/TestLoadScr.TestLoadScr"));
	UUserWidget* const W = SoftWidget.LoadSynchronous();
	if (LIKELY(IsValid(W)))
		Widget = W;
}

void ULoadScr::SetWidget(UUserWidget* const O) {
	Widget = O;
}

void ULoadScr::DoTick(const float dt) {
	UE_LOG(LogTemp, Warning, TEXT("LoadScr::%hs tick frame=%lli"), __func__, GFrameCounter);
	const UWorld* const World = GetWorld();
	if (!World) return;

	// The movie player creates a new transient slate threads, and displays the "movie" there.
	// and it puts the game in a bg thread. then on stop it reverses it.
	
	// this is a horrible hack to allow the timer to tick.
	// it might as well break other things.
	// the timermanager won't tick if this does not change.
	GFrameCounter+=1;
	World->GetTimerManager().Tick(dt);
}

void ULoadScr::Show() {
	UE_LOG(LogTemp, Warning, TEXT("LoadScr::%hs"), __func__);

	if (!IsInGameThread()) {
		UE_LOG(LogTemp, Warning, TEXT("ULoadScr::%hs was not on game thread. avoided a crash. "), __func__);
		return;
	}

	CreateMoviePlayer();

	FLoadingScreenAttributes Attr;
	Attr.bAutoCompleteWhenLoadingCompletes = false;
	Attr.bWaitForManualStop = true;
	// There's a bug in ue that will make your game crash ... https://issues.unrealengine.com/issue/UE-254119
	// to be fixed in 5.7
	Attr.bAllowEngineTick = false;
	Attr.MinimumLoadingScreenDisplayTime = 10;

	IGameMoviePlayer* const Player = GetMoviePlayer();
	if (UNLIKELY(!Player)) return;

	if (!Widget)
		Attr.WidgetLoadingScreen = FLoadingScreenAttributes::NewTestLoadingScreenWidget();
	else {
		Attr.WidgetLoadingScreen = Widget->TakeWidget();
	}

	Player->SetupLoadingScreen(Attr);
	Player->PlayMovie();

	Player->OnMoviePlaybackTick().AddUObject(this, &ULoadScr::DoTick); // doesn't work

	if (UNLIKELY(!UseBGTick)) {
		UE_LOG(LogTemp, Log, TEXT("ULoader::%hs UseBGTick is false. Timers won't work. Good luck."),
			__func__);
		// good luck. timers won't work
		return;
	}

	// see DoTick as to why this. yes, it's a hack.
	UseBGLoop = true;
	AsyncTask(ENamedThreads::Type::AnyBackgroundThreadNormalTask, [this] {
		while (UseBGLoop) { // this happens on the bg, otherwise it will lock the game thread or the slate thread
			static constexpr uint8_t SleepTime = 100; // large enough so that the async task is processed.
			// it's loading so we don't really need a small tick, it even could cause problems
			std::this_thread::sleep_for(std::chrono::milliseconds(SleepTime));
			AsyncTask(ENamedThreads::GameThread, [this] { // tick on the game thread
				DoTick(SleepTime);
			});
		}
	});
}

void ULoadScr::Hide() {
	UE_LOG(LogTemp, Warning, TEXT("LoadScr::%hs"), __func__);
	// CreateMoviePlayer();
	IGameMoviePlayer* const Player = GetMoviePlayer();
	if (UNLIKELY(!Player)) return;
	UseBGLoop = false; // would stop the fake timer
	Player->OnMoviePlaybackTick().RemoveAll(this);
	Player->StopMovie();
	Player->ForceCompletion();
}

1 Like

btw i just realized your best bet could be to just wrap your app in a .bat that sends it to the background.
otherwise check in your uproject and build/target.cs the stage in which the modules are started.
you can add a module that starts before the major point needed.