UE5 & MVVM
|| Introduction to the system
With the introduction of UE5.1 last year, Epic sneaked in the UMG ViewModel as a Beta plugin. If you look for the documentation — you’ll be pretty dissapointed. There isn’t a lot thats spoken about the system but I guess since it’s really early on and we’ll wait for Epic to get it moving forward.
In the meantime, allow me to explain:
UMG ViewModel is Epic’s MVVM Solution. It’s a design pattern used to create separated, independent UI by removing the code from the UI and moving it to a Model of which both the UI and playerfacing functionality (Jumping, Punching, Shooting) can interact with.
I like to break these 3 into simple names
View : UI
View Model : Mediator
Model : Character/Codebase
It shows its development value by the fact that designers and artists can iterate on their designs without having to touch any of the code. At any stage, an artist and a programmer can step in and nothing between the two will collide. So you can see why this is a lucrative idea for prototyping and management. (The new TombRaider game is using this Design Pattern!)
Let’s say for example you don’t particularly care about artist/programmer interference, why bother using a mediator such a model. Well for one, it’s a much more efficient way of updating your UI elements as it only updates when the variables update. The reason this is efficient is because the UI is simply waiting for these updates to come through instead of trying to grab and update them even when there has been no change to the variables. An example of such is trying to update a variable on tick. So unless its frame-crucial, it’s a very inefficient to bind any raw values at unwanted times.
The system is event-driven. Meaning that it can replace existing frameworks such as BPIs or Event Dispatchers.
As for drawbacks, the inclusion of the separation can cause some performance overhead if some events are not properly setup aswell as it generally taking more memory as your implementation may introduce more variables.
Now let me show you an example from my own project (Primal Dominion: Aftermath) where this system is used to efficiently update Magazine/Ammo information.
Note: Excuse my screenshots if some variables may not matchup with the screenshots (Some functions & variables have been stripped and replaced with static variables for confidentiality reasons).
Here we have a function that I’ve implemented into the widget, it has a reference to the player pawn, of which it validates if its a nullptr or not before getting access to a component with ammo information in it.
At first glance this seems pretty reasonable? But it’s not, even though this function should update only when the variable changes, we could potentially change the result of the function or the variable itself, or we might want to get another variable too, in which case this blueprint now becomes an issue for both Programmers and Artists to work on.
So how can we streamline this?
Well for starters, I suggest you construct your code to work off of callbacks/delegates as much as possible, while its true you can plug the ammo into tick and continuously tell the ViewModel to Set the variable, this is bad practice, even if it doesn’t damage your framerate as much.
So the best place to tell the ViewModel to update is after you’ve decremented your ammo. This is also a multiplayer game so we can get callbacks from the server when the variable gets updated. Since the ammo is replicated, we can use the OnRep feature in UE Replication to get that callback (Highly suggest you look into this if you’re new to Replication / Net Code). If you’re unfamiliar with it, it’s a function that fires anytime the variable is updated by the server. This means we can use it as a server correction.
NOTE: In a replicated environment it is very important you’re updating either with server responses and/or with client predictions. If you’re running straight off of client variables you may cause synchronization issues such as the ammo updating faster than the server can keep up causing fluctuating values. In the following code examples, I’m only showing the use of OnRep. It’s advisable to use client prediciton to eliminate any slow client update lag.
In the following screenshot, we have the variable, it’s using the OnRep and the definition of the function returns a delegate (or more if you need more precise updates) we can access in BP (this can be exchanged for any event of your choosing).
Then we bind that to an event which runs an RPC on Client to the ViewModel to update its variable with the value of the replicated one
Now that the ViewModel has been updated, all logic from the Model side is complete, the ViewModel will now communicate with the View (UI) next.
When that variable ‘Mag Ammo’ is updated, an event is fired to ‘Set’ the variable:
This is a different type of Setter, this one has some logic that runs aswell as updating the variable.
In the screenshot we have an if statement, this is because the macro for updating the variable will return is true if the new value is different to the old one.
If it’s new then run that BROADCAST_FILED_VALUE_CHANGED macro.
The broadcast does exactly as what you think, it sends out a message similar to the View (UI) to tell it that anything that is binded by the ‘GetAmmoPoolString’ function should be refreshed.
Here’s the function that gets binded, as you can see it’s exactly like the widget function from the inefficient system except this time we have the variable in the ModelView:
In our View (UI) of choice, we implement the ViewModel and as you can see that FString function is accessible:
We then can do a special bind that to our textbox of choice so that the textbox can be updated everytime the function gets called:
Thats it! No really, that’s all there is to this conversion.
Though fortunately it’s not all there is to the ViewModel, there’s a lot more to discuss including the setup, there’s a little bit of boilerplate to get through before implementation is as easy as this. However the documentation is useful in explaining the general understanding of the system!
I may revisit this post in the future with a GitHub example or a video tutorial on getting through the boilerplate.