top of page

The Restart Level System

  • Mike
  • Jul 4
  • 3 min read

Updated: 10 hours ago


Just like the Level Transition System, I knew I wanted a Restart Level System that felt good and immediate, and something that wouldn't take you out of the game.


I also realized that there are some similarities to the approach here as the restart system also has to go through different stages before the level can be played again.



The Technical Asepct


By imagining the following scenario; We know the level will go through different states. From a high level point of view, those stages could be:


UENUM(BlueprintType)
enum class ERestartLevelState : uint8
{
	IsReady,
	Restarting,
	PreRestart,
};

and then we basically cycle through the stages as the state of the level changes.


When the player clicks the Restart button, I initially send a callback to all systems that need to be informed about the next steps, creating a "pre-restart" state. This state occurs just before the actual transition to the Restarting state.


Not a lot of systems care about this, but in essence, if a system is in the middle of a process of doing something that spans over multiple frames, this callback tells the system to stop doing what its doing.


The Restarting state however is where the magic happens. Within this Restart State, we have different phases:


UENUM(BlueprintType)
enum class ERestartPhase : uint8
{
	None,
	PreRestart,
	Restart,
	PostRestart
};


The Restart Phase


The pre restart phase is where the camera shakes and waits for animations and other systems to do stuff. As some systems takes time, like playing an animation, or explosions, we need to make sure those systems are done before moving on to the Restart Phase.


As all phases goes through a similar process

Initialize -> Do Logic -> Completed

we basically have a queue that we are waiting for to be finished before initializing the next phase.

bool URestartLevelSubsystem::TryBroadcastNextRestartPhase()
{
	IncrementRestartPhase();

	if (RestartPhase == ERestartPhase::None)
	{
		return false;
	}

	BroadcastRestartPhase(RestartPhase);

	return GetRestartLevelState() == ERestartLevelState::Restarting;
}

void URestartLevelSubsystem::BroadcastRestartPhase(const ERestartPhase InRestartPhase)
{
	OnRestartLevelPhaseChange.Broadcast(InRestartPhase);

	for (TPair<FGameplayTag, FRestartComponentArray>& Pair : RestartEntityComponentMap)
	{
		for (URestartEntityBaseComponent* Component : Pair.Value.Components)
		{
			if (!IsValid(Component))
			{
				continue;
			}

			if (Component->Restart(InRestartPhase))
			{
				RestartingComponents.Add(Component);
			}
		}
	}
}

For Actors, adding a RestartComponent was the easy way of not having to think about registering for callbacks as the component does this for us. This is the only place where we are adding an entity to the queue as its only the actors that plays animations we need to wait for.



UCLASS(Abstract, HideCategories=(Activation, Replication, Tags, "Sockets", Cooking, AssetUserData, Navigation))
class RESTARTLEVELSYSTEM_API URestartEntityBaseComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable)
	bool Restart(const ERestartPhase InRestartPhase);

	UFUNCTION(BlueprintCallable, DisplayName="Restart Completed")
	void RestartComplete(UObject* InInterfaceFinished);

	virtual void RestartCompleted() const;
};

As this is the base component, the child components decide what they contain and how to handle the restart phase.


For other systems however, a callback to what phase the restart state is being in was sufficient for this project.


The system has a big flaw however, and that is that it can create a soft lock if a component never reports back that its finished, so as a developer its important to always remember to report back when a restart component has finished its animations.


When all phases are completed and all components have reported back its time to finish the restart phase and broadcast that the level can begin play again.


void URestartLevelSubsystem::BroadcastRestartFinished()
{
	for (const TPair<FGameplayTag, FRestartComponentArray>& Pair : RestartEntityComponentMap)
	{
		for (const URestartEntityBaseComponent* Component : Pair.Value.Components)
		{
			if (!IsValid(Component))
			{
				continue;
			}

			Component->RestartCompleted();
		}
	}

	OnRestartLevelChange.Broadcast(ERestartLevelState::IsReady);
}

Each component get a final call to clean up anything from the restart state.


Final Thoughts


That is basically all there is to it.


All I have to do as a developer is to make sure I either add the RestartComponent and

 
 
 

Comments


bottom of page