In this tutorial we’ll learn how to create a Graphical User Interface for our RPG, using a few simple nodes. We’ll also see how to update it using Godot’s event system and signals.
For now, we will mainly deal with two player stats:
- Health: represents the player’s health status. It has a maximum value that cannot be exceeded and slowly regenerates over time. Health points are lost when the player is hit by an enemy attack.
- Mana: it’s the player’s magic power. It has a maximum value that cannot be exceeded. It’s consumed when casting a fireball and it regenerates over time.
To start, let’s add these statistics to the player’s script:
# Player stats var health = 100 var health_max = 100 var health_regeneration = 1 var mana = 100 var mana_max = 100 var mana_regeneration = 2
The variables health and mana are the current values, while health_max and mana_max are the maximum values that cannot be exceeded. The health_regeneration and mana_regeneration variables are the regeneration rates of the two stats, expressed in recovered points per second.
Health and mana bars
The GUI of a game is used to display information to the player in a quick and easy way. For example, it can show to the player what their health, character’s level, and other stats are using bars and other graphic elements. The way generally used to show health and mana in a RPG GUI is to use bars whose length can vary when points are lost or recovered.
We will create these bars using ColorRect nodes. As the name implies, this node has only one function: to show a colored rectangle on the screen.
Add a ColorRect node as a child of Root and rename it Health.
In the Inspector, click on Color and set the values of Red, Green, Blue and Alpha to 0, 0, 0 and 160 respectively.
Then, open the Rect section and set Position to (3, 158) and Size to (74, 9).
Now, add a child node to Health, also of ColorRect type, and rename it Bar. In the Inspector, set its color to (255, 50, 50, 255) and, in the Rect section, set Position to (1, 1) and Size on (72, 7).
In the workspace, you’ll now see a red bar surrounded by a semi-transparent black border.
Run the game and move to the right. You will see that the bar will remain anchored to the ground and exit the screen!
To solve this problem, you need to create a second canvas to draw the GUI. Add a CanvasLayer node to Root.
Now drag the Health node into CanvasLayer.
Run the game again: this time the bar will remain anchored to the lower left corner of the game window.
To create the mana bar, right-click on the Health node and choose Duplicate.
A duplicate of the Health node will be created, including all its children. Rename the duplicate node as Mana.
Select Mana node and, in the Inspector, change the value of Rect → Position → y to 169. Then, select its child node Bar and change its color to (33, 128, 255, 255). You will now have a second blue bar to view the player’s mana level.
Updating health and mana values
Now that we’ve created the health and mana bars, we need to update their length whenever Player‘s health and mana variables are changed.
We will do this by using a custom signal, emitted from the Player node script when we change one of the player’s stats. We will attach to each nodes a script, that will handle the signal and take care of changing the length of the bars.
Open the Player script and declare the custom player_stats_changed() signal using the keyword signal:
The first time we want to emit this signal is when the game starts. This way, even if we had changed the length of the bars in the editor to performs some tests, at the start of the game they will be set to the correct initial state.
We will emit the signal inside one of the functions we will use most often in Godot: the _ready() function. As the name says, the _ready() function is called when the node is “ready”, i.e. when both the node and its children have entered the scene tree. This function is usually used for node initialization, so it’s the right place to emit player_stats_changed() to set the bars to their initial length.
To emit a signal, we’ll use the emit_signal() function. Add this code to Player‘s script:
func _ready(): emit_signal("player_stats_changed", self)
The emit_signal() function is called passing as the first argument the name of the signal we want to emit. In addition to the signal name, it accepts a variable number of parameters which are subsequently passed as parameters to the function that handles the signal.
In our case we set only one parameter: a reference to the Player node, obtained using the self keyword. Bar scripts will use this reference to read Player health and mana variables.
As we said earlier, health and mana regenerate over time. Then, we must add the code that increments these values very frame. Each time they are incremented, the player_stats_changed() signal must be emitted to update the bars.
To execute code continuously during the game, we will use another fundamental Godot function: the _process() function. This function is called at every frame, as fast as possible. The function has a parameter delta, which represents the elapsed time since the previous frame.
For health and mana regeneration, add this code to the script:
func _process(delta): # Regenerates mana var new_mana = min(mana + mana_regeneration * delta, mana_max) if new_mana != mana: mana = new_mana emit_signal("player_stats_changed", self) # Regenerates health var new_health = min(health + health_regeneration * delta, health_max) if new_health != health: health = new_health emit_signal("player_stats_changed", self)
At the beginning of the function, we calculate the updated mana value, adding to the current value the mana variation for the current frame (mana_regeneration * delta). The min() function ensures that the maximum health value is never exceeded. If the new value is different from the previous one, the value is updated and the signal to update the GUI is emitted. For the health variable, it works the same way.
The last circumstance in which we want to emit the player_stats_changed() signal is when we cast a fireball. Casting a fireball costs a certain amount of mana, which we’re going to subtract when the fireball input event is detected.
In the _input() method, change the fireball event handling code like this:
elif event.is_action_pressed("fireball"): if mana >= 25: mana = mana - 25 emit_signal("player_stats_changed", self) attack_playing = true var animation = get_animation_direction(last_direction) + "_fireball" $Sprite.play(animation)
The code checks if there is enough mana to cast a fireball (the cost is 25 points). If so, the points are subtracted and the signal is emitted to update the GUI.
Connecting the signal to scripts
At this point, we only need to create the scripts for the two bars and connect them to the signal emitted by Player.
To keep the project organized, in the FileSystem panel, create a folder named GUI, in which we will put all files related to the user interface. Attach a new script to the Health node and call it HealthBar.gd. Save it in the GUI folder you just created.
If you want, you can delete all the lines of the script except the first one (extends ColorRect).
Now we must connect the player_stats_changed() signal to this script. Select the Player node and go to the Node → Signals panel. Select the player_stats_changed() signal and press Connect.
When the Connect Signal window appears, select the Health node and press Connect again.
Godot will open the Health node script and you’ll see the new function that handle the signal. Replace its code with this:
func _on_Player_player_stats_changed(var player): $Bar.rect_size.x = 72 * player.health / player.health_max
As you can see, we added an argument to the function. This argument will contain the reference to Player that we have passed as a parameter when emitting the signal. The function does nothing but set the width of the Bar node by calculating it as the maximum width (72 pixels) multiplied by the player’s health percentage (player.health / player.health_max).
Repeat the same procedure for the Mana node, creating the ManaBar.gd script and connecting it to the signal. The code that handles the signal is also very similar:
func _on_Player_player_stats_changed(var player): $Bar.rect_size.x = 72 * player.mana / player.mana_max
Run the game and press the fireball button. You will see the mana value drop instantly and then slowly rise again.
If you want to test the health bar, you can temporarily change the health variable in Player’s script to a value lower than health_max. You will see the bar grow as health regenerates (the health bar moves more slowly than the mana bar because health regenerates by 1 point per second instead of 2).
Experience points and player’s level
The second block we must add to the GUI will show us the experience points (XPs) earned by the player and his current level. In this tutorial we will just draw the interface, without going into the details of its working. We will view XPs and levels progression in a future tutorial.
What we want to create is a simple box, where the XP and level values are shown. To show text, we need a font that reflects the pixel art style of the game, so I created one that you can download by clicking the button below.
In the FileSystem panel, create a folder named Fonts and drag the font you just downloaded into it. We can’t directly use the font in a GUI node, but instead, we must first create a DynamicFont type resource. To do so, right-click on the Fonts folder and choose New Resource.
Choose DynamicFont and press Create.
Finally, save the new resource as Font.tres.
The properties of the newly created resource will appear in the Inspector (switch to it if you have the Node panel still selected). In the Font section, drag the Schrödinger.ttf file into the Font Data property. In the Settings section, set Size to 10 (at this size, the font is pixel perfect). Finally, save the resource by pressing the disk icon.
Create a rectangle by adding a node of type ColorRect as a child of CanvasLayer and call it XP. In the Inspector, set Color to (0, 0, 0, 160) then, in the Rect section, set Position to (124, 158) and Size to (72, 20).
Now, add four Label nodes as children of XP, and set their Custom Fonts → Font properties to use the Font.tres resource we created earlier. Then, set their parameters as shown in the table:
|Name||Text||Align||Rect → Position||Rect → Size|
|LabelXP||XP:||Left||(2, 1)||(20, 10)|
|LabelLevel||LVL:||Left||(2, 10)||(20, 10)|
|ValueXP||0/100||Right||(21, 1)||(50, 10)|
|ValueLevel||1||Right||(21, 10)||(50, 10)|
This is the final result:
The game we are creating is very simple and therefore there will be only two types of objects that the player can own: health potions and mana potions.
So, our inventory will simply be made up of two boxes, that show us how many potions we have. Next to the quantity, we will place two sprites to identify the type of potion. Even for inventory and potions, for now we’ll only draw the user interface. There will be a specific tutorial explaining how it works.
First of all, download the potions sprite by clicking the button below.
Import the image you just downloaded into the project, placing it in the GUI folder. Remember to re-import the image disabling the Filter property.
To create the box for health potions, add a ColorRect child node to CanvasLayer and call it HealthPotions. In the Inspector, set its Color property to (0, 0, 0, 160), then, in the Rect section, set Position to (243, 158) and Size to (36, 20).
Now, add a Sprite node to HealthPotions. In the Inspector, drag the potions.png file into the Texture property then, in the Region section, turn on Enabled and set the region Rect‘s w and h to 16. Finally, in the Transform section, set Position to (10, 10).
To show the amount of potions the player owns, add a Label node to HealthPotions. In the Inspector, in the Custom Fonts section, set Font to Font.tres. Then, set Text to 0, Align to Right and, in the Rect section, set Position to (19, 6) and Size to (14, 10).
For the mana potions box, right-click on HealthPotions and choose Duplicate. Rename the newly created copy in ManaPotions. With ManaPotions selected, in the Inspector, change Rect → Position to (281, 158). Then, select its child Sprite node and in Region → Rect set x to 16 to change the image of the potion.
This is the final result:
In this tutorial we learned how to create a simple Graphical User Interface and update it using Godot’s event system.
You can try the final result of the tutorial by clicking here:
In the next tutorial, we will introduce monsters into the game and provide them with basic artificial intelligence.