MultiPlayer: How to set a persistent loading screen while connecting

Hello,

I’m having trouble to set a loading screen that will persist several seconds after a player joined a game. My project is prepping several things that I need to hide when a client join. Issue is not happening to the Host though.

I tried several methods, none worked perfectly.

  1. Set the LoadingScreen Widget in Game Instance. Results: Widget gets automatically removed upon arriving in the new map.
  2. Set the Widget in the PlayerController OnBeginPlay. Results: Widget appears but not immediately. We can see the game for a frame or two.
  3. Using the Construction Script in the player controller to send an Event to the GameInstance (since it’s not possible to create a widget in the construction script). Results : Error: CreateWidget cannot be used on PlayerController with no attached player.
  4. Using the Level OnBeginPlay to Create the widget. Results : The level is visible for a frame or two (same as 2)

I’m out of options here. Is there some kind of option somewhere to magically fix this ? I dunno, like a bool “Auto Remove Widget on map travel” ?

Thanks

I have the same problem … How do you remove that frame or two. I tried many things and nothing seems to work …

I’ve put that task aside for a while, but I found out that using server travel instead of open map will keep the widgets alive during the loading. I think, I haven’t dug much into that yet.

I actually found a solution. The problem with connection to server is that BeginPlay is called a few frames after the first frame from the login is displayed. This is because (I presume) there are additional things needed to be done to connect correctly.

The solution is to not use the BeginPlay event but rather Tick to setup the initial screen. Additional, enable AllowTickBeforeBeginPlay option. My setup is like this: on Tick, I just do DoOnce node, and then execute everything there instead of in BeginPlay. You can disable Ticking after the initial tick to save performance.

Which blueprint are you adding this to? I tried adding it to the map tick, the gamemode tick, and the player controller tick. No matter what I tried, the loading screen does not appear before you can see the half-built map.

In single player it seems so fast that I can’t tell, but when joining a server I can see it for a couple frames.

The game mode is only spawned on the server so that won’t work. The player controller is also spawned on server and replicated to the client. For map tick I am unsure if you can set “Tick before Begin Play”.

I just added a simple blueprint to level that calls this on tick. So create a new blueprint, use the Tick event → DoOnce → Spawn Widget that overlays everything and place that blueprint in the level. Do not forget to tick of “Tick before play”.

There is another option - in C++ you can access the UGameViewportClient::AddViewportWidgetContent that does not need a player controller. So on the game instance, create two functions, one for adding the loading screen that takes in a UUserWidget and a ZOrder and then one that removes the loading screen that takes a UUserWidget you want to remove.

Then using the TakeWidget function, you can pass that widget in as such:

void UYourClassName::ShowLoadingScreen(UUserWidget *Widget, int32 ZOrder)
{
	GetGameViewportClient()->AddViewportWidgetContent(Widget->TakeWidget());
}

void UYourClassName::RemoveLoadingScreen(UUserWidget *Widget)
{
	GetGameViewportClient()->RemoveViewportWidgetContent(Widget->TakeWidget());
}

This loading screen will persist through server travels, so you can remove it once everything is loaded. I exposed the functions to blueprints so I can create the widget in the game instance in BP and then feed it into the show loading screen function and keep a reference of it so I can use that to remove it later if it is valid.

3 Likes

Have someone solved this problem?
I would also like to add a joining session screen when a client joins a server.
you

good job; thanks your idea!!

This is a fairly old topic but if you don’t feel like doing it properly with the load screen plugin then there is another hack that works. Make a custom GameInstance class and on your Init function, add a custom timer and set it to loop every 0.016 seconds. In the loop, add your widget to the viewport if it is not currently added to the viewport. This way it’ll add the loading screen to the viewport instantaneously when you arrive at the new map.

It’s dirty but it works.

Hello Vanoric, thanks a lot! Do you know if it is still a valid solution in 2020 with UE4 4.25?
And any chance you could also post you header file to see to make this functions BP available?
Would be nice for c++ newbies.
And this have to be implemented in a C++ file inherit from GameInstance and then use this C++ file as parent for BP GameInsance, right?

On “RemoveViewportWidgetContent(Widget->TakeWidget()” for “UUserWidget *Widget” I get an error:
pointer to incompatile class type is not allows

EDIT: Solution if someone got the same error. You have to include
#include “Components/Widget.h”
EDIT2: sorry, the correct one to inculde is:
#include “Blueprint/UserWidget.h”
And add “UMG” to your build.cs

1 Like

hey,i want to know how you create the vars “LoadingScreen” and “IsRunningDedicisplayTime”,i see docs that they introduce the idea to bind “preloadmap” and “postloadmap” delegate on functions in gameinstance they created,but i cant find the two vars above,thanks.

@you very much for the tip! Worked well in my case.

Absolutely nuts there is no way to have a loading screen render the first frame on clients after a map change.

The vonoric solution worked well with TheFlow3K’s hints! I slapped a UFUNCTION(BlueprintCallable) on the functions and it works just fine calling it from bluerpints.

For anyone who comes to this I had to edit @Vanoric’s answer a bit.

Source/MyProject/Public/DynamicFunctionLibrary.h

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "DynamicFunctionLibrary.generated.h"

class MyProject UDynamicFunctionLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

	public:

	UFUNCTION(BlueprintCallable)
	static void AddWidgetToViewport(UGameInstance* GameInstance, UUserWidget* Widget, int32 ZOrder);

	UFUNCTION(BlueprintCallable)
	static void RemoveWidgetFromViewport(UGameInstance* GameInstance, UUserWidget* Widget);
}

Source/MyProject/Private/DynamicFunctionLibrary.cpp

#include "DynamicFunctionLibrary.h"
#include "Blueprint/UserWidget.h"
#include "Engine/GameInstance.h"

void UDynamicFunctionLibrary::AddWidgetToViewport(UGameInstance* GameInstance, UUserWidget* Widget, int32 ZOrder)
{
    UGameViewportClient* client = GameInstance->GetGameViewportClient();

    client->AddViewportWidgetContent(Widget->TakeWidget(), ZOrder);
}

void UDynamicFunctionLibrary::RemoveWidgetFromViewport(UGameInstance* GameInstance, UUserWidget* Widget)
{
    UGameViewportClient* client = GameInstance->GetGameViewportClient();

    client->RemoveViewportWidgetContent(Widget->TakeWidget());
}

I made a function library which has two functions for adding and removing a widget from the viewport. This version correctly passes the ZOrder argument and allows you to use it in more places, if you want. My loading screen persists between server travel now by simply swapping the AddToViewport and RemoveFromParent calls with the respective functions written above.

2 Likes

Wow, thank you so much! I’ve been looking for a way to get this working, and so far this was the only one that actually did it!
Can I ask how else do you actually use BlueprintFunctionLibraries? I have not used it until now so it was a new thing for me.

Function libraries are a way of sharing functions between two objects. Instead of having multiple versions of the same function you can have one version that is shared to whoever needs to use it. Be mindful of them, blueprint libraries should probably be as generic as possible. Any references used in the library will need to be loaded by anything using them, making it real easy to create circular dependencies and long reference trees without realizing it.

For more info, see the docs: