How to Create an RPG in Godot – Part 2

Introduction

Welcome back everyone!  It’s time to finish our 2D RPG in Godot!

For Part 1 of this tutorial series, we started setting up our 2D RPG project in the open-source and free Godot game engine.  Some of the things we learned about include: tilemaps, player controllers, raycasting, sprite animations, and more!  All in all, we have a great base to work with and expand on!

However, what’s an RPG without some enemies or items to loot?  In this second part of our 2D RPG tutorial for Godot, we’ll finish by adding these elements, as well as adding the ability for our camera to follow the player.

So sit back, and prepare yourself to finish your first RPG in Godot!

Project Files

For this project, we’ll be needing some assets such as sprites and a font. These will be sourced from kenney.nl and Google Fonts. You can, of course, choose to use your own assets, but for this course we’ll be using these.

  • Download the sprite and font assets here.
  • Download the complete Godot project here.

Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it!

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.

Camera Follow

With our big scene, there’s one problem which is the fact that the camera doesn’t move. To fix this, let’s create a new Camera2D node in the MainScene.

Camera2D node added to Godot RPG project

Then we can create a new script attached to the camera node. Call it CameraFollow. All we’re going to do here is find the player and move towards them every frame.

onready var target = get_node("/root/MainScene/Player")

func _process (delta):
    position = target.position

Finally, in the inspector make sure to enable Current so that the camera will be the one we look through.

GOdot Inspector with Current checked for Camera2D

Now if we press play, you’ll see that the camera follows the player.

Creating an Enemy

Let’s now create an enemy scene. These will chase after the player and attack them when in range.

  1. Create a new scene with a root node of KinematicBody2D
  2. Rename it to Enemy and save the scene
  3. Drag in the player_s_0.png sprite to create a sprite node
  4. Set the Modulate to red
  5. Create a CollisionShape2D node with a capsule shape and resize it
  6. Create a Timer node – this will be used for checking for when we can attack

Enemy node creation for Godot RPG

Scripting the Enemy

On the enemy node, create a new script called Enemy. We can start with the variables.

var curHp : int = 5
var maxHp : int = 5

var moveSpeed : int = 150
var xpToGive : int = 30

var damage : int = 1
var attackRate : float = 1.0
var attackDist : int = 80
var chaseDist : int = 400

onready var timer = $Timer
onready var target = get_node("/root/MainScene/Player")

Inside of the _physics_process function (gets called 60 times per second) we want to move towards the target if we’re further than the attack distance but closer than the chase distance.

func _physics_process (delta):

    var dist = position.distance_to(target.position)

    if dist > attackDist and dist < chaseDist:
        var vel = (target.position - position).normalized()

        move_and_slide(vel * moveSpeed)

Back in the MainScene let’s drag in the Enemy scene to create a new instance. Now we can press play and test it out. The enemy should move towards us.

Enemy Node added to MainScene in Godot

Now that the enemy moves towards us, let’s have it attack us.

  1. Go to the Enemy scene
  2. Select the Timer node
  3. In the Inspector panel, click on the Node tab
  4. Double click on the timeout() signal
  5. Click Connect

Godot Timer's Connect a Signal to a Method window

This will create the _on_Timer_timeout function. Here, we want to check if we’re within the attack distance and then attack the target. We’ll be creating the take_damage function on the player soon.

func _on_Timer_timeout ():

    if position.distance_to(target.position) <= attackDist:
        target.take_damage(damage)

In the _ready function which gets called once the node is initialized, let’s setup the timer node.

func _ready ():

    timer.wait_time = attackRate
    timer.start()

Finally for the enemy, let’s create the take_damage and die functions. The target.give_xp function will be called over on the player script later on.

func take_damage (dmgToTake):

    curHp -= dmgToTake

    if curHp <= 0:
        die()

func die ():

    target.give_xp(xpToGive)
    queue_free()

Player Functions

Now we want to go over to the Player script and add in some new functions to work with the enemies. Let’s start with the give_gold function which will add gold to the player.

func give_gold (amount):

    gold += amount

For the levelling system, let’s create the give_xp and level_up functions.

func give_xp (amount):

    curXp += amount

    if curXp >= xpToNextLevel:
        level_up()

func level_up ():

    var overflowXp = curXp - xpToNextLevel

    xpToNextLevel *= xpToLevelIncreaseRate
    curXp = overflowXp
    curLevel += 1

The take_damage and die functions will be called when an enemy attacks us.

func take_damage (dmgToTake):

    curHp -= dmgToTake

    if curHp <= 0:
        die()

func die ():

    get_tree().reload_current_scene()

Now if we press play, you’ll see that the enemy can attack and kill us – which will reload the scene.

Player Interaction

With our player, we want the ability to interact with chests and enemies. In the Player script, let’s create the _process function (called every frame) and check for when we’re pressing the interact button.

func _process (delta):

    if Input.is_action_just_pressed("interact"):
        try_interact()

The try_interact function will check to see if the raycast is hitting anything. If it’s an enemy, damage them but if it’s not – call the on_interact function if they have it.

func try_interact ():

    rayCast.cast_to = facingDir * interactDist

    if rayCast.is_colliding():
        if rayCast.get_collider() is KinematicBody2D:
            rayCast.get_collider().take_damage(damage)
        elif rayCast.get_collider().has_method("on_interact"):
            rayCast.get_collider().on_interact(self)

Chest Interactable

