# Circular health bars

This topic is 2065 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

So, a friend of mine and I are working out a game, when we came to the design of the HUD. We began discussing a circular bar for health, or experience, or something of the like. After coming up with the design we were discussing the tech behind it, and we can't wrap our minds around a good way to do a circular health bar. We were discussing a stencil buffer, or perhaps a shader, but that seems a bit much for just a GUI item.

Anyone know how to implement this in a simple fashion, or are we going to have to take one of the more complex routes?

##### Share on other sites

Anyone know how to implement this in a simple fashion, or are we going to have to take one of the more complex routes?

You realize that of the top of my head I could imagine at least three kinds of "circular health bars". An explanation what you're trying to do would help, but chances are that a texture is going to be your friend.

##### Share on other sites
Sorry about that, I'm referring to a bar similar to the teal bar in the thumbnail below. It starts on the right side, where it end in a small ball, and fills up along the black part all the way around the creature's head as your character's experience increases.

Thumbnail from The Witcher.

##### Share on other sites

I'm going to assume you don't mean a Diablo style health display and want to achieve a a sort of clock like display. There are a couple of ways to go about this depending on what your goal is. One way is to have a texture that contains only a small slice of the circle and render that using different rotations to make up your health circle. Another way to do this is to use an angle gradient texture and a pixel shader. Here's an example of an angle gradient:

Inside your pixel shader you decide what pixels you should render using a threshold value. So lets say you're at 25% health and you want to display a quarter of a circle, the gradient goes from 1.0 (white) to 0.0 (black), that means you only want the pixels with a 'color' value of 0.75 and up to render. This should give you the desired result after masking the pixels that lie out of the circle.

Did that help?

That actually helps quite a lot, thanks man.

##### Share on other sites
I don't know if you're going for a "pie-chart" style circle or a partial ring, but you can make either one out of procedural geometry.

The most straightforward way is to draw a bunch of triangles, either a triangle fan for a circle gauge or a triangle strip for a ring gauge. Depending on the size, you may not need all that many segments. You'll want to interpolate the vertex positions for the last gauge segment, though, so you can get fractional values. As an example, consider a ring-shaped gauge with 32 segments (64 triangles) that is 42% full. 32 * 0.42 = 13.44, so the gauge would have 13 completely-filled segments and one partial segment. If the full vertices of that last segment were V[sub]in[/sub][13], V[sub]out[/sub][13], V[sub]in[/sub][14], and V[sub]out[/sub][14], you'd use: V[sub]in[/sub][13], V[sub]out[/sub][13], V[sub]in[/sub][13] + (V[sub]in[/sub][14] - V[sub]in[/sub][13]) * 0.44, and V[sub]out[/sub][13] + (V[sub]out[/sub][14] - V[sub]out[/sub][13]) * 0.44.

You can also draw the gauge as a circle or ring texture applied to square geometry. That cuts the number of triangles you need to draw to a maximum of 5. Computing the positions is a little bit more complicated and involves trigonometry (specifically, the tangent). Here's how I did it for a game I worked on:

