Your First Godot Game: a Live Tutorial

Bruno Guedes
18 min readMar 13, 2019

--

Introduction

This is a “live” tutorial in the sense that I’m doing it as I write. The intention is to try and follow the development process as it feels, and detect possible points of difficulty and lack of clarity. Also, to not forget to document it as I go.

Some things are done in advance, though. The basic index of this piece, for example, and the assets. All else is basically improvised and condensed into a coherent and informative summary of what I’m doing, and the final result shall be very similar to what I also have produced. This should be nice.

So, you’ve decided to learn Godot! That’s wonderful! It’s a very cool engine with opensource code and supports every major OS marvelously (which means it has the best parts of both Unreal and Unity; it’s unreal!), but I don’t think I need to call upon the technical and philosophical marvels of the engine to justify the urge to learn to use it. The simple act of learning a new engine is stimulating and enriching enough! Excellent! Now where to start from?

Well, from my own experience, when I decided to learn Unity, I did so in a very simple manner: by challenging myself to make one game per month in Unity! In a similar manner, when I decided to learn LÖVE2D (and, by extension, Lua itself), I challenged myself in a similar manner (but only managed to get 4 games done before I had to let go of the tight schedule).

The point is: you learn programming by making programs. You can do all the preliminary reading and studying you wish, but you’ll probably only really learn and commit to it when you get doing it. So this tutorial has this very simple goal: to learn Godot by making a game in it.

Here are a few things this tutorial will do:

  • Teach Godot by making, step by step, a full game
  • Show, by example, some useful Godot concepts and design philosophies
  • Give you a sense of accomplishment for having made a game

Here are some things this tutorial won’t do:

  • Teach advanced concepts of Godot
  • End with a polished product ready to be sold as a new Triple-I masterpiece

Expectations curbed? Good. Let’s begin learning!

Game Design

First of all, let’s have guidelines. This is important because for two reasons:

  1. it’ll give us a clear idea of when the game is done.
  2. it’ll stop us from adding features and never finishing the game.

Nothing fancy, nothing too structured.

The Pitch

The final delivery of this project will be… Pong, but one-player. Which means it’s “Squash Pong”.

Features

  • The game happens on a 16:9 window
  • The player controls a paddle that goes up and down on the screen
  • A ball travels uniformly, bouncing off the top and bottom walls, the right wall and the player
  • Each time the ball hits the player, one point is added to the score
  • When the ball hits the left wall, it disappears and the score is reset
  • A high score holder keeps track of the maximum score the player has achieved without letting the ball pass through the left wall
  • When the ball hits the player or the top and bottom walls, it bounces normally.
  • When the ball hits the right wall, it increases in speed.
  • Pressing the ESC key quits the game

There we go. There are some requisites that could be added to this list, but they either were left out to keep the scope manageable, or we’re going to come back to them later on. Be patient.

With this list of goals to attend, we can start working with purpose. Let’s get started!

Starting the Project

…in the very beginning. Open Godot, and you will be greeted with a window like this:

Nevermind the fact that I have a few unfinished projects going on. Click the New Project button there and let’s set up the project somewhere. I’m calling mine “ProtoPong”. :V

Now that we’re in the Editor, you’ll want to set Godot to 2D mode. This will make things so much easier, trust me.

Setting up the Canvas

One thing we can take care of right now is the size of our screen. Go into the Project menu, click Project Settings and find the Display > Window category. The first properties we’ll edit are Width and Height. To create a space that adheres to our ideal of 16:9 aspect, let’s make it 640 by 360.

And now, believe it or not, we can test it! Click the play button on the top right corner and… surprise! It wants you to save this scene so you can set it as your starting scene! Call it whatever (Game.tscn is a good name) and now you have… this magnificent expanse of gray!

Oh, hi there!

Close it when you start to feel overwhelmed by all the nothing going on and let’s move on!

Building the Scene

The Scene tab (and one pesky tooltip)

Now you have the Scene Editor open and it’s time to make a scene! And here’s a cool thing about Godot: it’s all about nodes. It gives you a blank scene and you first start by clicking the + button right there on the right (presumably), in the Scene tab. That’ll open the Create New Node dialog, with loads of options of node types to be created. We’ll just pick the “Node” type, and… now we have a node! Fantastic.

This will be the root node of our tree, and so we’ll call it “Root”. Rename it by double-clicking it and changing its name, and that’s done!

NOTE: I’m not sure if this changed recently or if I just imagined it, but I was quite sure that Godot required at least one root node in a scene to run it. Apparently now it’ll create a root automatically on run, so you wouldn’t need this Root… but we’ll actually use it later on, so hang on to it!

We’ll go ahead and create the rest of the scene, which will be composed of the paddle and the ball

The Final (for now) Scene

In the end, you’ll have the scene you see at the side, and I’ll describe each node so you can add and configure them properly.

  • Root: The root of the scene. Every scene in Godot needs a root, which is where everything else is anchored, so we need this. It’s a common Node, because we just need it as a starting point (and a place to attach some of our game logic, as we’ll see…)
  • Paddle: The paddle, i.e., the player. It’s a Node2D, i.e., a Node in 2D space, because we’re actually going to see and move it in the screen. Set its transform position to (120, 180), a suitable location near the middle left.
  • PaddleShape: Could be a sprite, but for our purposes we’ll make this a simple vector shape. It’s specifically a Polygon2D node, and you can alter it’s Polygon (an array of points) to have 4 points, (8, 64), (8, -64), (-8, -64), (-8, 64), i.e. a rectangle of size 16x128.
  • Ball: The ball. It’s the thing we’ll bounce around. Also a Node2D.
  • BallShape: Again, the graphical shape of the ball. It is also a Polygon2D, and it has points (4, 4), (4, -4), (-4, -4), (-4, 4). A square of size 8.

NOTE: Yes, the ball is a square. Try as I might, I couldn’t find a good way to draw a circle right away in Godot, and I didn’t think we’d get much mileage out of having an actual sprite. A square will also be a better abstraction once we get to the game logic, and even give our game a retro feel! :V

“Get the Ball Running”: an intro to GDScript

If you run the game, you’ll see… a rectangle and a square.

The thrills!

And they do a total of zero things, if you don’t count the draw calls being made. Certainly not quite what we want, right? The thing we’re lacking here, you see, is behavior.

In Godot, we can assign scripts to a node, and said script will add behavior to that node. So what we’ll do now is create a few scripts… well, one script, that will add some behavior to our ball. Namely:

  • It’ll start with a given speed;
  • It’ll bounce on the top, bottom and right walls;
  • It’ll vanish beyond the left wall

Select the Ball node and click the button to add a script, on the top right of the Scene tab (right to the right of the Filter Nodes search box). It’ll open a dialog to create a new script. You can leave the options as they are, and create Ball.gd.

Copy the following text, either by typing or copy-pasting it, into the editor that has now appeared (if you copy-paste it, remember to paste it over the text that’s there already).

# Base Class for this script; in this case the same as the node you're attaching it to
extends Node2D
# Class variables go here# 'export' makes the variable visible in the Inspector for that node, so it can be configured in the editor
# It also allows the variable to have an expected type and other stuff (check the docs!)
# Starting Speed of the ball
export(int) var startingSpeed = 64
# Horizontal Speed of the Ball
var hspeed
# Vertical Speed of the Ball
var vspeed
# Called when the object is introduced in the scene, or at the start of the scene
func _ready():
# start with a random angle between 30 and 60
randomize()
# get the random starting angle... in radians!
var startingAngle = deg2rad(rand_range(30, 60))
self.hspeed = cos(startingAngle) * startingSpeed
self.vspeed = sin(startingAngle) * startingSpeed
# Called at every update step, has the amount of time passed until the last update (delta)
func _process(delta):
# Move itself based on its speed components
self.translate(Vector2(delta * hspeed, delta * vspeed))

# Check collision with the game walls
if self.position.y <= 4:
self.bounce_vertically()
elif self.position.y >= 360 - 4:
self.bounce_vertically()
elif self.position.x >= 640 - 4:
self.bounce_horizontally()

# Bouncing functions
func bounce_vertically():
self.vspeed = -self.vspeed
func bounce_horizontally():
self.hspeed = -self.hspeed

With this code attached to the Ball, you should have it moving diagonally, bouncing off the walls and then disappearing off the left. Great!

Great? Well, not great. But good.

Marvelous!

Setting Up Input

One thing you might have noticed is that our paddle can’t move. Another you may have noticed is that, in fact, there’s no way to make it move! For that, we’ll set up inputs for Godot to accept! Let’s go into the Project Settings window and to the Input Map.

