Creating nameplates and other dynamic onscreen elements
3/8/2022
Mihail Todorov
Markers, character information and other elements that frequently move on the screen based on character and camera positions are very common in game UI. The biggest struggle developers face while creating these elements in Gameface/Prysm is how to create a more complex element while maintaining maximum performance. In this tutorial we’ll demonstrate how to create player and enemy nameplates that are positioned on screen depending on where their corresponding character is. We’ll also show how to add more complexity to these nameplates with health and mana bars, experience gaining and more. And finally, we’ll add these UI elements to a simple game.
This tutorial assumes some familiarity with Gameface or Prysm. If you are not that familiar with the product yet, you can check out our starter guide (link to starter guide).
To get started we’ll first create the frontend part of this sample and we can do this by making a html page called ‘index.html’ where the user and enemy nameplates will be defined. The reason why we are doing this in a single page is because it will be more performant than having it split into multiple pages (and views respectively) for each nameplate.
Once we have our html page created, inside we’ll add the following code.
Here we are adding the style.css(link to file) that contains all of the styles that will be applied, cohtml.js(link to file) that will allow us to communicate with the game, and index.js that we’ll create for any custom logic that we might want to add.
In the user-nameplate-container we’ll add the following html code:
Once we run this code in the Player, we can see the following:
Where each of the numbers refers to:
- User level
- Experience bar
- Mana bar
- Health bar
- User power moves counter - these will be triggered when a power move is executed and is depleted
- Power move fill - which will be empty after each use and will start to fill. Once full, you can use the power move
After that we can add the enemy nameplate with the following code:
Once we run it in the player, we’ll get the following, where 1 is the enemy health bar and 2 is the enemy level:
Now that we have our nameplates created, we’ll need to go over each element and data-bind it in order to have it working with our game. We’ll start with the user nameplate since it will be static on our screen.
The first thing that we’ll need to data-bind are the power moves. The idea here is simple: the power moves will be an array of objects which will have a filled property that can be either true or false. This will allow us to control how many power moves our character has. For each object in the array we’ll create a power move element and then we’ll add a data-bind-if to create the fill.
Now if we use a power move, we can set the filled property to false and it will appear as empty on the screen.
Next we can set the level and the experience bar.
For the level we can just data-bind the value:
And for the experience bar we’ll data-bind the width:
We can also do the same for the health and mana bars:
Since the health, mana and experience bars are svg elements that are scaled, if we use percentage the width will be set according to the parent svg element width and then scaled. To avoid that and any unnecessary calculations, we can set the max values to be the same as the width. That being said, sometimes you would want to display things in a different way on the screen and in such cases it is recommended to have the logic that does this in the backend rather than the frontend to avoid any performance issues.
For simplicity we’ll set all of these values as they are without any changes - for example we’ll set the max player health to be 590 as is the width of the bar.
The last things that have to be shown are the texts of the health and mana, for this we’ll be using data-bind-value again:
Next we can add data-binding to the enemy nameplates. Since we expect to have more than one enemy, we’ll start by adding a data-bind-for to our “enemy-nameplate-container” class element:
With this we can now set the individual values of the nameplate. We’ll first start with the positioning. Since we are going to apply transformations to it such as translating and scaling it, we can directly use ‘data-bind-style-transform2d’ which allows us to provide a transform matrix from our model.
Then the only two things left to data-bind are:
The level, which we can do the same way as the user nameplate
And the health bar
As you can see, we are using stroke-dashoffset to create the effect of a radial bar and we are doing a calculation of 2600 (the stroke-dasharray) - the health, which will go from 1500 to 0 to create the desired effect.
Now we have to hook up everything to our game to see it in action. For the game we’ll be using the one made in this tutorial: https://www.youtube.com/watch?v=ROMWCMmNBHE by Unreal Engine Tutorials without the UI from the tutorial and with some minor modifications to fit our use case.
For this tutorial we are using Unreal Engine 4.27 with Gameface 1.30
The first thing we’ll add to our game is our UI. To start we’ll create a folder called ‘multiple-nameplates’ in GAME_ROOT_FOLDER\Content\uiresources and copy our html, css and javascript files to there. Then we’ll open our HUD blueprint and change the coui: path to match our newly created folder
Now if we load our game we’ll see the Player nameplate on the screen.
Next we have to create the structs that will be our models. We’ll be using two models, one for the Player and one for all of the enemies - PlayerModel and EnemyModel respectively.
To create them, we’ll just make the following structs in our Content Browser:
-
PowerMove struct. This struct will allow us to create a bar with segments that will show the number of power moves that our character can use. The reason that we are creating a struct is so that we are able to use it as an array in our PlayerModel
Here the default value of the ‘filled’ property will be true. -
Next we need to make the Player struct:
-
Then the Enemy struct which will be for a single enemy:
-
And finally the EnemiesArray which will be an array of Enemy structs that we’ll be using as our EnemyModel:
Now that we have all of our structs ready, we can start adding them to our HUD blueprint. To start we’ll just create three variables for the EnemiesArray (the array of Enemy structs), Enemy (which will be the EnemiesArray struct) and Player which we will use to create our models. Next we need to add the Ready for Bindings event so that we are able to register the models. To do that in the blueprint we right click on an empty space and find ‘Get Gameface HUD’
From there we pull the pin and find ‘Bind Event to Ready for Bindings’
And then we can pull the red event pin from there and create our event. In our event listener we can now create our PlayerModel which will be responsible for the Player and our EnemyModel which will hold all of our Enemy nameplates.
After we create our models we need to synchronize them (like in the screenshot above), so that their values get updated in the frontend.
Next we need to start updating the values of the Player struct so that our character can have dynamic health and mana, gain experience, and use power moves. We will start with creating our variables in the ThirdPersonCharacter blueprint. We’ll need the following items:
With the following default values:
Where PlayerModel is our struct that we’ll pass to our HUD, Model Player Power Moves is the array of power moves, powerMoveCounter will tell us how many power moves we have left and NextLevelExperience will tell us how much experience points we need to get to the next level.
The first thing we are going to set in our model is health. We’ll add it to our struct in the Event AnyDamage listener, right after setting it
This however won’t affect the model so we can add an event dispatcher to dispatch a PlayerUpdate event that will update it when we need it to. And we’ll pass our struct to it so that we can use it to update the values in the frontend.
Next we can make the left key deplete our Mana. Before that however we need to do a quick check if the Mana is above 45 to be able to fire. That way we won’t actually have it drop below 0.
Then we can deplete the Mana by 45 after firing the projectile. To make things more interesting, we can also set the damage the projectile deals to correspond to our Player level, which will make it so that the higher the level, the more damage that we do:
And finally we update our struct again and call the PlayerUpdate event: