Making a Grid Inventory System with Godot Part 2 — Pickup System
It has been some time since I first published a tutorial on how to make a grid inventory system with Godot which you can follow it here.
In this tutorial, we build on top of that to create a system where the player can pick up item in the game world and add it into the inventory.
Table of Contents
- How Collision Detection Works in Godot
- Setup Physics Layer
- Add New Item into Inventory
- Create the Player Scene
- Create the Item Scene
- Instance Item from the Script
- Debug for Collision Shape
Prerequisite
This tutorial assumes a basic knowledge with the Godot engine. You should have some familiarity with the Godot Editor, Node and Scene.
Important Note: I wrote and tested the code in this tutorial with Godot 3. If you use newer version of Godot, some code may need some tweak in order to work correctly.
Getting Started
To begin with, download the project here. Unzip the files, launch Godot and import the project.
How Collision Detection Works in Godot
Before beginning the tutorial, let’s take a look at how collision detection work in Godot.
Very often in a game, we need to know if two objects bump into each other i.e. collision detection. Godot provides us with four nodes that all extends from CollisionObject2D to do this:
1. StaticBody2D
- It is a physics body, a stationary one that is not moved by the physics engine when the object bumps into it. As such, a solid tree or rock in the game world can be a StaticBody2D node i.e. If the player bumps into it, the tree or rock that collide with the player will not move, is static.
2. RigidBody2D
- This is the physics body that simulate 2D physics. With RigidBody2D node, we can set its mass, velocity, friction, etc. When objects bump into it, the physics engine can calculate how the body will react based on its properties. For example, a box that can be pushed by the player, or a ball that bounces off from the player.
3. KinematicBody2D
- A KinematicBody2D node is also a physics body, but it is meant to be user-controlled, with useful API to do movement, collision tests, etc. We must define in the code how the body moves or acts in different circumstances. For example, a player’s movement that is controlled with arrow keys, or an enemy who charges towards nearby player.
4. Area2D
- Lastly, we have Area2D node that is simply used for collision detection, no physics. For example, an item’s object that checks if the player intersects with it to pick it up, or a portal that does teleporting when the player enters.
When collision happens, the node emits a signal that we can connect to call a function to do stuff. When a PhysicsBody2D enters or exits the node, the node emits signal body_entered and body_exited respectively. When an Area2D enters or exits the node, the node emits signal area_entered and area_exited respectively.
Apart from that, we must also attach at least one CollisionShape2D node as a child to these nodes. This is the way we define the object’s collision boundary. The collision shape can be one of the Shape2D in Godot: A circle, rectangle, oval, etc.
Next, let’s examine how we can use Physics Layer for collision detection. This is the way we tell the collision engine how CollisionObject2D objects interact with each other in the game world. For example, we can setup an object A to interact with object B, but not with object C etc. with Physics Layer.
CollisionObject2D has two collision properties that we can set for an object to particular Physics Layer:
- Collision Layer: We set which Physics Layer the object belongs to.
- Collision Mask: We set the Physics Layer of all the other objects in the game world that we want this object to interact with.
Understanding this, let’s consider a scenario: Say we have a player that can move around to collect coins spawned in the world. Meanwhile, the player needs to avoid bumping into the enemy along the way.
In this case, we have three nodes: Player, Enemy and Coin. We can use three layers of Physics Layer: Player, Enemy and Coin. Then we assign the collision layer and mask of the node:
- The player can collect coin spawned in the world. Also, the player can be hurt by the enemy. As such, we set its collision layer to Player and mask to both Enemy and Coin.
- The enemy can hurt the player. Therefore, we set its collision layer to Enemy and mask to Player.
- The coin can be collected by the player only. We can set its collision layer to Coin and mask to Player.
I hope you get an idea how Physics Layer work in Godot. Next up, let’s see how we can setup Physics Layer for our system.
Setup Physics Layer
In our case, we have a player that can pick up item in the game world. We can use two layers of Physics Layer: Player and Item.
To setup physics layer, in the Godot’s Main Menu at the top, go to Project -> Project Settings, then under General tab, look for Layer Names -> 2D Physics on the left sidebar. Rename Layer 1 and Layer 2 to Player and Item respectively.
Note: It is optional to give the Physics Layer a name here. Things still work if we leave it empty. However, if we put a name, it provides good hint when we set the collision layer and mask later on.
Add New Item into Inventory
Once the player picks up the item’s object in the game world, we need to add it into the inventory. Let’s open res://inventory.gd, add two new functions:
The function find_empty_slot search the items array for an empty slot and return the slot’s index if found.
The function set_item is the one that we call to add the item into the inventory. It takes an argument the item’s data (Dictionary) to add into the inventory. The logic is straightforward: First, we clone the item’s data. If the item can stack, we check if we can add it to an existing slot with the same item. Else, we add the item to an empty slot. If the item can be successfully added into the inventory, we return OK.
Sometimes, the bag is already full, and the item cannot be added into the inventory anymore, we return FAILED.
Create the Player Scene
To better organize all of our scenes, create a new actors folder in the root directory. We will group scenes that we create later in this folder.
Let’s start to create the player scene. The player needs to detect collision, and we want to control the player’s movement with arrow keys, a KinematicBody2D node is well suited here.
Create a new scene, add a KinematicBody2D node and rename it to Player. Add a Sprite child node to the Player scene, then in the Inspector, drag and drop icon.png (Godot icon) to its Texture property, also scale it down to 0.5.
We know that a KinematicBody2D node needs to have a user-defined collision shape. This is why we get a warning icon at the top right corner of the Player scene now. Let’s add a CollisionShape2D child node to the Player scene: In the Inspector, head over to its Shape property, select from options New CircleShape2D, then set the Circle’s Radius property to 24. Save the scene as player.tscn in the actors/player folder. Up to this point, your Player scene should look like this:
Let’s also setup the collision layer and mask for the player. Click to highlight KinematicBody2D root node in the Player scene, in the Inspector, look for properties CollisionObject2D -> Collision, set the collision Layer to Layer 1 (Player) and Mask to Layer 2 (Item).
Now let’s add a script to move the player:
If you need help with the movement code, can check out Godot documentation in detail here.
To see if it is working, let’s add Player scene into Main scene. Open res://main.tscn, instance Player scene as a child. Place the player in the center of the screen by setting its Position property to 160 and 90 for x and y position values. Your Main scene looks like this now:
Run the project (F5) and verify that we can move the player around with arrow keys.
Create the Item Scene
There is no need to create a scene for each item’s object in the game world. Instead, we can create a base item’s object scene, that we can reuse and instance as many copies as we need in the game world.
Since an item’s object only needs to detect if the player intersects with it, we can use a simple Area2D node here.
Create a new scene, add an Area2D node and rename it to Item. Add two child nodes: Sprite and CollisionShape2D to the Item scene. To configure CollisionShape2D node: In the Inspector, head over to its Shape property, select from options New CircleShape2D, then set the Circle’s Radius property to 8. Save the scene as item.tscn in the actors/item folder. Up to this point, your Item scene should look like this:
To configure the collision properties, click to highlight Area2D root node in the Item scene, in the Inspector, look for properties CollisionObject2D -> Collision, set the collision Layer to Layer 2 (Item) and Mask to Layer 1 (Player).
Let’s add a script to Item scene:
We export a variable key to allow us to set a String to get the item’s data later on. In the _ready function, we call get_item_by_key function from the Global autoload script to get the item’s data. Then, we update the Sprite’s texture to show the item’s icon.
Now we can connect the signal to detect collision. Our player is a physics body, we can connect the item’s object to the signal body_entered that will fire whenever the player intersects with the item’s object in the game world.
To do this, click to highlight Area2D root node in the Item scene, in the Node tab besides the Inspector, look for then right click on the signal body_entered(body: Node), and choose Connect…. When prompt, click Connect button in the window.
Back to the item_pickup.gd script, let’s update the function on_Item_body_entered:
Here, we get the item’s data by calling function get_item_by_key from the Global autoload script, then we add the item into the inventory. Only if the item has successfully added into the inventory, we can remove the node by calling the function queue_free.
Now let’s instance some items in the game world. Open res://main.tscn, instance three Item scenes as its children. In the Inspector, set the Key property to sword, apple and potion respectively. You can reposition them randomly in the screen. Your Main scene looks like this now:
Run the project (F5), move the player around to see if you can pick the items up. Also verify that the item can be added into the inventory.
Instance Item from the Script
In this section, let’s see how we can spawn item in the game world with script. Open res://main.gd script, update the _ready function:
First, we preload the Item scene by passing its file path to function preload and save it in a variable ItemPackedScene for reference.
In the _ready function, we have new lines 10–13. First, we create a new item instance by calling ItemPackedScene.instance(), then we assign it a key value and reposition it in the screen. Finally, we add it as a child to the Main scene.
If we want to drop an item in the game world, for example, after the player defeats an enemy, this is how we can do it.
Save the script and run the project (F5) now. We can see an armor item is being spawned in the world when the game starts.
Debug for Collision Shape
When debug, it is useful that we can see the collision shape around the object. By default, this option is not turned on so we can’t really see it in the first place.
To enable it, in the Godot’s Main Menu at the top, go to Debug -> Visible Collision Shapes, tick the checkbox to turn it on. Run the project (F5) and you’ll see colored collision shapes shown around the object now.
Finishing Up
Thank you for reading this! I hope that you enjoy this tutorial and learn a thing or two here. If you have any question, feel free to leave a comment. Until next time!
