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.

Player stats

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:

signal player_stats_changed

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"

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 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_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_max).

Repeat the same procedure for the Mana node, creating the 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.

Download “Schrödinger Font” Schrö – 6 KB

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:

NameTextAlignRect → PositionRect → Size
LabelXPXP:Left(2, 1)(20, 10)
LabelLevelLVL:Left(2, 10)(20, 10)
ValueXP0/100Right(21, 1)(50, 10)
ValueLevel1Right(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.

Download “SimpleRPG Potions Sprite” potions.png – 262 B

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.


Anastasia · March 7, 2020 at 5:47 pm

Ok hello! As always, I did something wrong and the manna bar doesnt work
I get lots of the error:
E 0:00:02:0155 Error calling method from signal ‘player_stats_changed’: ‘ColorRect(’: Method not found.
core/object.cpp:1238 @ emit_signal() @ _ready()
and cant quite say why.

var health = 100
var health_max = 100
var health_regeneration = 1
var mana = 100
var mana_max = 100
var mana_regeneration = 2
var attack_playing = false

signal player_stats_changed

# Player movement speed
export var speed = 75
var last_direction = Vector2(0, 1)

func _ready():
emit_signal(“player_stats_changed”, self)

func _input(event):
if event.is_action_pressed(“attack”):
attack_playing = true
var animation = get_animation_direction(last_direction) + “_attack”
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”

Here’s the stats fragment of the code. I asure you the signal is well connected and everything. The bars do have proper code too
thanks for being patient with my stupidity

    Davide Pesce · March 7, 2020 at 7:43 pm

    According to the error, there is no _on_player_player_stats_changed function in Have you checked that the name is typed correctly?

      Anastasia · March 8, 2020 at 11:13 am

      Both the mana and health code looks like this:
      extends ColorRect

      func _ready():
      pass # Replace with function body.

      func _on_Player_player_stats_changed(var player):
      $Bar.rect_size.x = 72 * player.mana / player.mana_max

        Davide Pesce · March 8, 2020 at 11:22 am

        Godot is case sensitive, _on_Player_player_stats_changed is a different function than _on_player_player_stats_changed. Always check that lowercase and uppercase letters of functions, variables and nodes are correct!

Anastasia · March 8, 2020 at 12:02 pm

YES you’re right! Bun now i get another set of errors

W 0:00:02:0448 Font oversampling only works with the resize modes ‘Keep Width’, ‘Keep Height’, and ‘Expand’.
scene/main/scene_tree.cpp:1158 @ _update_root_rect()

Guess the rect cant shrink? But why?

    Davide Pesce · March 8, 2020 at 8:48 pm

    Open the Project Settings window and go to the Display → Window section. Check if Strech → Mode is 2d and Strech → Aspect is keep.

      Anastasia · March 11, 2020 at 12:29 pm

      Aspect wasnt, Thanks~!

Leave a Reply

Your email address will not be published. Required fields are marked *

By continuing to browse this Website, you consent to the use of cookies. More information

This Website uses:

By continuing to browse this Website without changing the cookies settings of your browser or by clicking "Accept" below, you consent to the use of cookies.