(There are, in fact, lots of inputs set up by default in Godot, but we’ll set our own up for educational reasons.)

We’ll add two actions named “paddle_up” and “paddle_down”. Using the + button to the right of the tabs named after them, you can add a key to be bound to it. Or more than one, just for fun.

Pictured: Fun(?)

Now we’re going to add a script to incorporate behavior to our paddle. We can call it Paddle.gd:

extends Node2D# Speed of the paddle, in pixels per second
export(int) var paddleSpeed = 64
# Current vertical speed of the paddle
var vspeed = 0
# Called at every update step, has the amount of time passed until the last update (delta)
func _process(delta):
self.position.y += delta * vspeed

# Correct position if paddle is out of bounds
if self.position.y < 64:
self.position.y = 64
if self.position.y > 360 - 64:
self.position.y = 360 - 64
# Called on every input event, including key presses and releases
func _input(event):
# Check event for up key
if event.is_action_pressed("paddle_up"):
self.vspeed = -paddleSpeed
if event.is_action_released("paddle_up"):
self.vspeed = 0

if event.is_action_pressed("paddle_down"):
self.vspeed = paddleSpeed
if event.is_action_released("paddle_down"):
self.vspeed = 0

And now you have it: when you press up, the paddle goes up! When you press down, down it goes! It has some weird behavior when you press them both at once and release, but that’s not important. The important thing is that the ball passes right through the paddle, and we’re going to work on it right away.

Collision checks

If you have some experience with programming, you might have realized one thing about all this code: it’s kind of messy with magic numbers and undocumented constants. That’s because we’re checking for the boundaries manually, while we could be using the engine to our advantage. Doing that with collisions will also add some interesting design decisions to be made.

So, what we’ll be adding to the game is a set of colliders, to cover the cases of interaction we’ll need to deal with:

  • The ball bouncing on walls (top, bottom, right)
  • The ball bouncing on the paddle

I’m not going to go into the specifics of Godot’s collision system (especially because I don’t really know it that much), so in summary, what we want her are three static colliders for the walls and two kinematic bodies for the ball and paddle. We’ll have to add more nodes to the root for the static ones and change the Ball and Paddle nodes to be KinematicBody2D nodes (using the Change Type option in their right-click context menu) and add child nodes to define their collision shapes. In the end, your tree should look like the one you can see to the left.

The Bottom and the Top wall should be almost the same, with the collision box defined as a Rectangle with extents (320, 1), the TopWall node set with transform at (320, 0) and the BottomWall at (320, 360). The RightWall node will have the collision shape set as a Rectangle with extents (1, 180), and its transform will be positioned at (640, 180).

The Ball and Paddle colliders will have extents (4, 4) and (8, 64), filling their shapes respectively.

NOTE: Watch out if you decide to copy-paste the nodes that are the same. Godot WILL copy the reference to the shape inside them, and changing it will also change the same reference in other nodes. You can click the down arrow in the Shape definition and use the Make Unique option to create a copy of the shape so you can change independently.

As you can see here

Another thing we’ll do is set up the collision layers, which is what tells Godot how to handle collisions and what collides with what. Basically, what we want is to have layers for the ball, the paddle and the walls, and set it all up so the ball collides with the walls and the paddle, and the paddle also collides with the walls. Go to Project Settings > Layer Names > 2d Physics and let’s set up the physics layers as such:

And set up the physics objects (KinematicBody and StaticBody) of each object to match their respective values:

Layer configuration for Paddle
Mask configuration for Paddle
Layer Configuration for Ball
Mask configuration for Ball
Layer configuration for the walls
Mask configuration for the walls

NOTE: Once again you might have noticed something quite peculiar about this setup: every layer collides with every other layer! We wouldn’t even need to set up layers in this case, as everything would just collide normally. It’s a good exercise, though.

Those were some tedious images, I know. I’m sorry. Now, to the fun part! First we’re going to change the _process method on the Ball and Paddle scripts, so they’ll move using the move_and_collide function instead of just moving. Then we’ll handle the KinematicCollision2D objects that come out of the move_and_collide function in case a collision did happen, so the objects will behave accordingly.

NOTE: From now on, the code I’m presenting will be replacing or adding to the code already implemented, unless it’s a new file that wasn’t created at. The editor should tell you if you redefine existing functions, but still be careful to replace the correct ones.

