In the next two parts, we will design and code the life and energy bars to put them in the party’s Heads-Up Display (HUD) in the next lesson.
These bars give the player feedback on each battler’s state, but we can also use them to provide real-time feedback on the action they can use. We will code the energy bar so that when you hover an action in the UIActionMenu, you get a preview of the energy cost in the Head-Up Display.
We will see this in the next lesson, where we will introduce a global signal bus, a pattern we use to loosely connect distant nodes in Godot, limiting coupling.
For now, let’s focus on the life bar. It will rely on the built-in TextureProgress node, while the energy bar will show you how to create a custom discrete counter.
Godot comes with two nodes to create progress bars: ProgressBar and TextureProgress. The first relies on the theme system and is ideal for loading bars in plugins, for example, while you’ll most likely use the second for in-game gauges.
Create a new scene with a TextureProgress node as its root and name it UILifeBar. Add both a Tween and an AnimationPlayer node as its children.
Let’s configure the UILifeBar node. Select it and set its Textures -> Under and Textures -> Progress to life_bar_bg.png
and life_bar_fill.png
, or any base and fill texture you’d like to use.
The Under and Over textures are always visible and sandwich the Progress one. The node will clip the Progress texture based on the Min Value, Max Value, and Value properties. We will control those via code.
Set the Step property to 0.01
so we can smoothly animate the bar. Godot will round the bar’s internal value based on the Step property, which can help to create some kinds of discrete bar designs.
I designed two animations with the AnimationPlayer: “danger”, which plays when the battler’s health is low, and “damage”, which we’ll play when the battler takes damage.
The danger animation affects the UILifeBar’s Tint -> Progress property, which works like Modulate but only affects the Textures -> Progress texture.
It’s a 1.6 seconds looping animation with three keys that keep the bar green and one that tints the bar in red.
The damage animation is much shorter, with a duration of 0.2 seconds. It flashes the entire node using the Modulate property.
While the keys appear all white, some use the color picker’s Raw color mode to use tones that brighten the pixels. On those keys, I’m using a value of 4
in the RGB color channels.
To turn on the Raw color mode, you first need to turn off the HSV
And with that, we can move on to the code.
Attach a new script to the UILifeBar node.
# Animated life bar. extends TextureProgress # Rate of the animation relative to `max_value`. A value of 1.0 means the animation fills the entire # bar in one second. export var fill_rate := 1.0 # When this value changes, the bar smoothly animates towards it using a tween. # See the setter function below for the details. var target_value := 0.0 setget set_target_value onready var _tween: Tween = $Tween onready var _anim_player: AnimationPlayer = $AnimationPlayer # We initialize the bar through a function because we need to set the `target_value` without # passing through its setter function. func setup(health: float, max_health: float) -> void: max_value = max_health value = health target_value = health # We animate the bar using the `Tween` node. When the tween completes, we may need to play the # "danger" animation. _tween.connect("tween_completed", self, "_on_Tween_tween_completed") func set_target_value(amount: float) -> void: # If the `amount` is lower than the current `target_value`, it means the battler lost health. if target_value > amount: _anim_player.play("damage") target_value = amount if _tween.is_active(): _tween.stop_all() # We have to calculate the animation's duration every time as we want to have a constant rate or # speed at which the bar fills or empties itself. var duration := abs(target_value - value) / max_value * fill_rate # We then tween from the current `value` to the `target_value` for `duration` seconds. _tween.interpolate_property(self, "value", value, target_value, duration, Tween.TRANS_QUAD) _tween.start() # When the tween animation completes, we play the "danger" animation if the battler's health is down # to 20%. func _on_Tween_tween_completed(object: Object, key: NodePath) -> void: if value < 0.2 * max_value: _anim_player.play("danger")
And with that, your life bar can animate smoothly and even flash when the player loses health. We will use it in the Heads-Up Display (HUD) lesson, right after coding the energy bar.