🚀 Spring Sale is live! 🚀 Don't miss your chance to get all my products at 30% OFF!  

Get it!
avatar
罗传月武(YueWu)@罗传月武(yuewu)
Technology
News

UE4 Stack based UI Management

ue4 stack ui

Introduction

Writing UI code is often something that programmers tend to avoid, but even for this task, which is generally shunned by programmers, it requires some careful consideration to write it well and elegantly.

Although I don't know what good code is, I do know what bad code is. I have browsed through the UI systems of most online stores and read numerous "best practices" for UMG (Unreal Motion Graphics). Unfortunately, most of them have not been implemented in an elegant manner. There is no unified UI management, and the practices vary greatly. Some switch between different widgets using enums in the PlayerController, some create a UI widget immediately when they want to display it, and others close all UI widgets using GetAllWidgetOfClass and then open the desired one. There are even some that use fancy techniques like "One widget rules all!" (and then use WidgetSwitcher to switch between widgets and use interfaces to communicate between UIs).

All of the above are bad practices.

If you understand the concept of a stack and a state machine, please continue reading (these concepts are well-known to game developers and don't require much explanation).

Approach

A stack-based state machine is the killer technique for solving this type of problem (although it's not the only approach). Let's recall the games we have played.

  1. Opening a menu: Pressing the Tab key brings up the pause menu (Layer 1) and enters the viewport. The pause menu has buttons for resuming the game, accessing game settings, and quitting the game. When you click on the game settings button, another game settings menu (Layer 2) pops up and covers the pause menu. The game settings menu has options for sound settings, graphics settings, etc. When you click on the graphics settings, another graphics settings menu (Layer 3) pops up.
  2. Closing a menu: Each menu has an "X" button in the upper right corner to close the UI. When you close the graphics settings menu (Layer 3), it is removed from the viewport, and the game settings menu (Layer 2) returns to the top. When you close Layer 2, the pause menu (Layer 1) returns to the top.
  3. Closing all menus at once: For example, when the player presses the Esc key, they directly return to the game interface. This action essentially automatically closes Layers 3, 2, and 1.

Alright, have you noticed?

Do the operations of opening and closing menus described above resemble a stack? When a menu enters the viewport, it is pushed onto the stack (Push, entering the top of the stack). When a menu exits the viewport, it is popped from the stack (Pop, leaving the top). When a top-level menu is covered by a newly opened menu and becomes unresponsive (going down one level in the stack), it is pushed (leaving the top). When the top-level menu exits the viewport, the menus below it that were covered reappear at the top (going up one level in the stack, Pop, re-entering the top of the stack). Pressing Esc to close all menus is actually popping the menus from the top one by one.

At the same time, it also resembles a state machine: Each menu in the stack is a state in the state machine, and only one state can be active at a time, which is the state at the top of the stack (CurrentState). Entering/exiting the stack's top is equivalent to the state's Enter and Exit.

Implement Stack StateMachine

Since UI operations follow a certain pattern and can be simulated using a stack-based state machine, let's first implement a generic stack-based state machine in Unreal Engine. Then, based on the stack-based state machine, we can implement a UI stack-based state machine (because a stack-based state machine is not limited to UI management).

First, each state can be entered, exited, and updated, including the state machine itself. States in the stack can also be pushed and popped. Here is the specific implementation:

Types Definition

ue4 stack ui

Create an enumeration and declare the necessary delegates, which can be placed in any header file, such as "StateMachineTypes.h".

Stack State Interface

ue4 stack ui

Next, create an Unreal interface called "StackStateInterface.h". This interface defines the common behaviors of states.

Stack State

ue4 stack ui

Then, create a stack state "StackState.h" that implements the StackStateInterface.

ue4 stack ui

StackState.cpp

State StateMachine Definition

d5c3a797-ef09-4f24-a375-f4ab263d122a.jpg
ue4 stack ui

Now that we have states, let's implement a stack-based state machine. To make it reusable, let's create it as a component. StackStateMachineComponent.h

The StackStateMachineComponent also implements the StackStateInterface, and it can push and pop states, as well as pop specific states or all states.

ue4 stack ui

This part mainly provides callbacks for blueprints to ensure flexibility.

ue4 stack ui

GetterSetter,供外部访问。

Stack StateMachine Implementation

ue4 stack ui

Implementation of state pushing

ue4 stack ui

Implementation of state popping

ue4 stack ui

Pops specified nums of states or empty the stack.

ue4 stack ui

Ensure continuous update of the current state.

ue4 stack ui

The state machine itself is also a state

ue4 stack ui

We need to know when a state is pushed or popped, for example, to log what happened in the stack.

Implementing UI Stack StateMachine

UI StackState

Now that we have a generic stack state machine that can accomplish many things and can be extended, let's extend it to manage UI. In Unreal Engine, all UIs are UserWidgets, so let's create a UIState that inherits from UserWidget. Its functionality is similar to UStackState, except that it inherits from a different parent class. However, both of them implement the IStackStateInterface. This way, any UserWidget created in our game can be managed as a stack state by the stack-based state machine.

ue4 stack ui

UIState.h,It is similar to UStackState, but the interfaces for blueprint usage are now native implementations.

ue4 stack ui

UIState.cpp,Similar to UStackState

ue4 stack ui

When a UI state enters/exits the top

ue4 stack ui

Update is not implemented yet and can be implemented in blueprints.

UI Stack State Machine

ue4 stack ui

UIManagerComponent.h, "GetCurrentUIState" used the two meta of DeterminesOutputType and DynamicOutputParam.

Usage of UI Stack StateMachine

Now that we have a generic stack state and stack state machine, and we have added UI stack states and a UI stack state machine (UUiMnanagerComponent), we generally place this manager under the PlayerController. Any UMG (Unreal Motion Graphics) that needs to be used as a "UI" in the game should inherit from UIState.

1.In the PlayerController:


170e66f4-db4a-4398-a084-bedf2903ef67.jpgf0bdba70-2b40-4de6-9b64-2a2b89cf7e48.jpg623b5922-67d2-417e-a70b-2717c7bff8de.jpg686b825a-2d80-42e7-96e5-81f8b7e5fc62.jpgda91f35e-cf1c-406b-85ce-3c7fc18fae61.jpg


Best Practices/Design Concepts/Q&A

1. Why did you use screenshots instead of copying the code?

Because you're lazy. What you type is yours, and if you don't type it, it's still mine.

2. You mentioned that the UI should be created only when needed, but in your screenshots, you create them when they are needed. Why?

That's not good, especially when the UI has many dependencies. In practice, I would maintain a configurable TMap<FName, TSubclassof<UUIState>> States in the UIManager and create all the UIs in the BeginPlay of the UIManager, storing them in a TMap<FName, UUIState*> StateInstances. Then, I would write a helper function like PushNamedState(FName UIName);

3. What if I want to have animated transitions for opening and closing my UI?

You can create a subclass called AnimatedUIState that inherits from UIState. In this subclass, you can implement various transition effects (such as lerping opacity, color, etc.) and provide interface functions like GetTransitionTarget (which can be overridden in blueprints to specify the target for the transition). Then, you can execute the relevant transition effects during the Enter and Exit of the state. If you want to use UMG animations for transitions, unfortunately, UMG animations cannot be inherited. However, if it's just transitions, you can implement them in code.

4. What if my UI is not structured in a hierarchical manner, such as having multiple tabs on one screen?

In that case, you can use a WidgetSwitcher to ensure that all the UIs are within the same layer. It just means that this layer will be more complex.

5. How do I implement in-game UI, such as a player's health bar?

Typically, in my UIManager, I would add a default state called UMG_PlayingState during the game process. All UIs displayed during gameplay would be shown on top of this state. You can also perform various actions based on the UICount, such as allowing the Tab key to open the pause menu only when UICount = 1 (meaning there is only one Playing state). Additionally, this PlayingState can handle other things, such as displaying screen prompts when the player picks up items (ShowInfo(FText Text)).

6. PlayerController exists on the server (specifically, on a Dedicated Server), and servers do not have UMG. How do we handle this in a multiplayer scenario (added on June 1, 2020)?

When performing operations on the UIManager, first get the PlayerController and use the IsLocalPlayerController function to ensure that UI operations are only performed on the local player.

7. If you have any other questions, feel free to ask in the comments.