# Animated Loading Screen with MoviePlayer and Slate in UE5

Most solutions I found for making a loading screen in Unreal Engine rely on converting your whole project to use level streaming, setting up an UMG widget with a delay or similar ways which, depending on your project, can take a lot of time to set up and is not always reliable.

While making my own game [Toontanks: Circuit Clash](https://icouldbreathe.itch.io/toontanks-circuit-clash) I discovered a different way to make a loading screen, which relies on the MoviePlayer. It is supported by all platforms and doesn't require a lot to set up. It runs on a background thread and can be bound with pre-loading and post-loading map events. It also supports playing videos, audio, UMG and Slate.

## Setting up MoviePlayer

This will require a custom C++ game instance class, which runs throughout the whole game and is one of the first objects to be initialised. I will use it as a base class for my game instance blueprint class, from which I will be passing my UMG widget.

To use the MoviePlayer we need to include the `MoviePlayer` module in the build file. The `UMG` module will also be needed to create the widget later on.

```cs
PublicDependencyModuleNames.AddRange(new[]
	{
		"Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "MoviePlayer", "UMG"
	});
```

## The Loading Screen with MoviePlayer

To create a UMG widget in C++, we need to have a hold of the class of the widget. So we define a `TSubclassOf<UUserWidget>` property which will be provided via blueprints.

Then, define `BeginLoadingScreen` and `EndLoadingScreen` functions like so in your game instance header file.

```cpp
public:
	virtual void Init() override;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Loading Screen")
	TSubclassOf<UUserWidget> LoadingWidgetClass;

	UFUNCTION()
	virtual void BeginLoadingScreen(const FString& MapName);
	UFUNCTION()
	virtual void EndLoadingScreen(UWorld* InLoadedWorld);
```

Bind the delegates in your `Init` function and don't forget to include the right header file.

`FCoreUObjectDelegates` holds a series of delegate bindings for the engine. `PreLoadMap` and `PostLoadMapWithWorld` are called exactly when a map begins loading and finishes, respectively.

```cpp
#include "MoviePlayer.h"

void UToonTanksGameInstance::Init()
{
	Super::Init();

	FCoreUObjectDelegates::PreLoadMap.AddUObject(this, &UToonTanksGameInstance::BeginLoadingScreen);
	FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &UToonTanksGameInstance::EndLoadingScreen);
}
```

In your `BeginLoadingScreen` function, create the `UUserWidget` and get its underlying slate widget pointer using `TakeWidget()` as the MoviePlayer only accepts the native `SWidget` pointer.

Then, define your attributes with `FLoadingScreenAttributes`. It's a structure for all the attributes your loading screen will have. The explanation for each one can be found in the [documentation](https://docs.unrealengine.com/4.26/en-US/API/Runtime/MoviePlayer/FLoadingScreenAttributes/). Assign your `SWidget` pointer to the `WidgetLoadingScreen` attribute.

And simply pass the structure into `SetupLoadingScreen()`.

```cpp
void UToonTanksGameInstance::BeginLoadingScreen(const FString& MapName)
{
  if (!IsRunningDedicatedServer())
  {
    const auto LoadingWidget = CreateWidget<UUserWidget>(this, LoadingWidgetClass, TEXT("LoadingScreen"));
    LoadingSWidgetPtr = LoadingWidget->TakeWidget();

    FLoadingScreenAttributes LoadingScreen;
    LoadingScreen.WidgetLoadingScreen = LoadingSWidgetPtr;
    LoadingScreen.bAllowInEarlyStartup = false;
    LoadingScreen.PlaybackType = MT_Normal;
    LoadingScreen.bAllowEngineTick = false;
    LoadingScreen.bWaitForManualStop = false;
    LoadingScreen.bAutoCompleteWhenLoadingCompletes = true;
    LoadingScreen.MinimumLoadingScreenDisplayTime = 1.f;

    GetMoviePlayer()->SetupLoadingScreen(LoadingScreen);
  }
}
```

And that's it! Now you have an UMG widget that is displayed every time a new map is being loaded.

The MoviePlayer will automatically destroy the loading screen when the map has finished loading, but you can use `EndLoadingScreen` for anything extra you would like to do.

And this is where we could stop, but I wanted to explore how to use this with Slate.

## Why Slate?

Because `MoviePlayer` runs on a background thread, the UMG widgets will not tick. In my game, I circumvented this by creating my own Slate widget for the part that need to be animated, it's also a good opportunity to learn about Slate in general. And it works if I just include it as part of the UMG widget I am using for the loading screen.

What I want from my animated widget, is essentially just two brushes. One sits on top of the other and rotates at some rate. Those brushes then can be exposed to the editor to provide custom images. The result should look something like this:

%[https://media.giphy.com/media/KQJDAcmkJmCvxcLzMw/giphy.gif] 

## Setting up Slate

This is slightly more advanced, so I would advise you to follow the [source code](https://github.com/icouldbreathe/toontanks-circuit-clash/tree/main/ToonTanks/Slate).

This will require private modules for Slate, which are already written in the build file, all you need to do is to uncomment it.

```cpp
// Uncomment if you are using Slate UI
PrivateDependencyModuleNames.AddRange(new[] { "Slate", "SlateCore" });
```

To make a Slate widget, two classes will be needed. A `UWidget` and an `SCompoundWidget` class. The `UWidget` class is essentially a wrapper for the `SCompoundWidget` class, and is the one that will be exposed to the editor.

## SCompoundWidget

The `SCompoundWidget` is the base class from which all non-primitive widgets are built, and that's what we need. It holds a protected member called `ChildSlot` that we will use to provide the content and hierarchy of the widget.

Now, create a class, which I am naming `SLoading` that inherits from the `SCompoundWidget` class. By convention, Slate widget file names start with an `S`.

There are two major things to set up in this class. A `Construct` method, where we will construct the widget, and the overridden `OnPaint` function to define how it renders.

Define the rotating and the background brushes along with their setters and getters, as well as `Sequence` and the `CurveHandle` that will be used for the animation. `RotationSpeed` is the rate at which the brush is going to rotate.

```cpp
private:
	FCurveSequence Sequence;
	FCurveHandle CurveHandle;

	const FSlateBrush* RotatingBrush;
	const FSlateBrush* BackgroundBrush;
	float RotationSpeed;
```

You can set up your arguments with `SLATE_BEGIN_ARGS` and give them initial values like so.

```cpp
public:
	SLATE_BEGIN_ARGS(SLoading)
			: _RotatingBrush()
			, _BackgroundBrush()
			, _RotationSpeed(0.2f)
		{
		}

		SLATE_ARGUMENT(const FSlateBrush*, RotatingBrush)
		SLATE_ARGUMENT(const FSlateBrush*, BackgroundBrush)
		SLATE_ARGUMENT(float, RotationSpeed)
	SLATE_END_ARGS()
```

### Construct

In the `cpp` file, in the `Construct` method, you will need to define your widget.

`SNew` defines a new component, in this case an overlay, which should have two slots defined with `+ SOverlay::Slot()`. Each slot should have `HAlign` and `VAlign` properties to center it. And in each of those slots - the appropriate brush, which in this case are our images.

We also would like to assign the brushes with the initial values and set up a curve with a given start time and offset, and start playing it.

```cpp
void SLoading::Construct(const FArguments& InArgs)
{
	RotatingBrush = InArgs._RotatingBrush;
	BackgroundBrush = InArgs._BackgroundBrush;
	Sequence = FCurveSequence();

	ChildSlot
	[
		SNew(SOverlay)
		+ SOverlay::Slot()
		  .HAlign(HAlign_Center)
		  .VAlign(VAlign_Center)
		[
			SNew(SImage)
			.Image(BackgroundBrush)
		]
		+ SOverlay::Slot()
		  .HAlign(HAlign_Center)
		  .VAlign(VAlign_Center)
		[
			SNew(SImage)
			.Image(RotatingBrush)
		]
	];

	CurveHandle = Sequence.AddCurve(0.f, 60.f);
	Sequence.Play(AsShared(), true, 0.f, false);
}
```

### OnPaint

The `OnPaint` function defines how the widget is rendered. Here we can set up its transform for rotation, as well as pass in colour and opacity parameters.

Let's define `RotationTransform` which takes our curve's linearly interpolated value between 0 and 1, which we can use to determine the angle of the rotation and multiply it by our `RotationSpeed` variable for the rate.

Then, create the geometry for the two brushes, using the calculated `RotationTransform` for the rotating brush. `FVector2D(0.5, 0.5)` centers the pivot from which it's going to be rotating.

```cpp
const auto RotationTransform = FSlateRenderTransform(FQuat2D(CurveHandle.GetLerp() * 360.f * RotationSpeed));

const FGeometry RotatingImageGeo = AllottedGeometry.MakeChild(
		RotatingBrush->ImageSize,
		FSlateLayoutTransform(),
		RotationTransform,
		FVector2D(0.5f, 0.5f)
		);

const FGeometry BackgroundImageGeo = AllottedGeometry.MakeChild(
		BackgroundBrush->ImageSize,
		FSlateLayoutTransform(),
		FSlateRenderTransform(),
		FVector2D(0.5f, 0.5f)
		);
```

Now, make two boxes for each brush, passing in the geometry. The last argument is for the tint which allows it to be customised in the editor.

```cpp
FSlateDrawElement::MakeBox(
		OutDrawElements,
		LayerId,
		BackgroundImageGeo.ToPaintGeometry(),
		BackgroundBrush,
		ESlateDrawEffect::None,
		InWidgetStyle.GetColorAndOpacityTint() * BackgroundBrush->GetTint(InWidgetStyle)
		);

FSlateDrawElement::MakeBox(
		OutDrawElements,
		LayerId,
		RotatingImageGeo.ToPaintGeometry(),
		RotatingBrush,
		ESlateDrawEffect::None,
		InWidgetStyle.GetColorAndOpacityTint() * RotatingBrush->GetTint(InWidgetStyle)
		);
```

## UWidget

Now we can create the `Loading` class which inherits from the `UWidget`, in which we define the properties that will be exposed to the editor and synchronised with our `SWidget`.

```cpp
public:
	virtual void SynchronizeProperties() override;
	virtual void ReleaseSlateResources(bool bReleaseChildren) override;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Appearance")
	FSlateBrush RotatingBrush;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Appearance")
	FSlateBrush BackgroundBrush;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Rotation")
	float RotationSpeed = 0.2f;

#if WITH_EDITOR
	virtual const FText GetPaletteCategory() override;
#endif

protected:
	virtual TSharedRef<SWidget> RebuildWidget() override;
	TSharedPtr<SLoading> LoadingSlate;
```

In `SynchronizeProperties()` use the setters to sync the properties. The function is called after construction or when the editor needs to update modified state. `RebuildWidget()` is called when the underlying `SWidget` needs to be constructed, so this is where we pass in our initial parameter values to our SLoading widget on construction.

`GetPalleteCategory()` allows to set up our own category in the editor palette to organise it and find our custom made Slate widgets.

```cpp
void ULoading::SynchronizeProperties()
{
	Super::SynchronizeProperties();
	LoadingSlate->SetRotatingBrush(&RotatingBrush);
	LoadingSlate->SetBackgroundBrush(&BackgroundBrush);
	LoadingSlate->SetRotationSpeed(RotationSpeed);
}

void ULoading::ReleaseSlateResources(bool bReleaseChildren)
{
	LoadingSlate.Reset();
}

TSharedRef<SWidget> ULoading::RebuildWidget()
{
	LoadingSlate = SNew(SLoading)
	.RotatingBrush(&RotatingBrush)
	.BackgroundBrush(&BackgroundBrush)
	.RotationSpeed(RotationSpeed);
	return LoadingSlate.ToSharedRef();
}

#if WITH_EDITOR
const FText ULoading::GetPaletteCategory()
{
	return LOCTEXT("CustomPaletteCategory", "ToonTanks Slate");
}
#endif
```

## Success

Now we can load up the editor and find our newly created Slate widget in the widgets palette.

![Slate widget appears in the editor.](https://cdn.hashnode.com/res/hashnode/image/upload/v1706628572471/6d8fbf8c-b932-465f-a097-9284419f6b96.png align="center")

And if we drag it in, we can customise its properties.

![Custom parameters.](https://cdn.hashnode.com/res/hashnode/image/upload/v1706628612241/da5a3cc8-5a20-47da-b999-c051b86fbe0e.png align="center")

That's it! Don't forget to pass your widget in your game instance blueprint and it should be good to go.
