3D Unit Healthbars

Problem

You want a floating “healthbar” for your 3D game objects (mobs, characters, etc.).

Solution

For this solution, we’re going to re-use a 2D healthbar based on a TextureProgress node. It’s already set up with textures and code for updating the value and color. If you already have something similar, feel free to use it here. In the example, we’ll name this scene “Healthbar2D”.

alt alt

If you need some assets, here are the three images used in the bar:

alt alt

alt alt

alt alt

Note

Re-using existing objects can save you a lot of time. Don’t re-invent the wheel everytime you need a healthbar, camera, or other common object.

Project setup

We’ll start with a KinematicBody mob. It’s programmed to spawn and travel in a straight line. It also has the following code to handle damage:

func _on_Mob_input_event(camera, event, click_position, click_normal, shape_idx):
    if event is InputEventMouseButton:
        if event.button_index == BUTTON_LEFT and event.pressed:
            health -= 1
            if health <= 0:
                queue_free()

alt alt

Clicking on a unit deals one damage. Do ten damage, and the unit is destroyed. Now we need a visual representation of that using our 2D bar.

2D in 3D

You can display a 2D image in 3D using a Sprite3D. Add one to a new scene and name it “Healthbar3D”. First, we’ll get it configured and sized, so set the Texture to the green bar image.

The Sprite3D acts like any other 3D object - as we pan the camera around, our perspective on it changes. However, we want the healthbar to always “face” toward the camera so that we can see it.

In the Inspector, under Flags, set Billboard to “Enabled”.

Now try moving the camera to confirm that the texture is always facing you.

alt alt

Add an instance of this scene to the Mob scene and position the bar above the mob’s body.

alt alt

Viewport texture

We don’t want the Sprite3D to show a static texture - we want it to display the 2D TextureProgress. We can do that using a Viewport node, which can export a texture.

Add a Viewport as a child of the Sprite3D. In the Inspector set these properties:

  • Transparent Bg: On
  • Rendering/Usage: 2D
  • Render Target/V Flip: On

We also need to set the size of the viewport to match the size of the healthbar texture, which is (200, 26).

Instance the HealthBar2D as a child of the Viewport. Your scene should look like this:

alt alt

If the Viewport were not a child of the Sprite3D, we could set it as the sprite’s texture directly in the Inspector. Since it’s a child, it won’t be ready at the right time, so we’ll need to set it in a script attached to the Sprite3D:

extends Sprite3D

func _ready():
    texture = $Viewport.get_texture()

Connecting it all together

In the mob’s _on_Mob_input_event() method, add the following after reducing the health:

$HealthBar3D.update(health, max_health)

Add the following to HealthBar3D.gd:

onready var bar = $Viewport/HealthBar2D

func update(value, full):
    bar.update_bar(value, full)

This calls the update method that already exists on the 2D bar, setting the progress bar’s value and selecting the bar color:

func update_bar(amount, full):
    texture_progress = bar_green
    if amount < 0.75 * full:
        texture_progress = bar_yellow
    if value < 0.45 * full:
        texture_progress = bar_red
    value = amount

Click on the mobs to see the health bars change.

alt alt

Note

Download the project file here: 3d_labels.zip

Wrapping up

You can use this technique to display any other Control nodes, such as Label, VideoPlayer, etc. You can even use the Viewport to “project” an entire 2D game in 3D space.

Like video?