For Ball.gd:

# Called at every update step, has the amount of time passed until the last update (delta)
func _process(delta):
# Move itself based on its speed components
var collision = self.move_and_collide(delta * Vector2(hspeed, vspeed))

# Deal with the collision if it happens
if collision:
self.handle_collision(collision)

# Handle collision
func handle_collision(collision):
# If normal is horizontal, the wall is vertical
if collision.normal.x != 0:
self.hspeed = -self.hspeed

# If normal is vertical, the wall is horizontal
if collision.normal.y != 0:
self.vspeed = -self.vspeed

For Paddle.gd:

# Called at every update step, has the amount of time passed until the last update (delta)
func _process(delta):
# Just move and stop if hit a wall, no need to deal with the collision at all
self.move_and_collide(delta * Vector2(0, vspeed))

A quick play shows that this code works. The ball’s code does the job of bouncing around, and the paddle is stopped by the Physics engine itself upon hitting the walls. There’s just one more thing we’ll want to do, and it has to do with one acceptance criterion: “When the ball hits the right wall, it increases in speed.

There are different ways of doing this, but one that I want to implement is to define the walls in such a way that they can hold information. To be precise, they’ll have an elasticity coefficient so that when the ball hits, it may bounce back with different momentum.

Let’s create a new scrip in the Editor, and call it ElasticWall.gd. It’ll be a StaticBody2D, and it’ll be as such:

extends StaticBody2D# Elastic coefficient
export(float) var elasticity = 1.0
# Colliding with this causes the ball to bounce horizontally?
export(bool) var horizontalCollision
# Colliding with this causes the ball to bounce vertically?
export(bool) var verticalCollision

If it seems odd that this script has no functions, don’t worry: it means it doesn’t add behavior to the node. It only adds data, and data is what we’ll use.

Now attach the script to all the Wall nodes in the tree, either by using the Script property in the Inspector, or dragging the script onto the node.

Look at them scripts go!

And now all of the Wall nodes will have a few extra properties to be tweaked in the Inspector!

Exquisite!

So we’ll check the Vertical Collision box for the Top and Bottom walls and the Horizontal Collision box for the Right Wall, as well as set the Elasticity property to 1.25.

And, finally, we’ll change the Ball script to take advantage of this data:

# Load the ElasticWall class
const ElasticWall = preload("ElasticWall.gd")
# Handle collision
func handle_collision(collision):
if collision.collider is ElasticWall:
if collision.collider.horizontalCollision:
self.hspeed = -self.hspeed
if collision.collider.verticalCollision:
self.vspeed = -self.vspeed

# Multiply speed by the elasticity coefficient
self.hspeed *= collision.collider.elasticity
self.vspeed *= collision.collider.elasticity
else: # Then it's the paddle
# Only reflect if colliding with the paddle horizontally
if collision.normal.x != 0:
self.hspeed = -self.hspeed

In summary, we’re telling the ball that, upon collision, it should check if what it’s colliding with is an instance of ElasticWall and, if it is so, to use the data from that script to bounce (and build up in speed if it’s the right wall). Otherwise, we know it was the paddle and we don’t want to ball to reflect from it unless hitting on the right side, so we only deal with that case.

We’re almost done. Well, almost almost. There are a few things to do…

Tally up the score

Right, we were supposed to be keeping scores! Let’s set up the stage, so to speak. Namely, let’s set up a UI.

NOTE: This is the part where I admit that I have never used Godot’s UI creation before and to be honest it baffled me. It doesn’t quite work like the Unity UI creation, and it seems that I’d need to study it better to make an elaborate HUD. that’s why what we’re making here is very, very basic.

Our UI is basically just a container with the Score panel which contains both the current score and the highest score. It’s not an elaborate UI, so we aren’t going to design it a lot. The one thing that I would advise is to select the Score Container node and use the Layout menu to set it up on Top Wide. Set the bottom margin to 32 and then the Score label will probably center itself nicely. Setting the minimum size of the label to (32, 16) also will probably help.

Now, for scripts, we’ll have three things: two new scripts and one change in an existing one. First thing we’ll do is add a script to the Root. It’s what I like to call a “controller”, a script without a real presence in the game world.