// get vertex memory VertexTL pointList[7]; VertexTL *ppoint = pointList; unsigned short pointCount = 0; static unsigned short indexList[15] = { 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6 }; unsigned short indexCount = 0; // generate center point ppoint->vv.x = (x0 + x1) * 0.5f; ppoint->vv.y = (y0 + y1) * 0.5f; ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u0 + u1) * 0.5f; ppoint->uv.v = (v0 + v1) * 0.5f; ppoint++; // generate top point ppoint->vv.x = (x0 + x1) * 0.5f; ppoint->vv.y = (y0); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u0 + u1) * 0.5f; ppoint->uv.v = (v0); ppoint++; if (curAngle < 0.25f * M_PI) { // one triangle pointCount = 3; indexCount = 3; // get segment fill ratio float ratio = 0.5f + 0.5f * tanf(curAngle); // generate top right point ppoint->vv.x = (x0) + ratio * (x1 - x0); ppoint->vv.y = (y0); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u0) + ratio * (u1 - u0); ppoint->uv.v = (v0); ppoint++; } else if (curAngle < 0.75f * M_PI) { // two triangles pointCount = 4; indexCount = 6; // generate top right point ppoint->vv.x = (x1); ppoint->vv.y = (y0); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u1); ppoint->uv.v = (v0); ppoint++; // get segment fill ratio float ratio = 0.5f + 0.5f * tanf(curAngle - M_PI * 0.5f); // generate right point ppoint->vv.x = (x1); ppoint->vv.y = (y0) + ratio * (y1 - y0); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u1); ppoint->uv.v = (v0) + ratio * (v1 - v0); ppoint++; } else if (curAngle < 1.25f * M_PI) { // three triangles pointCount = 5; indexCount = 9; // generate top right point ppoint->vv.x = (x1); ppoint->vv.y = (y0); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u1); ppoint->uv.v = (v0); ppoint++; // generate bottom right point ppoint->vv.x = (x1); ppoint->vv.y = (y1); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u1); ppoint->uv.v = (v1); ppoint++; // get segment fill ratio float ratio = 0.5f + 0.5f * tanf(curAngle - M_PI); // generate bottom point ppoint->vv.x = (x1) + ratio * (x0 - x1); ppoint->vv.y = (y1); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u1) + ratio * (u0 - u1); ppoint->uv.v = (v1); ppoint++; } else if (curAngle < 1.75f * M_PI) { // four triangles pointCount = 6; indexCount = 12; // generate top right point ppoint->vv.x = (x1); ppoint->vv.y = (y0); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u1); ppoint->uv.v = (v0); ppoint++; // generate bottom right point ppoint->vv.x = (x1); ppoint->vv.y = (y1); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u1); ppoint->uv.v = (v1); ppoint++; // generate bottom left point ppoint->vv.x = (x0); ppoint->vv.y = (y1); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u0); ppoint->uv.v = (v1); ppoint++; // get segment fill ratio float ratio = 0.5f + 0.5f * tanf(curAngle - M_PI * 1.5f); // generate left point ppoint->vv.x = (x0); ppoint->vv.y = (y1) + ratio * (y0 - y1); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u0); ppoint->uv.v = (v1) + ratio * (v0 - v1); ppoint++; } else { // five triangles pointCount = 7; indexCount = 15; // generate top right point ppoint->vv.x = (x1); ppoint->vv.y = (y0); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u1); ppoint->uv.v = (v0); ppoint++; // generate bottom right point ppoint->vv.x = (x1); ppoint->vv.y = (y1); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u1); ppoint->uv.v = (v1); ppoint++; // generate bottom left point ppoint->vv.x = (x0); ppoint->vv.y = (y1); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u0); ppoint->uv.v = (v1); ppoint++; // generate top left point ppoint->vv.x = (x0); ppoint->vv.y = (y0); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u0); ppoint->uv.v = (v0); ppoint++; // get segment fill ratio float ratio = 0.5f + 0.5f * tanf(curAngle); // generate top left point ppoint->vv.x = (x0) + ratio * (x1 - x0); ppoint->vv.y = (y0); ppoint->vv.z = 0.0f; ppoint->rhw = 1.0f; ppoint->diffuse = color; ppoint->specular = 0xFF000000; ppoint->uv.u = (u0) + ratio * (u1 - u0); ppoint->uv.v = (v0); ppoint++; } 
This code builds an indexed triangle list, though it could be done somewhat more efficiently. The value curAngle is the fill angle in radians; x0, y0, x1, and y1 are the gauge's extents in screen space; and u0, v0, u1, and v1 are its texture coordinates.

##### Share on other sites
I'm going to assume you don't mean a Diablo style health display and want to achieve a a sort of clock like display.

There are no clock-like displays in diablo to my knowledge. Maybe you're thinking of the health orb that fills up from bottom to top?

##### Share on other sites

[quote name='Mussi' timestamp='1307420545' post='4820385']I'm going to assume you don't mean a Diablo style health display and want to achieve a a sort of clock like display.

There are no clock-like displays in diablo to my knowledge. Maybe you're thinking of the health orb that fills up from bottom to top?
[/quote]

...you don't mean a Diablo...[/quote].

##### Share on other sites
Another ( Really simple ) way of doing this would be using frames ;) But this is more amateurish, using the method kdmiller3 described is one I've implemented in the past as well and works quite well.[url="../../user/180008-kdmiller3/"][/url]

##### Share on other sites
...you don't mean a Diablo...
.
[/quote]
Whoops! Thanks for the correction

##### Share on other sites

Sorry about that, I'm referring to a bar similar to the teal bar in the thumbnail below. It starts on the right side, where it end in a small ball, and fills up along the black part all the way around the creature's head as your character's experience increases.

Thumbnail from The Witcher.

I'd like to clarify that the technique I described works for the shape(or any other irregular circle shape) provided in your example picture, not sure if that was clear from my previous post. Basically you use the angle gradient to get the right portion of the health you wish to display and then you mask pixels to form the shape you want.

