Creating a damage indicator

ui tutorials

12/5/2024

Mihail Todorov

In any action-packed game, conveying clear and immediate feedback to players is crucial for a satisfying experience. A damage indicator UI can significantly enhance gameplay by visually communicating when and where damage occurs. This article dives into how you can design and implement a responsive and immersive damage indicator UI, providing players with vital real-time feedback during intense gameplay moments.

Getting started

We’ll start by creating our UI separately. To do that we’ll need only a single HTML page and the cohtml.js file.

As many damage indicators show from where the damage comes from in a sort of a triangle that goes from the center to the edge and follows the origin of the damage to notify the player.

To recreate this we’ll use the conic-gradient .

Setting up our HTML

For this case we’ll be using data-bindings to show the indicators on the screen and rotate them. This is why we’ll need to create for each indicator two elements, one which will be the wrapper and another where the indicator will be.

This is because data-bindings in Gameface are not always evaluated in the same order. If we use a structural data-binding, like in our case data-bind-for to loop the elements, we need to make sure that there is a wrapper for the data-binding.

1
<div class="container">
2
<div class="damage-overlay"></div>
3
</div>

Styling the indicator

In the style tag we’ll add the following CSS:

1
body {
2
width: 100vw;
3
height: 100vh;
4
margin: 0;
5
}
6
7
.damage-overlay {
8
--angle: 90deg;
9
position: absolute;
10
width: 100vw;
11
height: 100vh;
12
background-image: conic-gradient(
13
from var(--angle) at 50% 50%,
14
red 1%,
15
transparent 2%,
16
transparent 99%,
17
red 100%
18
);
19
}

This will put the indicators on top of each other so they can be stacked.

Something to note is that we have set in the .damage-overlay element a variable called angle. This variable will allow us to change the angle of the indicator instead of having to reset the whole background image.

Adding the data-bindings

For this case we’ll add a Damage model that will contain all of our indicators.

Since we are currently working without a game, we’ll need to mock that data. So in our JS we’ll do:

1
engine.whenReady.then(() => {
2
engine.createJSModel('Damage', {
3
damages: [
4
{
5
angle: '83deg',
6
time: 1,
7
},
8
],
9
});
10
engine.synchronizeModels();
11
});

Where we’ll create our Damage model and add the damages property which will have all of our indicators. Each one will have the angle and the time that it will be on the screen. In our case this time will also relate to the opacity of the element.

With that data, we can add the data-bindings to our html.

1
<div data-bind-for="indicator:{{Damage.damages}}" class="container">
2
<div data-bind-style-opacity="{{indicator.time}}" class="damage-overlay"></div>
3
</div>

You may notice that there is no binding for the angle and the reason is that there is no built in data-binding for changing variables.

This is why we’ll create our own custom data-binding.

We’ll declare a class that will be our handler for the data-bindings and we’ll use the init and update methods to change the variable.

1
class AngleHandler {
2
init(element, value) {
3
element.style.setProperty('--angle', value);
4
}
5
6
update(element, value) {
7
element.style.setProperty('--angle', value);
8
}
9
}

Then we need to register the new binding attribute:

1
engine.whenReady.then(() => {
8 collapsed lines
2
engine.createJSModel('Damage', {
3
damages: [
4
{
5
angle: '83deg',
6
time: -1,
7
},
8
],
9
});
10
engine.registerBindingAttribute('angle', AngleHandler);
11
engine.synchronizeModels();
12
});

And we add the new data-binding to the element:

1
<div data-bind-for="indicator:{{Damage.damages}}" class="container">
2
<div data-bind-style-opacity="{{indicator.time}}" class="damage-overlay"></div>
3
<div data-bind-angle="{{indicator.angle}}" data-bind-style-opacity="{{indicator.time}}" class="damage-overlay"></div>
4
</div>

Now by changing the angle in our model, we can change the angle of our indicator.

Adding to a game

Although this is all there is to adding a damage indicator that rotates with the damage origin, it still doesn’t have any actual data. This is why we’ll add it to Unreal’s ThirdPerson sample.

Setting up the project

Before we start adding it, we need to set up our project in Unreal Engine. We’ll use the getting started guide from our documentation to install the Gameface plugin and integrate it into an existing project or use the sample project created by the installer.

Spawning the damage origin

The first thing in our game that we need to do is to set a random point in our world that will be where the damage occurred and our damage indicator will point to it.

To do that we’ll add a new NavMeshBoundsVolume to our map and place it over the ground.

This will allow us to call the blueprint node - GetRandomLocationInNavigatableRadius

We’ll set the location to be in a radius of 200 around our player.

We’ll also call this in an event listener so that we can call it at a random interval later.

Calculating the angle

Now that we have the location of our damage origin, we need to calculate the angle between the player and that origin.

To do that we’ll make a new function called CalculateAngle.

In this function we are doing the following:

  1. We get the player mesh location in the world
  2. We get the damage origin location that is passed to the function
  3. We convert the 3D coordinates of both into 2D screen coordinates
  4. We then use the FindLookAtRotation function to calculate the angle between both
  5. Finally the angle that we get we offset by 90 degrees as the coordinate systems between Unreal and the web are different and we add the necessary suffix in order to pass it to our UI

The reason that we convert the 3D to 2D coordinates is because this allows us to easily account for things like camera and character movement. This approach hover comes with the downside that if you are far away from the origin it will be offscreen and it’s coordinate will not be properly translated.

Passing our angle to the game

Next to pass our angle we’ll create two structs - one will be our model Damage which will have a key damages that will be an array of our second struct - the indicator itself. The indicator will have the angle, time and also the original damage origin so that we are able to update the angle during the game.

We’ll set the members of the indicator struct and then add it to an indicator array. After that we can add to our Damage struct the array and create a model from it.

Calling our custom event again

Finally we’ll set it so that we can call our custom event over and over again at a random interval.

Updating the angle during the game

The last thing we need to do is to update the angle of our damage indicator when the game runs so that it will rotate when we move the player and camera.

In conclusion

By implementing an intuitive damage indicator UI, you’re not only improving gameplay feedback but also creating a more engaging experience for your players. Experiment with animations, colors, and positioning to ensure your UI fits seamlessly into your game’s style and mechanics. With these techniques, your game will stand out for its polished and player-focused design.

On this page