In this tutorial we will learn how to create objects that can be picked up and used by the player. To keep things simple, in our little game there will be only two types of objects, i.e. two potions that will allow the player to recover health and mana.

Potion scene

To begin, download the image we will use for potions by clicking the button below.

Download “SimpleRPG Small Potions Sprite” small_potions.png – 202 B

To keep the project organized, create a new Potion folder under Entities. Within this folder, import the image you just downloaded (remember to re-import the image with Filter disabled).

Now, create a new scene. We will use this scene for both types of potions. Use an Area2D node as the root node, and rename it Potion.

Add a Sprite node to Potion. In the Inspector, drag small_potions.png to the Texture property. Since the image contains both potions, we need to enable the Region section and set the w and h properties to 8. In this way, only one potion is used.

Then, add a CollisionShape2D node to Potion. In the Inspector, set Shape to new RectangleShape2D and resize the rectangle to have the same size of the potion.

Potion script

Attach a new script to Potion and save it as in the Entities/Potion folder.

In the script, before extends Area2D, add this line:


When you add at the top of a script the tool keyword, it will be executed not only during the game, but also in the editor. Running a script in the editor can be useful for doing many things, but it’s mostly used in level design to show things that would otherwise be visible only during game play.

In our case, it will allow us to see in the editor what kind of potion we instantiated. Without using the keyword tool, both types of potions would be shown in the editor with the same sprite.

We will see later that you can also decide which parts of the script execute in the editor, which in game, and which in both.

First, we need a variable to store the type of potion:

enum Potion { HEALTH, MANA }
export(Potion) var type = Potion.HEALTH

The first line declares an enumeration. Enums are a way of declaring a group of related constants. We have declared an enum called Potion, which contains two constants HEALTH and MANA, to which the values 0 and 1 are automatically assigned.

In the second line we declare the variable type, which has the constant Potion.HEALTH as default value. As we have already seen in the previous tutorials, the keyword export makes a variable visible in the Inspector. If after export, inside brackets, we put the name of an enum, this variable will be shown in the Inspector as a drop down list.

Save the scene as Potion.tscn in Entities/Potion.

If you instantiate a potion by dragging the Potion.tscn scene into the main scene, and then you try to change the type of potion by changing the type variable, you’ll see that the sprite of the potion does not change.

To change the sprite in the editor based on the type variable, we can check its value inside the _process() function. Since we used the keyword tool at the beginning of the script, the _process() function will be executed in the editor, so the sprite will change as soon as we change the value of Type in the Inspector.

So, let’s add the _process() function to the script:

func _process(delta):
	if Engine.editor_hint:
		if type == Potion.MANA:
			$Sprite.region_rect.position.x = 8
			$Sprite.region_rect.position.x = 0

The first line of the function checks if we are in the editor, using the editor_hint property of the Engine class. We want to execute this code only in the editor, because during the game the potion type does not change. So, it would be a waste of processing power to repeat the type check at each frame. If we are in the editor, we check the potion type and based on that, we choose the region of the image to use as the potion’s sprite.

Now, place a potion in main scene and move it near the player. Then, place a second potion and in Inspector change type to Mana. You’ll see the sprite change.

When running the game, we need to set potion’s sprite at the beginning. So, we must put the type check in the _ready() function:

func _ready():
	if type == Potion.MANA:
		$Sprite.region_rect.position.x = 8
		$Sprite.region_rect.position.x = 0

If the player walk over a potion, we want it to be picked up. We must therefore detect if the player has entered the potion area. To do so, we connect the body_entered() signal of Potion to itself. The function _on_Potion_body_entered() will be automatically added to the script. Add this code to the function:

func _on_Potion_body_entered(body):
	if == "Player":

First, the function checks if the body that entered the area is Player. If so, it does two things:

  • it calls Player‘s add_potion() function, which will update the player’s inventory and emits the signal to update the GUI (we will write this function shortly)
  • it removes the potion from the scene tree.

Since there is nothing else to do in the Potion scene, save it and return to the main scene.

Player script

