The energy bar is going to offer a discrete representation of the player’s energy. As the player has few energy points, we will have each as a separate node in our bar. Doing so will allow us to animate the points as you saw in the previous lesson.
Our energy bar needs two components:
Let’s start with the individual point scene. Create a new scene with a TextureRect named UIEnergyPoint. Add another TextureRect named Fill as a child of it as well as a Tween node.
Assign the energy_point_bg.png
file to the UIEnergyPoint’s Texture and energy_point_fill.png
to the Fill node. This way, the fill image displays in front of the background. Also, lower the opacity of the Fill using its Visibility -> Modulate property.
It should be slightly visible.
Also, set the UIEnergyPoint’s Stretch Mode to Keep Centered to center the texture in the node’s bounding box.
Attach a script to UIEnergyPoint to define its behavior. We will control it from the bar instance, so it only defines public methods that another node can call.
# Represents one energy point. This node has two animations and functions to make it appear and # disappear smoothly. extends TextureRect # This represents a target offset when selecting the point and tweening the node's position. const POSITION_SELECTED := Vector2(0.0, -6.0) # All our animations affect the `Fill` node. onready var _fill: TextureRect = $Fill onready var _tween: Tween = $Tween # We store the start modulate value of the `Fill` node because it's semi-transparent. # This way, we can animate the color from and to this value. onready var _color_transparent := _fill.modulate func appear() -> void: # We animate the `Fill` node's color from `_color_transparent` to a fully opaque white for `0.3 # seconds`. _tween.interpolate_property(_fill, "modulate", _color_transparent, Color.white, 0.3) _tween.start() func disappear() -> void: # This is the opposite of the appear animation, going from the fill's current `modulate` value # to `_color_transparent`. _tween.interpolate_property(_fill, "modulate", _fill.modulate, _color_transparent, 0.3) _tween.start() func select() -> void: # This animation offsets the node to `POSITION_SELECTED`. The "out" easing means the animation # starts at full speed and slows down as it reaches the target position. _tween.interpolate_property(_fill, "rect_position", Vector2.ZERO, POSITION_SELECTED, 0.2, Tween.TRANS_CUBIC, Tween.EASE_OUT) _tween.start() func deselect() -> void: # This is the opposite of the select animation. _tween.interpolate_property(_fill, "rect_position", POSITION_SELECTED, Vector2.ZERO, 0.2, Tween.TRANS_CUBIC, Tween.EASE_OUT) _tween.start()
The bar’s mostly going to be code. Create a new scene with an HBoxContainer as its root and name it UIEnergyBar.
We’re going to instantiate the points as a child of this node, and the container will align them horizontally.
Attach a script to the node with the following code.
# Bar representing energy points. Each point is an instance of UIEnergyPoint. extends HBoxContainer # We preload the energy point to instantiate it in `setup()` # Once again, you need `UIEnergyPoint.tscn` to be in the same directory for the path to work. const UIEnergyPoint: PackedScene = preload("UIEnergyPoint.tscn") # The following properties are in the same logic as Godot's built-in progress bar, which extends the # `Range` class. While we could also extend `Range`, it works with floating-point values, while our # energy is integer-based, and it would have more unnecessary features. var max_value := 0 var value := 0 setget set_value var selected_count := 0 setget set_selected_count # The setup function creates `max_energy` instances of the energy point scene. func setup(max_energy: int, energy: int) -> void: max_value = max_energy value = energy # This is a shorthand for `range(max_value)`. for i in max_value: var energy_point: TextureRect = UIEnergyPoint.instance() # The points will be children of the bar, allowing us to manipulate them with the # `get_child()` function. add_child(energy_point) func set_value(amount: int) -> void: var old_value := value value = int(clamp(amount, 0.0, max_value)) # The code below makes points appear and disappear when the value changes. As each point is a separate object, we need to loop over them and call the corresponding function on each instance. if value > old_value: # If we have more points, we need to play the "appear" animation on the added points only. # That's why we generate a range of indices from `old_value` to `value`. for i in range(old_value, value): get_child(i).appear() else: # If we lost points, we generate a range of indices going down, using `step`, the function's third argument. for i in range(old_value, value, -1): get_child(i - 1).disappear() # This function follows the logic of `set_value()` but plays the "select" and "deselect" animations # on the points instead of "appear" and "disappear". We will use it in the next lesson to preview # the energy cost of actions in the player's turn. func set_selected_count(amount: int) -> void: var old_value := selected_count selected_count = int(clamp(amount, 0.0, max_value)) if selected_count > old_value: for i in range(old_value, selected_count): get_child(i).select() else: for i in range(old_value, selected_count, -1): get_child(i - 1).deselect()
You may have noticed I like to use setter functions. Since Godot 3, the engine uses properties a lot more than it used to, and I like to use properties too so the code style stays somewhat consistent with it.
With this, we can control the bar by changing its value
and its selected_count
. We will do so in the next lesson, where we’ll build the HUD.