Circular health bars

Started by
18 comments, last by rip-off 11 years, 9 months ago
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?
Advertisement

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.
f@dzhttp://festini.device-zero.de
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:

671-small.jpg

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?
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.
dHoEi.png
Thumbnail from The Witcher.

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:

671-small.jpg

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.



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.
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 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]

re-read what he typed
...you don't mean a Diablo...[/quote].
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]
If I've helped you in any way please push the reputation button, thanks!

Abstraction is my choice of words.
Portfolio: http://www.0x3a.com/
Blog: http://blog.0x3a.com/
re-read what he typed
...you don't mean a Diablo...
.
[/quote]
Whoops! Thanks for the correction :P

This topic is closed to new replies.

Advertisement