#Best way to handle health bars in a 2D game?

7 messages · Page 1 of 1 (latest)

tawny zenith
#

Basically the title. I have a 2D game and I want to draw health bars above the player and enemies when they've taken damage.

Initially a tried to use UI components to do so, but you can't have UI entities as children of non-UI entities, which would mean that I'd need to manually position every health bar above each respective entity. Doable, but tedious.

My next attempt was to just use a couple of Mesh2d squares, one as the black backdrop and a second in front of it as the red health bar, then I just have to scale the health bar horizontally based on the percentage of health remaining. That worked but the problem was that when you scale it it scales from the center and the center was in the middle of the square resulting in the health bar shrinking towards the center instead of towards the left as I had intended. There's a few ways to work around that but once again it seemed kind of tedious to manage and potentially error prone.

I inquired in the UI channel if there was a way to use a UI component as the child of an entity and got told that I can render the UI element to a texture and then use that texture on a non-UI entity. That would work, and if I only wanted a health bar for the player that would be good enough. However since I also want health bars for all the enemies, I'm looking at rendering potentially hundreds of health bars in a worst case scenario and then trying to manage assigning the right sub-section of the rendered texture to its respect enemy. This if anything sounds even more difficult to manage than simply trying to keep the UI component following the respective entity as I would have needed to do in the first solution.

Every solution I've tried seems to have some significant drawback to it. In an ideal world I would just be able to attach the UI entity as a child of the respective player or enemy entity. So, what's the recommendation here?

marble brook
#

I would do the second option but with materials, one background and one for the healthbar. You can avoid the entire positioning problem by using a custom material shader on the healthbar. Write something like

@group(2) @binding(0) var<uniform> material_color: vec4<f32>;
@group(2) @binding(1) var<uniform> health_rate: f32;

@fragment
fn fragment(mesh: VertexOutput) -> @location(0) vec4<f32> {
    if mesh.uv.x > health_rate {
      discard; 
    }
    return material_color
}
drowsy locust
#

Just as a side node, it should be not tedious at all to position the healthbars manually. Power of bevy shines in cases like this as you can make single system that queries for player/enemy location component and their healthbar and positions them. One system, and it will work for any future things out of the box as all you need to do is to attach right components to your new units and it will just work 🙂

twin zealot
#

Agree with Arrekin. With regard to the Sprite/Mesh2d rectangle solution, you will probably already have a system to update the size of the bar for all health bars. The math to position it correctly in the same system is trivial.

My healthbars are implemented as a HealthBar component with attributes like width, height, offset from the parent entity, whether or not to display them when the bar is full, etc. A system comes along and finds Added<HealthBar> and adds the background and foreground sprites. Another system comes along and updates the bar position for anthing with HitPoints and HealthBar.
https://github.com/rparrett/taipo/blob/main/src/healthbar.rs to illustrate.

The "normal UI" solution is less desirable IMO unless you need the bars to appear on top of other UI elements. But positioning them is also somewhat easy (set Style::top/left with Camera::world_to_viewport. This does get annoying though when you potentially have to order the system correctly relative to physics systems / transform propagation to avoid 1 frame of lag.

Rendering UI to a texture seems like a massive complication and probably performance issue. I wouldn't touch that one.

Shader solution seems like it would work just fine, but IMO, the less rendering code you have to write, the better. bevy_render is one of the least stable parts of the engine.

tawny zenith
#

Thank you everyone for your feedback.

@twin zealot Yeah, I didn't much want to touch the rendering UI to texture in part because of performance concerns. I think I probably could get it to perform reasonably well by packing all the healthbars into a single texture and then just rendering a subset of that texture as appropriate, but it's honestly not worth doing all that and I can see several potential problems and edge cases, the solving of which would be even more complicated than literally every other approach mentioned. Also thank you for providing that code for reference, I've already learned a few interesting things from looking at it (in particular the trick of using the Added filter on queries to spawn child-entities is pretty slick).

I think ultimately I'll go with what @twin zealot and @drowsy locust suggested, it's just a slight modification of the second approach I had already tried. The shader solution suggested by @marble brook is interesting and would definitely work, but would also be the most likely to get broken in a future update. I may pursue that approach in the future if instead of a basic colored bar I decide to go with a 9-sliced sprite as I think it would potentially look better than simply scaling it, but for a simple colored bar just scaling it on the horizontal access seems sufficient for now.

twin zealot
#

Bevy has built-in support for 9slicing sprites now, so no custom shader should be necessary.

tawny zenith
#

ah, sorry I wasn't being clear, I know it has support. What I meant is that rather than the sprite being scaled I would rather it be truncated. The shader would accomplish that easily. There's probably a way to do that without using a shader, but it would potentially be more complicated, where the shader solution is fairly straightforward.