How to create different types of bars in Gameface

ui tutorials

2/6/2025

Mihail Todorov

Bars are a fundamental part of any game UI, used to represent health, mana, stamina, loading progress, and more. But not all bars are created equal—some are segmented, others deplete radially, and some even animate bidirectionally. In this guide, we’ll cover different bar types, their use cases, and how to implement them effectively in your game.

What kinds of bars are we going to create in this tutorial

We are going to create 4 types of bars:

A radial bar:

A segmented bar:

An equalizer bar:

A bar that expands in both directions:

These bars offer unique visual styles compared to the traditional horizontal or vertical bars and can be easily created using Coherent Gameface.

Radial Bar

A radial bar is a simple yet effective way to represent progress in a circular manner. Here’s how you can create one using Coherent Gameface:

First, create the HTML structure for the radial bar. You will need a container and an SVG element to draw the circular path.

We will use stroke-dashoffset and stroke-dasharray to create the filling effect. Since Gameface supports these properties only on path elements, we need to use a path instead of a circle.

1
<svg class="circle-bar-svg" viewBox="0 0 100 100">
2
<path
3
d="M 50, 50 m -40, 0 a 40,40 0 1,0 80,0 a 40,40 0 1,0 -80,0"
4
fill="none"
5
stroke="rgba(255,255, 255, 0.2)"
6
stroke-width="8"
7
/>
8
<path
9
class="circle-bar-fill"
10
d="M 50, 50 m -40, 0 a 40,40 0 1,0 80,0 a 40,40 0 1,0 -80,0"
11
fill="none"
12
stroke="#efa7be"
13
stroke-width="8"
14
/>
15
</svg>

We also added two paths: one will be the fill, and the other will serve as the container for the fill.

And to center the icon, we’ll wrap the entire structure in a container and position the image absolutely on top.

1
<div class="circle-bar">
2
<div class="circle-bar-icon heart"></div>
15 collapsed lines
3
<svg class="circle-bar-svg" viewBox="0 0 100 100">
4
<path
5
d="M 50, 50 m -40, 0 a 40,40 0 1,0 80,0 a 40,40 0 1,0 -80,0"
6
fill="none"
7
stroke="rgba(255,255, 255, 0.2)"
8
stroke-width="8"
9
/>
10
<path
11
class="circle-bar-fill"
12
d="M 50, 50 m -40, 0 a 40,40 0 1,0 80,0 a 40,40 0 1,0 -80,0"
13
fill="none"
14
stroke="#efa7be"
15
stroke-width="8"
16
/>
17
</svg>
18
</div>

Now to style our bar:

1
.circle-bar {
2
width: 70%; /*This way it will be depended on the parent container, if you want to use it separately, you need to input your own width and height*/
3
height: 30%;
4
position: relative;
5
}
6
7
.circle-bar-svg {
8
width: 100%;
9
height: 100%;
10
}
11
12
.circle-bar-fill {
13
stroke-dashoffset: 250;
14
stroke-dasharray: 250;
15
transform: rotate(90deg); /*We rotate the circle since the stroke-dashoffset doesn't start from our desired location*/
16
transform-origin: center;
17
}
18
19
.circle-bar-icon {
20
position: absolute;
21
background-position-x: 50%;
22
background-position-y: 50%;
23
background-size: 53% 30%;
24
width: 100%;
25
height: 100%;
26
background-repeat: no-repeat no-repeat;
27
}

To complete our radial bar, we need to adjust the stroke-dasharray and stroke-dashoffset values.

  • When the stroke-dashoffset is set to 250, the bar appears empty.
  • When the stroke-dashoffset is set to 0, the bar is fully filled.

So, when we connect this to the game, we need to remember:

  • 250 means the bar is 0% filled (empty).
  • 0 means the bar is 100% filled.

To make it functional, we need to connect it to our game. There are two ways to achieve this: using data-binding or using events.

Using data-binding

If we have a model registered in our game, all we have to do is to add the following to our path element:

1
<path
2
class="circle-bar-fill"
3
d="M 50, 50 m -40, 0 a 40,40 0 1,0 80,0 a 40,40 0 1,0 -80,0"
4
fill="none"
5
stroke="#efa7be"
6
stroke-width="8"
7
data-bind-style-stroke-dashoffset="{{Model.radialBar}}"
8
/>

