In many games there are user interface (UI) buttons that can only be pressed when a certain amount of progress occurrs. The progress can be as simple as the passage of time or part of a sophisticated point accumulation system. These types of buttons are common in real time strategy (RTS) and role playing games (RPG). This type of UI element may be thought of as a progress button (ProgressButton
). This article examines how to construct a ProgressButton
for Godot using GDScript.
Scenario
Our game calls for a button that can only be pressed after a milestone has been reached. The button controls a tooltip that selects what the player can place in the game. The milestone is an epoch. The button can be pressed as soon as a certain number of epochs have elapsed. The player needs to know roughly how much progress has been made towards the epoch milestone in visual form. Once the milestone is reached the button is enabled for the player to press. When the button is pressed the tooltip is loaded. After the player clicks the left mouse button the ProgressButton
is reset and the tooltip is disabled until the next epoch milestone.
Construction
The ProgressButton
is made with the control nodes VBoxContainer
, Button
, and ProgressBar
. The Button
and ProgressBar
nodes are children of the VBoxContainer
to make the nodes stack vertically.
To Build:
- Create a new scene.
- Add a
VBoxContainer
. - Rename the
VboxContainer
toProgressButton
. - Add a
Button
as a child ofProgressButton
. - Add a
ProgressBar
as a child ofProgressBar
. - Attach a script to
ProgressButton
calledProgressButton.gd
. - With
Button
selected connecttoggled
signal to theProgressButton
node.- This step adds the
_on_Button_toggled
function toProgressButton.gd
- This step adds the
- With
ProgressBar
selected connectprogress_changed
signal to theProgressButton
node.- This step adds the
_on_ProgressBar_value_changed
function toProgressButton.gd
- This step adds the
ProgressButton.gd
ProgressButton.gd
provides the interface other nodes consume to instance copies of ProgressButton
. The interface consists of max_val
to represent the maximum value of the ProgressBar
, tooltip_mode
to represent the mode the ProgressButton
sets the tooltip to, tooltip
which supplies ProgressButton
an interface to control the tooltip, and clicks
to track the number of clicks allowed by the tooltip after the epoch milestone is reached.
extends VBoxContainer
export var max_val = 100
export var tooltip_mode = 0
var tooltip_enabled = false
var tooltip
ProgressButton
defines a signal to tell the parent node when it should reset ProgressButton
to its next state.
signal reset
The reset
signal is used to tell the parent node when to reset the state of the progress button.
func _ready():
$ProgressBar.max_value = max_val
The max_value
of the progress bar is set in the _ready
function.
func _on_ProgressBar_value_changed(value):
if value >= $ProgressBar.max_value:
$Button.disabled = false
else:
$Button.disabled = true
When the value of the progress bar changes a signal is emitted. Whenever that signal is emitted if the value of the progress bar is greater than or equal to the max value of the progress bar the button is enabled.
func _on_Button_toggled(button_pressed):
if button_pressed and not tooltip_enabled:
tooltip_enabled = true
When the button is pressed the tooltip is enabled. This allows the player to spend their tooltip “click”.
func _input(_event):
# If a tooltip is registered check the tooltip mode against the button mode.
if tooltip and tooltip.mode == tooltip_mode:
handle_click()
else:
handle_click()
If there is a tooltip registered to this button and the tooltip mode matches the mode set for the button then _input
calls handle_click
. This prevents handle_click
from being called when other ProgressButtons
control the tooltip. If no tooltip is registered handle_click
is called to allow testing the ProgressButton
.
func handle_click():
if (Input.is_action_just_pressed("mouse_left")
and tooltip_enabled
and $Button.pressed
and not $Button.is_hovered()):
$ProgressBar.value = 0
tooltip_enabled = false
emit_signal("reset")
handle_click
handles the player’s input after the ProgressButton
has been pressed.
Testing the ProgressButton
Testing in this case means building an interactive example that uses the ProgressButton
.
To build the example:
- Create a new scene.
- Add a
Control
node. - Rename the control node to
ProgressButtonTest
. - Instance the
ProgressButton
as a child ofProgressButtonTest
. - Attach a script to
ProgressButtonTest
calledProgressButtonTest.gd
. - With
ProgressButton
selected connectreset
to theProgressButtonTest
node.- This step adds
_on_ProgressButton_reset
toProgressButtonTest.gd
.
- This step adds
ProgressButtonTest.gd
extends Control
var intervals = 0
var interval = 0
intervals
represents how many intervals are shown in the ProgressButton
progress bar. interval
represents the current interval based on the elapsed epochs.
signal epoch
The epoch signal is emitted every time an epoch has elapsed. In this example an epoch is one second.
func create_timer(wait_time):
var timer = Timer.new()
timer.set_wait_time(wait_time)
timer.set_one_shot(true)
timer.connect("timeout", timer, "queue_free")
add_child(timer)
timer.start()
return timer
A timer node is used to for emitting the epoch signal. The create_timer
function is a helper for creating new timer nodes.
func _ready():
$ProgressButton/ProgressBar.value = interval
intervals = $ProgressButton.max_val
connect("epoch", self, "_on_epoch")
while true:
emit_signal("epoch")
yield(create_timer(1), "timeout")
On ready, the value of the ProgessButton
progress bar is set to the current interval and intervals
is set to the maximum value of the ProgressButton
. Next the epoch signal is connected to the ProgressButtonTest
node. _on_epoch
advances the state of the ProgressButton
each time the epoch
signal is emitted. Finally, an infinite loop emits the epoch
signal and yields execution until the next timer ends. This allows other processing to occur while waiting for the next epoch.
func _on_epoch():
if interval < intervals:
interval += 1
$ProgressButton/ProgressBar.value = interval
For each epoch, if the current interval is less than the maximum intervals interval
is incremented and the ProgressButton
‘s progress bar value is set to the current interval.
func _on_ProgressButton_reset():
interval = 0
$ProgressButton/ProgressBar.value = interval
When it is time to reset the progress button interval
and the progress bar are both reset to zero.
Conclusion
The ProgresssButton
is useful controlling actions that have “cooldown” periods after the action has been used. The construction of this UI element is straight forward. Although this example focuses on the passage of time, other mechanisms such as progress towards a certain goal may be used instead.