extends Nodevar score = 0
var highestScore = 10
func _ready():
pass
# Start of a new game
func reset():
score = 0
# Update the score
func addScore():
score += 1
if score > highestScore:
highestScore = score
# Returns the score formatted for our UI holder
func getScore():
return "%04d / %04d" % [self.score, self.highestScore]

Next is the Score label’s script, which serves to update its contents. It’s pretty simple:

extends Label# This variable will contain the root node
onready var root = get_node("../..")
func _process(delta):
self.text = root.getScore()

And, finally, one change to the Ball node. We’ll add a different behavior when the ball hits the paddle. Remember the specs: we want to add points when the paddle hits the ball.

onready var root = get_node("..")# Handle collision
func handle_collision(collision):
if collision.collider is ElasticWall:
if collision.collider.horizontalCollision:
self.hspeed = -self.hspeed
if collision.collider.verticalCollision:
self.vspeed = -self.vspeed

# Multiply speed by the elasticity coefficient
self.hspeed *= collision.collider.elasticity
self.vspeed *= collision.collider.elasticity
else: # Then it's the paddle
# Only reflect if colliding with the paddle horizontally
if collision.normal.x != 0:
root.addScore()
self.hspeed = -self.hspeed

I’ll take just a quick while to mention the get_node function there. Godot’s node tree is a very central point to its architecture, and so it provides ways of traversing the node hierarchy (so you can, for instance, affect hte child nodes of an actor). And just like in the filesystem tree, “..” denotes the previous level in the tree. That’s why Root is “../..” from the Score label, and also “..” from the Ball node.

And no you’ll have this gameplay going on. Success!

This was rather harder than it seems.

Wait, success?

Rinse and repeat

Okay, reviewing the feature list all the way up there, we have remaining…

  • When the ball hits the left wall, it disappears and the score is reset
  • Pressing the ESC key quits the game

Let’s do the easier one first: set up an input that we’ll call “quit” and assign it to the ESC key. Then we’ll add the _input function in the gameplay controller so it’ll finish the game instantly.

func _input(event):
if event.is_action_pressed("quit"):
get_tree().quit()

But we don’t want to open the game again to play again when the ball goes over the left side, right? So now we’re going to do two things: firstly, have the ball tell our gameplay controller that the game is over; and then make the gameplay controller actually reset the game to its initial position, beyond just the score.

# Called when the object is introduced in the scene, or at the start of the scene
func _ready():
# start with a random angle between 30 and 60
randomizeMovementAngle(30, 60)
# Called at every update step, has the amount of time passed until the last update (delta)
func _process(delta):
# Move itself based on its speed components
var collision = self.move_and_collide(delta * Vector2(hspeed, vspeed))

# Finish the game if too far to the left
if self.position.x <= -128:
root.reset()

# Deal with the collision if it happens
if collision:
self.handle_collision(collision)
# Let's isolate this routine in a function
func randomizeMovementAngle(minAngle, maxAngle):
randomize()
# get the random starting angle... in radians!
var startingAngle = deg2rad(rand_range(minAngle, maxAngle))
self.hspeed = cos(startingAngle) * startingSpeed
self.vspeed = sin(startingAngle) * startingSpeed

The reason why we have isolated that logic in a new function will become clear enough very soon. What we did here was have the ball reset the game when it goes far enough to the left. The next step is to change our gameplay controller to do something else in reset()

# Start of a new game
func reset():
# Reset the current score
score = 0

# Reset the ball at the middle of the screen
var ball = get_node("Ball")
ball.position = Vector2(320, 180)
ball.randomizeMovementAngle(30, 60)

And now… you have a game that actually starts over when it finishes! Yay!

Yay!

You are now officially a Godot Developer!

What you have right now is, basically, a complete game that fills all of those requirements we set out at the beginning of this tutorial. Hence… congratulations, you’ve just developed a game in Godot. You are now a Godot Game Developer!

Now, whether this is the first time you’re meddling with Godot or if this is the merely the first game you ever complete in Godot, then this is just the beginning. There are lots of resources that the engine offers. Lots of things to learn. Lots of ways of doing things! I was going to include some addenda to this article with improvements to this game that also explored more concepts and principles of design in Godot. I’ve decided to leave that for another article for a couple of reasons:

  1. This tutorial is long enough on its own and it serves its purpose;
  2. I’ve actually understood some of those concepts wrong and I feel like I need to grasp them better before teaching about them.

So… this is it for now. Have a lovely day and keep on learning, y’all!

--

--