Using events

Using events requires adding some extra lines of code to our JavaScript:

1
engine.on('updateRadialBar', (value) => {
2
const radialBar = document.querySelector('.circle-bar-fill');
3
radialBar.style.strokeDashOffset = value;
4
});

The final result will be:

Segmented Bar

A segmented bar is a type of progress bar that is divided into multiple segments or sections, each representing a distinct part of the overall progress.

Use Cases:

  • Multi-step Processes: Ideal for visualizing progress in multi-step forms or workflows, where each segment represents a step.
  • Task Completion: Useful in applications where multiple tasks need to be completed, with each segment representing a task.
  • Health and Status Indicators: Common in games and applications to show different levels of health, battery, or other status indicators.
  • Data Visualization: Can be used to represent different categories or parts of data in a visually appealing way.

We’ll start by adding our HTML to the page for the bar:

1
<div class="bar-container">
2
<div class="bar-container-fill"></div>
3
<div class="bar-container-frame"></div>
4
</div>

What we do differently here is that instead of containing the fill within the frame, we’ll wrap a larger frame around the fill and skew the fill.

To do this we’ll add the following style:

1
.bar-container {
2
width: 36vh;
3
height: 5vh;
4
}
5
6
.bar-container-frame {
7
width: 100%;
8
height: 100%;
9
background-image: url(./bar-container.png);
10
background-size: 100% 100%;
11
background-repeat: no-repeat;
12
position: absolute;
13
}
14
15
.bar-container-fill {
16
width: 76%; /*As we skew the fill and try to fit it in a frame, the width is not going to always be 100%*/
17
height: 61%;
18
margin-left: 6.5vh;
19
background-image: linear-gradient(to right, transparent 20%, #e7b842 0%); /*This actually creates our segmented look*/
20
background-size: 2.736vh 100%;
21
transform: skewX(48deg);
22
}

Once the segmented bar is visually complete, we have two options to control its appearance:

  1. Changing the Width: This method involves adjusting the width of the fill to represent different progress levels. For example, you might set the width to 76% for a full bar and 69% for one segment less. This approach requires precise adjustments for each segment.

  2. Using CSS Animations: This method involves creating a CSS animation to control the bar’s appearance. You can then manipulate the animation using either the Web Animations API or by setting a negative animation delay to achieve the desired effect.

Using CSS animations is easier so we’ll go with that approach.

We’ll create the animation:

1
@keyframes bar-anim {
2
from {
3
width: 0%;
4
}
5
6
to {
7
width: 76%;
8
}
9
}

and add it to our bar:

1
.bar-container-fill {
2
width: 76%;
3
height: 61%;
4
margin-left: 6.5vh;
5
background-image: linear-gradient(to right, transparent 20%, #e7b842 0%);
6
background-size: 2.736vh 100%;
7
transform: skewX(48deg);
8
animation: bar-anim 1s steps(10) linear paused;
9
}

Notice how we use steps in the animation. This allows Coherent Gameface to automatically split the animation into segments, eliminating the need for manual adjustments.

To connect it to our game we again can use either data-binding or events. This time however we’ll demonstrate how to use data-binding with the animation-delay and the events with the WAAPI.

Using data-binding

If we have a model registered in our game, we can bind the animation delay to our model:

1
<div class="bar-container bar-container-left">
2
<div class="bar-container-fill"></div>
3
<div
4
class="bar-container-frame"
5
data-bind-style-animation-delay="-{{Model.segmentedBar}}"
6
></div>
7
</div>

Negative animation delay can be used to control the starting point of an animation. By setting a negative delay, you effectively start the animation partway through its cycle. This is useful for segmented bars where you want to show progress at different stages without restarting the animation.

For an animation with a 1-second duration and 10 steps like this one, each step represents 0.1 seconds (100ms). By using negative delays, you can control which step the animation starts from.

For example:

1
animation-delay: -0.1s /*starts the animation at the first step (10% progress).*/
2
animation-delay: -0.2s /*starts the animation at the second step (20% progress).*/
3
animation-delay: -0.3s /*starts the animation at the third step (30% progress).*/

Since also our animation-state is set to paused it will just go to the correct animation frame. In this case our model should have values from 0 to 1 to represent the steps of the segmented bar.

Using Events

Just like in our radial bar example, we need to listen for an event, but this time we’ll use the WAAPI (Web Animations API) to control the animation.

1
engine.on('updateSegmentedBar', (value) => {
2
const segmentedBar = document.querySelector('.bar-container-fill');
3
const animation = segmentedBar.getAnimations()[0];
4
5
if (animation) { //We need to make sure there is an animation so it doesn't throw an error
6
animation.currentTime = value * 1000; // value should be between 0 and 1
7
}
8
});

The final result will be:

Equalizer bar

The equalizer bar is a type of segmented bar that features segments in varying colors. This design is particularly useful for providing visual cues when certain values are reached, similar to how audio level equalizer bars indicate different sound intensities.

There are two ways to create an equalizer bar: using multiple elements for each segment or using a single image.

Using multiple elements gives you more control over each segment but can impact performance, especially if you have many bars. Using a single image is more efficient and simpler to implement.

We’ll start by adding our HTML:

1
<div class="sound-levels">
2
<div class="sound-bar">
3
<div class="sound-bar-fill"></div>
4
</div>
5
<div class="sound-bar">
6
<div class="sound-bar-fill"></div>
7
</div>
8
<div class="sound-bar">
9
<div class="sound-bar-fill"></div>
10
</div>
11
<div class="sound-bar">
12
<div class="sound-bar-fill"></div>
13
</div>
14
<div class="sound-bar">
15
<div class="sound-bar-fill"></div>
16
</div>
17
<div class="sound-bar">
18
<div class="sound-bar-fill"></div>
19
</div>
20
<div class="sound-bar">
21
<div class="sound-bar-fill"></div>
22
</div>
23
<div class="sound-bar">
24
<div class="sound-bar-fill"></div>
25
</div>
26
</div>

and styling it:

1
.sound-container {
2
width: 20vh;
3
height: 22vh;
4
position: absolute;
5
top: 47%;
6
transform: translateY(-50%);
7
right: 8%;
8
opacity: 0.7;
9
}
10
11
.sound-levels {
12
height: 40%;
13
width: 100%;
14
display: flex;
15
}
16
17
.sound-bar {
18
height: 100%;
19
border: 0.1vh solid #9bf2fa;
20
width: 12.5%;
21
}
22
23
.sound-bar-fill {
24
width: 100%;
25
height: 100%;
26
background-image: url(./sound-bar.png);
27
background-size: 100% 100%;
28
mask-image: linear-gradient(to bottom, white, white);
29
mask-size: 100% 0%;
30
mask-position: bottom center;
31
mask-repeat: no-repeat;
32
animation: sound 1s steps(17) linear;
33
}

Here, we create a mask using a gradient and adjust its size to achieve the desired effect:

1
@keyframes sound {
2
from {
3
mask-size: 100% 0%;
4
}
5
6
to {
7
mask-size: 100% 100%;
8
}
9
}

Since we are utilizing an animation, connecting it to our game follows the same approach as with the segmented bar.

The final result will be:

Bidirectional bar

A bidirectional bar is a unique type of progress bar that expands in both directions from a central point. Creating this bar follows the same principles as a standard progress bar, with a slight modification to achieve the bidirectional effect.

The HTML follows the standard way of using a container and fill:

1
<div class="visualizer">
2
<div class="track"></div>
3
</div>
1
.visualizer {
2
height: 7vh;
3
border: 0.3vh solid white;
4
position: absolute;
5
bottom: 28%;
6
right: 5.9%;
7
display: flex;
8
align-items: center;
9
padding: 0.4vh;
10
opacity: 0.7;
11
}
12
13
.track {
14
width: 1vh;
15
height: 0%;
16
background-color: white;
17
margin: 0 0.2vh;
18
}

The key to achieving this effect lies in using the flexbox layout with display: flex and align-items: center. This setup ensures that when the bar’s width changes, it expands symmetrically from the center rather than from one end.

Connecting it to the game is straightforward. You can either use data-bind-style-width if you have a model, or handle it with events by adjusting the width, similar to the approach used in the radial bar.

The final result will be:

In conclusion

From simple linear bars to complex radial and segmented designs, implementing the right type of bar can enhance both functionality and visual appeal. By understanding the mechanics behind different bar styles, you can tailor your UI elements to fit your game’s needs. Now, go ahead and experiment with different designs to find the best fit!

On this page