Introduction

Welcome, everyone! Even after all the years games have existed, RPGs are still one of the most popular genres that aspiring developers want to learn how to make. However, given how feature-rich RPGs can be, it can be hard to know where to start. Thankfully, though, with Godot and some programming knowledge at hand, you will have all the tools you need to start on your very own dream RPG!

In this RPG tutorial series, we’ll be creating a 2D RPG inside of the Godot game engine together. It will feature a top-down player controller, enemies, combat, loot, and even a leveling system. For Part 1, though, we will be covering a number of new systems inside of Godot as we start to build our RPG, including tilemaps, sprite animations, and raycasting. This course will require a basic understanding of the Godot game engine and the GDScript scripting language. If you’re new to Godot, you can view our introductory tutorial here.

Otherwise, though, we hope you strap in and are ready to learn how to make 2D RPGs with Godot.

If you’d like to jump straight in making enemies and loot, check out Part 2 instead!

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 .

Don't miss out! Offer ends in Access all 200+ courses

Access all 200+ courses New courses added monthly

New courses added monthly Cancel anytime

Cancel anytime Certificates of completion ACCESS NOW

Setting Up The Project

To begin, let’s create a new Godot project. Inside of the editor, let’s then import the assets we’ll be needing.

Once we have the assets, let’s create our first scene by selecting 2D Scene in the scene panel. Rename this node to MainScene, then save the scene to the file system.

Creating the Player

Now that we have the main scene, let’s create a new scene (Scene > New Scene) of type KinematicBody2D.

Rename the node to Player

Save the scene to the file system

As a child of this node, we want to…

Drag in the player_s_0.png sprite in to create a new sprite node

sprite in to create a new sprite node Set the position to 0, 0

Create a child node of type CollisionShape2D

Set the Shape to Capsule

to Capsule Resize it to fit the sprite

Right now, we’re using just a sprite node to allow us to see the player. Later on, we’ll be replacing this with an AnimatedSprite node.

Finally, for our player, let’s create a RayCast2D node. You’ll see that in the scene view, an arrow will appear to come out of the player. This is a raycast, which is a concept in game development that is used in many different cases. You give a raycast an origin position and a direction. Then it will shoot a point from that origin in the given direction. If that point ever hits a collider it will return that object and some other information.

In our case, the raycast has a max distance and this will be used to detect interactable objects and enemies.

Make sure to enable Enabled

Scripting the Player Movement

Now that we have the player scene, let’s create a new script attached to the kinematic body node called Player. We can start with our variables.

var curHp : int = 10 var maxHp : int = 10 var moveSpeed : int = 250 var damage : int = 1 var gold : int = 0 var curLevel : int = 0 var curXp : int = 0 var xpToNextLevel : int = 50 var xpToLevelIncreaseRate : float = 1.2 var interactDist : int = 70 var vel = Vector2() var facingDir = Vector2() onready var rayCast = $RayCast2D 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var curHp : int = 10 var maxHp : int = 10 var moveSpeed : int = 250 var damage : int = 1 var gold : int = 0 var curLevel : int = 0 var curXp : int = 0 var xpToNextLevel : int = 50 var xpToLevelIncreaseRate : float = 1.2 var interactDist : int = 70 var vel = Vector2 ( ) var facingDir = Vector2 ( ) onready var rayCast = $ RayCast2D

Later on, we’ll be adding in a couple more variables, but for most of our systems, this will do. Now, in order to move the player around, we’ll need to know which buttons do what. Go to the Project Settings window (Project > Project Settings…) and click on the Input Map tab. Here, we want to create 5 new actions and assign a keyboard key to each of them.

move_left – left arrow key

move_right – right arrow key

move_up – up arrow key

move_down – down arrow key

interact – space

Back in the script, we can create the _physics_process function. A built-in function which runs at 60 FPS (good for physics). What we want to do here, is a few things.

Reset vel (vector containing our velocity) Detect the 4 direction keys to change the velocity and facing direction (used for animations later) Normalize the velocity vector to prevent faster diagonal movement Move the player based on the velocity using the KinematicBody2D node function

func _physics_process (delta): vel = Vector2() # inputs if Input.is_action_pressed("move_up"): vel.y -= 1 facingDir = Vector2(0, -1) if Input.is_action_pressed("move_down"): vel.y += 1 facingDir = Vector2(0, 1) if Input.is_action_pressed("move_left"): vel.x -= 1 facingDir = Vector2(-1, 0) if Input.is_action_pressed("move_right"): vel.x += 1 facingDir = Vector2(1, 0) # normalize the velocity to prevent faster diagonal movement vel = vel.normalized() # move the player move_and_slide(vel * moveSpeed, Vector2.ZERO) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func _physics_process ( delta ) : vel = Vector2 ( ) # inputs if Input . is_action_pressed ( "move_up" ) : vel . y -= 1 facingDir = Vector2 ( 0 , - 1 ) if Input . is_action_pressed ( "move_down" ) : vel . y += 1 facingDir = Vector2 ( 0 , 1 ) if Input . is_action_pressed ( "move_left" ) : vel . x -= 1 facingDir = Vector2 ( - 1 , 0 ) if Input . is_action_pressed ( "move_right" ) : vel . x += 1 facingDir = Vector2 ( 1 , 0 ) # normalize the velocity to prevent faster diagonal movement vel = vel . normalized ( ) # move the player move_and_slide ( vel * moveSpeed , Vector2 . ZERO )