##### Share on other sites
I did something very similar in a pixel shader -- passed in the %age to fill as a parameter to the corners. Each pixel looked to see if it was inside the circle or outside and bailed if so. Then looked to see what angle round the circle it was, compared that to the display %age and picked an appropriate colour.

We also had a version which did a texture fetch based on the angle so we could pass gradients in as textures to use.

We could then put health bars on thousands of screen elements.

##### Share on other sites
That pixel shader technique has the advantage of working with all kinds of crazy shapes, not just circles. You might be able to do the same thing without a shader by using alpha test.

##### Share on other sites
In general, GUI stuff like this isn't done with a clever shader. The most commonly used tech for game GUIs is Flash, using ScaleForm to render it into the game (RAD game tools recently released Iggy, with equivalent functionality). In this case, the health bar -- whatever its shape -- would be an animation, with the player's health level determining which frame was shown. This lets your GUI artist use content creation tools he's comfortable with, and lets him iterate without constant support from a programmer.

Obviously a ScaleForm license is out of the reach of most indie devs, but if you're using UDK, good news -- it's in there. (Crytek might have it available too, but I'm not sure.)

##### Share on other sites
I'd just use an alpha-masked texture. Draw out a nice alpha gradient along the path you want your health bar to follow, and build 2 textures one for the completely filled state and one for the completely empty state. Then pass in the % of life that the player has left to the shader, and have it use the empty texture for alpha values greater than this, and the filled texture for alpha values less than this (with perhaps some blending between them around the boundary). This way, you can have any shape that you want.

##### Share on other sites
Hello everybody,

first of all, I must apology for posting in such an old topic. However, I've seen this article (and some others around the internet) a hundred times, and tried to learn some HLSL syntax, but I cannot figure out how to turn this idea into code. I have found some interesting stuff in other forums, but most of it deprecated. Since this is the best explanation I've read about how to do it so far, I've decided to ask here: can someone write an example of the pixel shader? Thank you in advance.

Greetings,

Aaron Contreras

##### Share on other sites

[quote name='Lloyd040690' timestamp='1307423706' post='4820392']
Sorry about that, I'm referring to a bar similar to the teal bar in the thumbnail below. It starts on the right side, where it end in a small ball, and fills up along the black part all the way around the creature's head as your character's experience increases.

Thumbnail from The Witcher.

I'd like to clarify that the technique I described works for the shape(or any other irregular circle shape) provided in your example picture, not sure if that was clear from my previous post. Basically you use the angle gradient to get the right portion of the health you wish to display and then you mask pixels to form the shape you want.
[/quote]

And you don't have to use an angle gradient either, you can use a spiral gradient, linear gradients, or a custom drawn mask for some really wierd fill patterns.

Also, you don't have to use a pixelshader for it, you can place your mask texture as the alpha channel in the "fill" texture and use a fixed function alpha test.

##### Share on other sites

Since this is the best explanation I've read about how to do it so far, I've decided to ask here: can someone write an example of the pixel shader? Thank you in advance.

##### Share on other sites

Hello everybody,

first of all, I must apology for posting in such an old topic. However, I've seen this article (and some others around the internet) a hundred times, and tried to learn some HLSL syntax, but I cannot figure out how to turn this idea into code. I have found some interesting stuff in other forums, but most of it deprecated. Since this is the best explanation I've read about how to do it so far, I've decided to ask here: can someone write an example of the pixel shader? Thank you in advance.

Greetings,

Aaron Contreras

i'm not certain about hlsl, but i imagine it shouldn't be too hard to get the idea from a glsl example, going off the second post, i'd draw a rectangle(or w/e shape really), and do something like so in shader:

 uniform float Health; //Mapped health between 1-0, where 1 is full health, and 0 is none. unifom sampler2D HealthTexture; uniform vec4 g_Color; //global color(generally red for health) in vec2 TexCoord; out vec4 f_Color; //final output color void main(){ f_Color = texture2D(HealthTexture, TexCoord); if(f_Color.r<=1.0f-Health) discard; //discard the fragment if our health is too low, by doing 1-health(1-health so that we map from black->white in terms of loss health) //alternativly, instead of discarding, we could do some other color like black, it's up to you. f_Color*=g_color; //Now make it w/e color we want. } 

anywho, that's how i in vision it would be done.

##### Share on other sites

This topic is 2065 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

This topic is now closed to further replies.