Open Player script ( and at the beginning add this code to store player’s inventory:

# Player inventory
enum Potion { HEALTH, MANA }
var health_potions = 0
var mana_potions = 0

We simply declared two variables to store how many potions we have. We also copied here the enum used in, so we can use the same constants.

Now, we must add the add_potion() function to the script. We called this function in the Potion script when the player pick up the potion.

func add_potion(type):
	if type == Potion.HEALTH:
		health_potions = health_potions + 1
		mana_potions = mana_potions + 1
	emit_signal("player_stats_changed", self)

This function simply updates the inventory based on the type of potion picked up and then emits the signal to update the GUI.

Updating potions count in GUI

In the GUI tutorial, we had added the interface nodes to show the inventory, but we hadn’t written the code to update the values, so let’s do it now.

Attach a new script to the HealthPotions node (it’s located under CanvasLayer), call it and save it in the GUI folder.

Select Player and go to the Node panel to connect the player_stats_changed() signal to HealthPotions. The function _on_Player_player_stats_changed() is automatically added to the script. Replace the function with this code:

func _on_Player_player_stats_changed(var player):
	$Label.text = str(player.health_potions)

The function simply assigns the amount of potions currently present in the inventory to the text property of the Label node. To assign the value, it must first be converted to a string, using the str() function.

Repeat the procedure for the ManaPotions node. The function in this case will be:

func _on_Player_player_stats_changed(var player):
	$Label.text = str(player.mana_potions)

Run the game and try moving the player on the potions to see if they are removed from the scene and if the GUI is updated correctly.

Dropping a potion after enemy death

In RPGs, when an enemy dies, usually players get a loot meant to reward them for progressing in the game. In our game, when the player defeats a skeleton, there will be an 80% chance of getting a random potion.

To implement loot dropping, open the script. First of all, we need a reference to the Potion.tscn scene to be able to instantiate it when the skeleton die:

# Reference to potion scene
var potion_scene = preload("res://Entities/Potion/Potion.tscn")

In the _ready() function, we must add a call to the randomize() function to randomize the seed of the random number generator:

func _ready():
	player = get_tree().root.get_node("Root/Player")

In the hit() function, when the skeleton dies, we must randomly decide whether to instantiate a potion. Edit the function as follows:

func hit(damage):
	health -= damage
	if health > 0:
		... death code ...
		# 80% probability to drop a potion on death
		if rng.randf() <= 0.8:
			var potion = potion_scene.instance()
			potion.type = rng.randi() % 2
			get_tree().root.get_node("Root").call_deferred("add_child", potion)
			potion.position = position

Immediately after executing the existing death code, a random number between 0 and 1 is generated using the randf() function. If this number is less than or equal to 0.8 (80%), a new potion is instantiated.

Then, an integer random number is generated using the randi() function. With a modulo (%) 2 operation, you get the reminder of the division by 2 of this number. Since the rest can only be 0 or 1, one of the two types of potion is randomly chosen (0 for health, 1 for mana).

Lastly, the potion is added to the scene tree and is placed where the skeleton died.

Note for more advanced readers: we cannot directly call the add_child() function to add the potion, because it isn’t safe to add a new Area2D to the node tree inside a function that handles the on_area_entered() signal of another Area2D node. We must therefore use call_deferred() to defer the call to add_child() during idle time.

Drinking potions

The last thing left to do is handling the input to drink potions.

First, click on the Project → Project Settings menu to open the Project Settings window and go to the Input Map tab.

We need to create two actions:

  • drink_health, to which we map the Comma key (,) and the joypad button 0 (equivalent to the PlayStation Cross button and the XBox button A)
  • drink_mana, to which we map the Period key (.) and the joypad button 1 (equivalent to the PlayStation Circle button and the XBox button B)

If you don’t remember how to create input actions, take a look at the Player Movement tutorial.

Close the window and go to the _input() function in Player‘s script. Add to the function the code to handle this two new actions:

elif event.is_action_pressed("drink_health"):
	if health_potions > 0:
		health_potions = health_potions - 1
		health = min(health + 50, health_max)
		emit_signal("player_stats_changed", self)
elif event.is_action_pressed("drink_mana"):
	if mana_potions > 0:
		mana_potions = mana_potions - 1
		mana = min(mana + 50, mana_max)
		emit_signal("player_stats_changed", self)

When the input action is detected, the function checks if the player has any potions left to use. If so, the quantity of the potion is reduced by 1 and 50 points are added to the corresponding stat (the min() function is used to not exceed the maximum value). Finally, it emits the player_stats_changed() signal to update the GUI.

Run the game and, when you have lost health points or consumed your mana, try to drink a potion to recover some points.


In this tutorial we have seen how to create objects that can be collected and used by the player. Furthermore, we learned how to use the tool keyword to add interactivity to the editor, and we expanded our knowledge of GDScript a little bit.

If you want to try the game, click on the button below:

In the next tutorial, we will add another fundamental piece of RPGs: Experience Points and Level advancement.



Jorge · December 15, 2019 at 3:43 pm

Hello, please finish this rpg Godot series! I’m starting with this engine and these tutorials are being really helpful to me. Thank so much for your work


    Davide Pesce · December 15, 2019 at 9:20 pm

    Thanks Jorge, I’m glad you found these tutorials useful! If everything goes as planned, by the end of January I should finish the series.


Akilen · December 16, 2019 at 9:46 pm

Hi David

Thanks for series. It gave me a lot of idea for my game


    Davide Pesce · December 17, 2019 at 8:58 am

    Happy to hear that!


Novu · February 2, 2020 at 11:37 am

Thanks for the tutorials, very helpful!

One small note, don’t know if it’s because I have not followed correctly all the previous lessons, but I think that when you say “In the _ready() function, we must add a call to the randomize()…” you show the source, and it should be in Also, already has a random number generator ready (rng), so this step is not needed.


    Davide Pesce · February 2, 2020 at 10:33 pm

    You’re right, I accidentally copied the code. Thanks for the feedback!

    Calling the randomize function of RandomNumberGenerator is still required in to initialize the generator with a random seed.


Anastasia · March 20, 2020 at 5:58 pm

I wanted to ask
the lines:
func _ready():
>if type == Potion.MANA:
> > >$Sprite.region_rect.position.x = 8
> >else:
> > >$Sprite.region_rect.position.x = 0

arent they supposed to be

func _ready():
>if type == Potion.MANA:
> >$Sprite.region_rect.position.x = 8
> >$Sprite.region_rect.position.x = 0

if not why?
Thank you for your time and keep up the great work!


    Davide Pesce · March 20, 2020 at 6:45 pm

    Yes, probably there was a problem copying and pasting the code. I corrected it, thanks for the report!


Supertramp · June 3, 2020 at 6:41 pm

Hey Davide,

Really enjoying the tutorial. It was recommended to me on a discord server (heartbeast). Loving the content. I wanted to ask, I found that you use `get_tree().queue_delete(self)` to remove scenes. I noticed I can achieve the same effect using `queue_free()`. I was wondering if there was a major difference between the two, or are you just more comfortable with the former.

Thanks! Again, love your content.


    Davide Pesce · June 3, 2020 at 11:11 pm

    Hi Supertramp, I’m glad you like the tutorials!
    About your question: there is no difference, internally queue_free() calls queue_delete(), so the two methods are equivalent.


Siba2005 · September 5, 2020 at 2:58 am

Hi David!
Thanks for this tutorial

I suggest adding to the verification code:
if health_potions> 0 and health 0 and mana <mana_max:
This will allow you not to spend the potion with full supplies, as in the excitement of the game you drink a potion without control.


    Siba2005 · September 6, 2020 at 9:05 am

    elif event.is_action_pressed(“drink_health”):
    if health_potions > 0 and health 0 and mana < mana_max:
    mana_potions = mana_potions – 1
    mana = min(mana + 50, mana_max)
    emit_signal("player_stats_changed", self)
    # Play sound


Beni · May 25, 2022 at 11:09 pm

Awesome Series.

Instead of potions I cast hearts wich directly fill up health, and big_quarter_hearts, where health gets one whole hearth more.

Here’s my german tutorial to a heartbar like in the Zelda Series (i mentioned it earlier but don’t know if it’s posted):

Thank you very much for this awesome tutorials, I learned alot from them.

Comments are closed.

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.