Now we need something to interact with. This is going to be a chest which will give the player some gold. Create a new scene with a root node of Area2D.

  1. Rename the node to Chest
  2. Save the node
  3. Drag in the rpgTile163.png to create a Sprite node
  4. Create a CollisionShape2D node
  5. Set the shape to a Rectangle and resize it to fit the sprite

Chest node object creation in Godot

Next, create a new script called Chest attached to the Area2D node. All we’re going to do here, is have the on_interact function which gives the player gold then destroys the node.

export var goldToGive : int = 5

func on_interact (player):

    player.give_gold(goldToGive)
    queue_free()

You might notice that interacting with the chest wont work but attacking the enemy does work. So in the Player scene, select the ray cast node and enable Collide With Areas.

Godot Inspector with Areas checked for RayCast 2D

Creating the UI

Create a new scene with a root node of Control.

  1. Rename the node to UI
  2. Save the scene
  3. Create a new TextureRect node and call it BG
  4. Set the Texure to UI_Square.png
  5. Enable Expand
  6. Drag the 4 anchor points down to the bottom center
  7. Set the Position to 386, 520
  8. Set the Size to 250, 60
  9. Set the Visibility > Self Modulate to dark grey

Godot UI Canvas with rectangle

As a child of this BG node, we want to create three new nodes.

  • LevelBG – TextureRect
  • HealthBar – TextureProgress
  • XpBar – TextureProgress

For the TextureProgress nodes…

  1. Enable Nine Patch Stretch
  2. Set the Under and Progress textures to UI_Square.png
  3. Set the Under tint to dark grey
  4. Set the Progress tint to red or green

Godot UI with health bar selected

Now we need to setup our fonts. With the assets we imported into the project, we have two font files. Right now, these are just .ttf files which need to be converted in a format that Godot can read. For each font file…

  1. Right click it and select New Resource…
  2. Select the DynamicFont resource
  3. Save this to the Font folder
  4. Double click on the new resource and drag the .ttf file into the Font > Font Data property

Godot FileSystem window with Roboto fonts circled

As a child of the LevelBG node, create a new Label node and rename it to LevelText.

  • Resize the rect box to fit the level BG square
  • Set Align and Valign to Center
  • Drag the bold dynamic font resource into the Custom Fonts property
  • Set the font Size to 35
  • Set the Visibility > Self Modulate to grey

Godot UI with Text for gold amount added

Create a new Label node called GoldText.

  1. Move and resize it to look like below
  2. Set Align to Center
  3. Set the Custom Font to the regular dynamic font resource

Scripting the UI

Next up, let’s create a new script attached to the UI node, called UI. We’ll start with the variables to reference the children nodes.

onready var levelText : Label = get_node("BG/LevelBG/LevelText")
onready var healthBar : TextureProgress = get_node("BG/HealthBar")
onready var xpBar : TextureProgress = get_node("BG/XpBar")
onready var goldText : Label = get_node("BG/GoldText")

Then we have our functions which update each of these nodes.

# updates the level text Label node
func update_level_text (level):

    levelText.text = str(level)

# updates the health bar TextureProgress value
func update_health_bar (curHp, maxHp):

    healthBar.value = (100 / maxHp) * curHp

# updates the xp bar TextureProgress value
func update_xp_bar (curXp, xpToNextLevel):

    xpBar.value = (100 / xpToNextLevel) * curXp

# updates the gold text Label node
func update_gold_text (gold):

    goldText.text = "Gold: " + str(gold)

Now that we have the UI script, let’s go over to the MainScene and drag the UI scene in. To make it render to the screen, we need to create a CanvasLayer node and make UI a child of it.

Godot Scene window with Canvas Layer circled

In order to be able to access the UI script for the player, we need to move the canvas layer up to the top of the hierarchy.

Godot CanvasLayer added to MainScene node

Over in the Player script, let’s create a variable to reference the UI script.

onready var ui = get_node("/root/MainScene/CanvasLayer/UI")

Create the _ready function which gets called once the node is initialized. In there, we’ll be initializing the UI.

func _ready ():

    ui.update_level_text(curLevel)
    ui.update_health_bar(curHp, maxHp)
    ui.update_xp_bar(curXp, xpToNextLevel)
    ui.update_gold_text(gold)

Now we need to go through a few functions and call the UI functions at the end of them. They go as following:

# give_gold function
ui.update_gold_text(gold)

# give_xp function
ui.update_xp_bar(curXp, xpToNextLevel)

# level_up function
ui.update_level_text(curLevel)
ui.update_xp_bar(curXp, xpToNextLevel)

# take_damage function
ui.update_health_bar(curHp, maxHp)

We can now press play and see that the UI is set at the start of the game and changes as we get gold, take damage, get xp and level up.

With all of this, we’re finished with our RPG game in Godot. Let’s now go back to the MainScene and place some enemies and chests around the place.

Godot 2D RPG with UI added to scene

Conclusion

Congratulations on completing the tutorial! Through perseverance, hard work, and a little bit of know-how, we just created a 2D RPG in Godot with a number of different features.  Over the course of Part 1 and Part 2, we’ve shown you how to set up:

  • A top-down 2D player controller
  • Enemies who can attack and chase the player
  • Chests we can interact with to get gold
  • A UI to show our health, XP, level, and gold

And with that, you should now possess the fundamental skills to create even more RPGs in the future!  Of course, you can expand upon the game created here – adding in new features, fleshing out current ones, or even just improving certain aspects you felt were lacking!  There are endless directions to go from here, but with this solid foundation, you’ll have quite the headstart!

Thank you very much for following along, and I wish you the best of luck with your future games.

ezgif 3 8c32af3c74f4