[-1/50] A naive approach to reactive updates with Unreal UMG
![[-1/50] A naive approach to reactive updates with Unreal UMG](/content/images/size/w960/2023/05/Diff.jpeg)
In Paperplanes, the players accumulate various effects that alter their abilities. And since the knowledge of what abilities are available at their disposal (and the current state of each one of them) is critical for the player to make moment-to-moment gameplay decisions, the active effects needed to be displayed on the HUD (Heads-up Display).
With an user interface which requires constant updates, such as a HUD (Heads-up Display) of a video game, the developer has two options:
- Re-draw all the components in the interface after every change.
- Re-draw only the components which have changed.
The second approach has been popularised by frontend frameworks for web-development like React and Vue.js among others. The approach has consistent developer ergnomics. The developer defines the widget tree for each possible state and then simply mutates the backing data-structure and the framework takes care of selectively re-drawing the parts of the UI that have mutated.
Let's take a simple example using Vue.js.
<div id="parent">
<div>
<span v-if="player.isAlive">Game active</span>
<span v-else>Game over</span>
</div>
<div>
<button @click="terminate">Terminate Player</button>
</div>
</div>
export default {
data() {
return { player: { isAlive: true } };
},
methods: {
terminate() {
this.isAlive = false;
},
},
};
In the above code we are linking UI to the player
object via the v-if
and v-else
directives. When player.isAlive
is true
"Game active" will be displayed and "Game over" otherwise.
The <button>
calls the method terminate()
on click and which in turn sets the isAlive
flag to false. Behind the scenes, the Vue.js library is observing the player
object and when any of the values change it calculates the diff of HTML nodes resulting from the change in the current state and the new state and applies only the diff instead of redrawing the entire DOM hierarchy.
From a developer point-of-view, updating the UI is as simple as calling terminate()
compared with earlier approach where one would need to empty the <div id="parent">
and inject new nodes for "Game over".
Reactive Updates with UMG
The design mock-up had each accumulated effect displayed in a vertical grid on the left-hand side of the HUD - each cell representing one effect. These cells can update frequently as the player progresses within a gameplay segment.

Each displayed effect is an UImage widget within a UUniformGridPanel container. The accumulated effects themselves are stored as a TSet<FName>
type where each FName
can be resolved to the associated player effect.
The HUD widget exposes the function:
UFUNCTION(BlueprintCallable, Category="LostFerry")
SetEffects(const TArray<FPlayerEffect>& InPlayerEffects)
Which triggers a diff between the InPlayerEffects
and the currently rendered effects and then tags each cell of the container UUniformGridPanel
with the desired change to be affected in the next pass. The tags are represented via the following enum:
UENUM(BlueprintType)
enum EGridCellAction
{
ADD,
REMOVE,
REPLACE,
RETAIN,
};
And in the next pass, the widgets are either drawn, updated or removed depending on the associated tag.
The current implementation has two limitations:
- It loops twice. First to tag the cells that need to be mutated and second one to affect the subsequent mutation.
- It has does not handle animations. For example, if a new effect is acquired and the icon for it is fading in, the algorithm is not capable of waiting for the animation to finish in case new mutations need to be applied before the finish.
In subsequent iterations, we will look to address these limitations.