Let’s hop over to the MainScene and test the movement out. In the file system, drag the player scene into the scene window to create a new instance of it. Then click the Play button, choose the main scene to be the base scene and see if the movement works.

Animating the Player

You’ll see that we can move around, but it’s pretty bland. Let’s now implement sprite animations. In the Player scene, delete the Sprite node and replace it with an AnimatedSprite node.

In the inspector, create a new sprite frames resource. The SpriteFrames window will then pop-up.

In the animations list there will be an animation called default. Double click on it and rename it to IdleDown. For this animation, we’re going to drag in the player_s_0 sprite.

From here, we can create the rest of the animations. These will be:

IdleDown (already created)

IdleUp

IdleLeft

IdleRight

MoveDown

MoveUp

MoveLeft

MoveRight

Here’s what the MoveDown animation will look like. Click on the New Animation button to create a new animation.

Go through now and create all of the idle and move animations. Once that’s done, select the AnimatedSprite node and…

Set the Animation to IdleDown

to IdleDown Enable Playing

Back in the Player script, let’s create a new variable to reference the animated sprite node.

onready var anim = $AnimatedSprite 1 onready var anim = $ AnimatedSprite

Then we can create the manage_animations function which will check the velocity and facing directions to determine which animation to play.

func manage_animations (): if vel.x > 0: play_animation("MoveRight") elif vel.x < 0: play_animation("MoveLeft") elif vel.y < 0: play_animation("MoveUp") elif vel.y > 0: play_animation("MoveDown") elif facingDir.x == 1: play_animation("IdleRight") elif facingDir.x == -1: play_animation("IdleLeft") elif facingDir.y == -1: play_animation("IdleUp") elif facingDir.y == 1: play_animation("IdleDown") 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func manage_animations ( ) : if vel . x > 0 : play_animation ( "MoveRight" ) elif vel . x < 0 : play_animation ( "MoveLeft" ) elif vel . y < 0 : play_animation ( "MoveUp" ) elif vel . y > 0 : play_animation ( "MoveDown" ) elif facingDir . x == 1 : play_animation ( "IdleRight" ) elif facingDir . x == - 1 : play_animation ( "IdleLeft" ) elif facingDir . y == - 1 : play_animation ( "IdleUp" ) elif facingDir . y == 1 : play_animation ( "IdleDown" )

The play_animation function will take in an animation name and play that.

func play_animation (anim_name): if anim.animation != anim_name: anim.play(anim_name) 1 2 3 4 func play_animation ( anim_name ) : if anim . animation ! = anim_name : anim . play ( anim_name )

We can call the manage_animations function every frame at the end of the _physics_process function.

manage_animations() 1 manage_animations ( )

Now when we press play, you should see that the animations play when we move around.

Creating the Tilemap

Now that we’ve got a player who can walk around, let’s create our world.

In the MainScene, create a new child node of type TileMap. Then in the inspector, create a new tileset.

These tile sets require tiles, so to do this, we need to create a new scene with all the available tiles in it.

Once all the tiles are in the new scene, go to Scene > Convert To… > Tile Set and save it as tileset.tres.

Back in the MainScene, select the TileMap node and in the inspector, select the tileset property and choose load. Select the resource we just made and the tiles should appear in the panel to the right.

We can now select a tile and paint it in the scene view. Here are some keys for building your tilemap:

Place tile – Left Mouse

Delete tile – Right Mouse

Draw line – Shift + Left Mouse

Draw rectangle – Shift + Ctrl + Left Mouse

Delete line and delete rectangle are the same but with right mouse

Now if we try to place a tree, you’ll see that it replaces the tile rather than placing it on top. To fix this, we can create a second tilemap.

Call the existing tilemap TileMap_Ground

Create a new tilemap called TileMap_Above

Set that tileset to the same tileset

We can now draw tiles on-top of the existing tilemap

Something you might also want is a collision with some tiles. To implement this, select the tilemap in the file system and the TileSet panel should pop up.

Select a tile

Click on the sprite that pops up (a bunch of settings should then appear)

Select Collision

Select the Rectangle tool

tool Draw the collider on the sprite

Let’s now create a larger scene with our two tilemaps.

Continued in Part 2

With the tilemap created and player set up, we now have the basis for a spectacular 2D RPG in Godot! Over the course of part 1, we’ve covered not only setting up sprite animations for your player character in Godot, but also setting up raycasts and tilemaps with appropriate collisions. Fortunately, with just these foundations, you can expand into other games as well, or create even more complicated RPG maps.

Nevertheless, our RPG certainly isn’t complete! In Part 2, we’ll cover the rest of our RPG creation process with enemies, loot, UI systems, and more! Hope to see you then!