Beginner Godot 2D Platformer

In this Godot 3.3 tutorial we will learn how to make a simple 2D platformer in Godot. Our player character will run and jump around a level and collect coins to increase their score.

We will learn how to design a level using TileMaps, how to move a Sprite using code, and how to make two objects interact with each other with signals.

Download Game Assets

Create a New Project

Browse your computer’s file directories and save the project somewhere you can find it later. Name the project "Retro Platformer" and create the folder.

For this project we will be selecting OpenGL ES 2.0.

Press Create & Edit when you are ready.

We are making a 2D game so select 2D Scene.

This will create a Node2D, which is the most basic 2D node. All other 2D nodes extend from Node2D.

Let’s rename it to "Level01". Press {ctrl} + {s} to save the scene.

Create a new folder to save our levels in. Name the new folder "levels" and save the scene there.

Import the Assets

Let’s download the asset files for our game. These files include things like the fonts, sound effects, and sprites for our game. Unzip the file and save it somewhere you can find it again, such as on your desktop.

Back to Godot, in the FileSystem window, right-click the res:// folder and select Open in File Manager. This will open up the Godot project folder in your operating system’s file explorer.

On your computer, move the assets folder into your Godot project. Open the assets folder and move icon.png to replace the default icon.

Your project should look something like this when you’re done.

Working with 2D

When you are working in 2D in Godot, you will be presented with the red horizontal X and green vertical Y axis. The origin point is where the X and Y axis intersects.

The blue box defines the size of your game’s window, as defined in the project settings.

Project Settings

Let’s define our project’s settings.

Open up the project settings. It will default to the General tab. On the left column, scroll down until you find:

Display > Window

When working with 2D you will want to figure out your window settings early, such as your game’s resolution.

For this tutorial, we will be using a minimalistic pixel-art, art-style. This means we will set our project to a small resolution, and upscale it to fit the user’s monitor.

The art assets we are using are 16px by 16px. We want our project’s resolution to be evenly divisible by that. And so to give us a 16:9 aspect ratio, we will set the Width to 480 and the Height to 270.

Test Resolution

This is a very small resolution. When developing the game, we may want to define a larger resolution for testing purposes.

When developing a game, I generally like having the game window be a little smaller than my screen.

We can type in the resolution multiplied a number directly into the field and Godot will calculate the result.

Next we will set the Stretch settings. You may have to scroll down to see these settings.

We will set the Mode to viewport and the Aspect to keep.

These settings will ensure our pixel-art is not stretched weirdly and that players with larger monitors do not see more of the level.

Pixel Snap

On the left column, scroll to:

Rendering > 2d

Under the 2d section enable Use Gpu Pixel Snap.

Create a TileMap

We will start this project by creating a TileMap.

A TileMap allows you to draw a level in your game with your mouse.

Make sure you have the Level01 scene open. You can always open it from the FileSystem window.

In the Scene window, add a new node by pressing the + icon or using the keyboard shortcut {ctrl} + {a}.

We are looking for the TileMap node. You can either navigate to it under:

Node > Node2D > TileMap

Or you can type it in to the search bar at the top of the same window.

Create a TileSet

Historically, computers had a very limited amount of memory. So to reduce RAM usage, game developers would put every texture in the game into a single image file. The game engine would divide that single image into the different parts, and show the correct texture where it was needed in the game.

That is the purpose of TileSets. An image will be divided up into pieces or tiles.

A new TileMap window has opened up to the right of the 2D viewport. It gives use the message:

Give a TileSet resource to this TileMap to use its tiles.

Let’s do just that.

In the Inspector window on the right we can see the properties of the currently selected node, in this case the TileMap. The Tile Set property is empty.

We need to create a new TileSet. Click the dropdown arrow and select New TileSet.

Open up the TileSet by clicking on it.

This will open up the TileSet window. You may have to make it bigger by dragging it up along that center top edge.

To get started with a TileSet, we need to add a texture. Textures in Godot are simply image files such as .png and .jpg files.

Press the + icon at the bottom left of the TileSet window.

Open the image from:

assets > images > Clean-Forest-Retro-Lines.png

Zoom in so we can better see what we are working with.

Blurry Pixel Art

At this point, depending on your settings, the textures may be blurry.

This is a result of how Godot imports textures by default. Since we working with pixel art, we want Godot to scale the images without applying any sort of smoothing filters.

We will have to reimport our images with new settings.

In the FileSystem window, open the ‘assets > images’ folder. Select the top image, hold shift, and select the bottom image. This will select all the images in between as well.

Go to the Import tab which is next to the Scene tab. Press Preset and select 2D Pixel.

Press Preset again and select Set as Default for ‘Texture’.

Finally press Reimport to actually apply the changes.

The texture in the TileSet window should now be noticeably clearer.

New TileSet Tile

Now that we have our texture defined, we can create some new tiles.

In the upper-right there are three different kinds of tiles we can create: Single Tile, Autotile, and Atlas.

Single Tile is pretty self-explanatory. You can think of this option as a single stamp you can place anywhere in your game.

Atlas Tiles are similar to single tiles but they are comprised up of multiple related tiles. You can think of this option as different versions of the same stamp. For example, we will use an atlas tile for the plants in our game, since there all several different plants to choose from, but they essentially serve the same purpose.

Autotiles are a very powerful tool that allow us to define a repeating pattern of tiles. We will be using Autotiles for our ground textures. We will define the up, down, left, and right texture of the autotile, and then we can simply draw our level and correct tile will be chosen automatically.

Let’s create the ground first. The ground will be an autotile.

Press New Autotile.

Snap to Grid

Before we define the region of the image that makes up the ground, let’s enable snap to grid.

The default grid size is too large for the images we are using. You can tell by looking at how the plants don’t fit with the grid. We are using 16px by 16px art.

Select the upper-left tile. Really we can select any tile here, as we just need to expose the tile properties in the Inspector window.

In the Inspector window under Snap Options, change the Step to 16 by 16.

Define the Region

Remember, we are creating an autotile for the ground.

We will define the region that contains all the ground tiles that will make up our autotile. I have highlighted the selected region in red.

Define the Bitmask

Select the bitmask section. Bitmasks are unique to autotiles.

TileSet autotile bitmasks define how the tiles should exist when next to each other. They define which tile should be on top, which tile should be on bottom, which tile is the center, etc.

There are three main properties you’ll need to set when creating an autotile.

In the Inspector window, expand the Selected Tile section.

We are creating an autotile so Tile Mode should be AUTO_TILE.

The Autotile Bitmask Mode defines how the tile should be subdivided for the bitmask. This depends on the specific art you are working with. For our case the default of 2X2 is correct.

The Subtile Size defines how big a single tile is for our autotile. In our case, this is identical to the grid step we set earlier, to 16 pixels.

You can use the left mouse button to draw the bitmask and the right mouse button to erase.

We will draw the bitmask where we want to define as the "inside". We want the green grass to be the outside/edges of the autotile, and the brown dirt to be the inside.

This is the final result.

Godot limits use to defining a region as rectangular. So as you can see, we have some extra grass tiles and a crescent moon that are not actually part of this autotile.

Autotile Icon

In the Icon section you can define which tile will appear as the icon for our autotile. I will just leave it as the default upper-left corner tile, but feel free to change it.

Autotile Collision

Finally let’s go the Collision section. Here we are defining which parts of the autotile the player should collide with.

Since we are creating the ground, the collision shape will take up the entire tile.

We will create a new rectangle collision shape. Select the square icon.

You can simply click a ground tile and a rectangle will be created aligning with the grid.

We need to repeat these steps for every ground tile.

  1. Select a new tile with the mouse.

  2. Select the square icon.

  3. Click the tile to create the collision shape.

At the end it should look something like this.

Autotile Rename

Before we finish creating this autotile, let’s rename it something shorter. In a moment you will see why shorter names are preferable.

In the Inspector window under the Selected Tile section I will rename it simply, "green".

Fix the Grid

In the Scene window, click on the TileMap node. If you hover the 2D viewport, you may notice that the grid is not aligned with our newly created TileSet.

We can fix this adjusting the cell size of the TileMap. In the Inspector window, disable Show Collision. Under the Cell section, change the Size to 16.

Almost There

Draw a 3 by 3 tile map, be sure to fill in the center as well.

You will notice that the outer edges are tiling correctly, but the center is out of place.

My hope in showing you this error, is that it will help you understand what exactly the bitmask is and how it works.

This is happening because we never defined the inside (or center) tile in the bitmask. So let’s fix that.

Make sure in the Scene window you have the TileMap node selected. In the Inspector window open the TileSet by clicking on it. In the TileSet window, select the one texture on the left we have added so far.

Before we continue let’s enable the tile names. Click the little i icon in the upper-right of the TileSet window.

The TileSet editor is pretty finicky as you may have already noticed, so we enable the tile names so that we can click on the names to start editing that tile.

Select the Bitmask section.

We need to define the inside/center tile. We will simply choose an empty tile.

Now that we have your autotile set up correctly, take a moment to study the bitmask.

The inside/center tile is fully red because it is defined by having other tiles on all sides. The top tiles are only red at the bottom because they will only connect to other tiles from the bottom, or other top tiles from the side.

Draw the TileMap Level

Select the TileMap node in the Scene window. First use the right mouse button to erase the tiles you have already drawn.

Now any new autotiles you draw should work correctly. Remember, you can’t just draw the outline, you also have to draw the inside for it to work correctly.

Feel free to draw a simple level. Or you want may want to wait until we create our player so you know how far they can jump.

Create the Player

We will come back to TileMaps and TileSets later. In the meantime let’s pivot to something else.

Let’s add some game play to our game and create the player. Since our player scene will read user input and move accordingly, the KinematicBody2D node will the one we want.

Select the root Level01 node in the Scene window. Add a new node of type KinematicBody2D.

The KinematicBody2D node should be a direct child of the Level01 node. If you accidentally made it as a child of the TileMap node, you can simply click-and-drag it to re-parent it.

Rename the KinematicBody2D node to "Player".

We want to make the Player its own scene so that we don’t have to recreate it every level. We can simply instance the Player scene in every level.

Right click the Player node and select Save Branch as Scene.

The save window should appear. In the upper-right, click the Create Folder and make a new folder called "player". Save the scene there.

Open the Player scene by clicking the movie clapper icon.

Player AnimatedSprite

I’m sure you have noticed the little yellow warning icon. It tells use that "This node has no shape". But before we can add a collision shape, we have to know what the player looks like. So let’s add the sprite first.

Our player will be animated so we will use an AnimatedSprite node.

Add a new node of type AnimatedSprite.

A warning icon appears for the AnimatedSprite node. It wants us to create SpriteFrames in the Frames property, so let’s do just that.

In the Inspector window on the Frames property, click the drop-down arrow and select New SpriteFrames.

Click on the new SpriteFrames to open up the Animation window.

Player Animations

Let’s create the idle animation first, for when our player is just standing in place.

By default, Godot will create an empty animation called "default". Rename the default animation to "idle".

Let’s add some frames to this animation. Click the grid icon.

Open up the player image in the assets folder.

assets > images > Clean-Retro-Lines-Player-Plain.png

We now need to set the grid to match the image. We want every frame to have it’s own grid cell. Unfortunately we can’t zoom in on this view, since we are using small images.

Set the Horizontal to 7 and set the Vertical to 3.

Select the two idle frames.

Click the Add 2 Frame(s) button.

Enable Playing in the Inspector window.

The default speed is a little fast. In the Animation window, let’s change the Speed (FPS) to 2.

Walk Animation

While we are at it, let’s just add the other animations now. We will do the walk animation next. Create a new animation and rename it to "walk".


Just like before, click the grid icon. We will use the same image.

assets > images > Clean-Retro-Lines-Player-Plain.png

Again, set the Horizontal to 7 and set the Vertical to 3.

Select the four walk frames and add them.

Jump Animation

Let’s do the same thing for the jump animation. Again, create a new animation and rename it to "jump". Click the grid icon and load the image. Set the Horizontal to 7 and set the Vertical to 3.

The source file has two jumping frames, but for our purposes we only want the second frame where the character has their legs extended.


Player CollisionShape2D

So now that we have our player’s sprite defined, we know how big to make the collision shape and where to put it.

Before we move on, we need to make sure that the idle animation is visible. In the Scene window, select the AnimatedSprite node. In the Inspector window, set the Animation property to idle.

Select the root Player node. Add a new node of type CollisionShape2D.

The warning icon tells us we must provide a shape, so let’s do just that.

In the Inspector window under the Shape property, click the drop-down arrow.

Our player sprite is very rectangular, so we will choose New RectangleShape2D.

In the Inspector window, click on RectangleShape2D to expand it’s properties.

You can either change the size of the CollisionShape2D in the 2D viewport or set it directly in the Inspector window. It’s also slightly off-center so we will need to move it.

In the Inspector window, these are the final values. For the RectangleShape2D on the Extents, the x value is 4 and the y value is 7.

And for the Position, the x value is -1 and the y value is 1.

Player Movement

It’s time to make the magic happen. Let’s write a script for the player movement.

Select the Player node in the Inspector window. Attach a script to it.

Godot will automatically place the script in the same folder as the scene file. For the Template we will use Empty.

While I have made it easy to copy and paste this code, I highly recommend you take the time to type it out. As you type, Godot will suggest functions and variables to you. You can press {tab} to let Godot autocomplete the top suggestion. If a list of multiple suggestions appear, you can use the arrow keys and navigate to a specific one. You can also simply ignore the autocomplete feature and continue to type normally.

2D Platformer Controller: Moving and Falling

extends KinematicBody2D

export var move_speed := 100
export var gravity := 2000

var velocity := Vector2.ZERO

func _physics_process(delta: float) -> void:
    # reset horizontal velocity
    velocity.x = 0

    # set horizontal velocity
    if Input.is_action_pressed("move_right"):
        velocity.x += move_speed
    if Input.is_action_pressed("move_left"):
        velocity.x -= move_speed

    # apply gravity
    # player always has downward velocity
    velocity.y += gravity * delta

    # actually move the player
    velocity = move_and_slide(velocity, Vector2.UP)

At the top of the file, our script extends KinematicBody2D. This means that any properties or functions that KinematicBody2D has, are available for us to use. Open up KinematicBody2D in the Godot documentation to learn more.

We declare three variables at the top: move_speed, gravity, and velocity.

We have exported the first two so that they become visible in the Godot editor under the Inspector window. We are able to edit them while the game is running and see how they change the game in real-time.

The := is an optional alternative to =, that enforces strict typing. Instead of just saying "this variable has this value", the := means "this variable has this value and is also of this type". That way if we ever try to assign a different type to this variable, Godot will throw an error. Its small detail that forces the programmer to write better code.

The variable velocity is of type Vector2. A Vector2 is simply a pair of numbers, stored in a single variable. More specifically, a Vector2 has an x and y property that are of type float.

We assign to velocity the value Vector2.ZERO. This is a shorthand way of saying Vector2(0, 0). Vector2 has some other constants you can find in the Godot documentation.

We then write all the movement code in the _physics_process() function. The _physics_process() function is a special function in Godot to handle all physics code. By default, this function runs 60 times every second to ensure a smooth and constant physics simulation. It is separate from the _process() function which runs every frame.

At the beginning of the _physics_process() we set the x value of velocity to zero, to reset the horizontal velocity. We only want the character to move left or right when the user is pressing a key.

When then check if the user is pressing the left or right keys, and set the x property of velocity to positive or negative move_speed.

Next we apply gravity. We take gravity multiplied by the delta and assign that the y property of velocity.

Negative values are up along the Y axis in Godot. Positive value are down. Therefore we add the gravity to the y property of velocity to make the character fall down.

What is delta?

You will notice that delta is an argument passed into the _physics_process() function. It contains how much time has passed since the _physics_process() last ran. In this example, we multiply delta and gravity together to ensure a smooth experience. By doing this, users running the game on weaker hardware will still have a smooth experience because the delta will compensate for any frame-rate drops.

Move and Slide

On this last line, we pass in the velocity variable as well as the constant Vector2.UP into Godot’s move_and_slide() function.

The second argument of the move_and_slide() function is which way is up. We are making a traditional side-scrolling game so "up" corresponds to the built-in constant, Vector2.UP. If we were making a different kind of game, we could easily pass in something else to completely change how the physics behaves.

You may have noticed that we do not multiply velocity.x by the delta, only velocity.y. The move_and_slide() function does that multiplication for us when we move horizontally.

Input Map

We have referenced two actions move_right and move_left, but we haven’t bound them to any buttons.

Godot’s input map allows us to bind multiple keys to a single in-game action. It also makes it easy to allow players to edit their keybindings.

Open the project settings.

Go to the Input Map tab. Type in "move_right" to the Action field and press Add.

Add "move_left" as well.

Click the + icon on the right to add an event.

Select Key. Godot will start listening for keyboard inputs. Press the key you want to bind the action to.

Feel free to bind the actions to whatever key you want. I have bound them to a and d as well as the arrow keys.

Launch the Game

Let’s launch the game. If this is the first time you’re launching the game, Godot will have you select a main scene. Choose the Level01 scene.

Our character should be able to move left and right, and will also fall off edges.

Close the game.

You may want to move the the Player to a more suitable starting position. You can open the Level01 scene, select the Player node, and click-and-drag it in the 2D viewport.

Jump

Next we are going to implement the back-bone of any platformer, jumping.

Go back to the Script view. So far we only have one script, but you can switch between scripts on the left-hand column. You can also open the script from the Scene window.

At the top of the Player script, add another exported variable with the others.

export var jump_speed := 550

In the _physics_process() function right above the move_and_slide() function call, add the jump code.

 # jump will happen on the next frame
if Input.is_action_just_pressed("jump"):
    if is_on_floor():
        velocity.y = -jump_speed # negative Y is up in Godot

If the user presses the jump action, we first check if they’re on the floor using this built-in function. If they are, we assign the y property of velocity a negative value. A negative value along the Y axis is "up" in Godot.

Jump Input Action

Again, we have referenced an action that we have yet to define. We need to bind the jump action to a key in the Input Map.

Open the project settings and go to the Input Map tab.


Add the action called "jump". Bind it to whichever key(s) you want.


I have bound the jump action to both the w key and up arrow.

Try It Out

Open the Level01 scene. Select the Player node.

Launch the game again. While the game is running, you can edit the exported variables we defined on the Player and see how it feels.

Connect Player Animations

Now we are going to change what animation plays as the player moves around. We will use Godot’s _process() function since it runs every frame.

First we will flip the sprite depending on which direction the player is travelling.

 func _process(delta: float) -> void:
    change_animation()

func change_animation():
    # face left or right
    if velocity.x > 0:
        $AnimatedSprite.flip_h = false
    elif velocity.x < 0:
        $AnimatedSprite.flip_h = true

We know if the player is travelling left or right by checking the x value in the velocity as that holds the horizontal velocity. AnimatedSprites come with a convenient property flip_h, to flip the sprite horizontally.

Feel free to launch the game and test it.

Add this next part inside the change_animation() function, under what we have already done.

if velocity.y < 0: # negative Y is up
    $AnimatedSprite.play("jump")
else:
    if velocity.x != 0:
        $AnimatedSprite.play("walk")
    else:
        $AnimatedSprite.play("idle")

If the player is travelling upwards, we will play the jump animation. Otherwise, if they are moving horizontally, the walk animation will play. And of course if none of those are true, the animation will return to the default idle animation.

And with that our player’s 2D platformer controller is finished. Play the game and jump around. Get a feel for the movement, as that will dictate your level design.

Make the Camera Follow the Player

Right now our camera is static. We want the camera to follow the player around the level. Luckily Godot has most of the functionality we need built-in.

Open the Player scene. Add a child node of type Camera2D.

Let’s set the properties we need.

The camera will "not work" unless we enable it by setting as the current camera. In the Inspector window enable the Current property.

Go ahead and launch the game. The camera follows the player. But it follows the player’s position 1:1, which does not look that great. What we really want is the camera to be static if the player is near the center, but then move with the player if the player nears the edge of the screen.

Godot calls this the Drag Margin.

In the Inspector window under the Editor section, turn on Draw Drag Margin. Now we can see what we’re working with.

We need to enable the the horizontal and vertical drag margin.

Feel free to experiment with the drag margin parameters and see what feels right.

Collecting Coins

Now that our player can move around the level, let’s give them a reason to. We are going to add coins for the player to collect.

Creating the coin is going to be pretty similar to how we made the Player scene.

Open the Level01 scene. Add a child node of type Area2D. Rename it "Coin". Save it as it’s own scene.

In the upper-right corner press the Create Folder button. Create a new folder called "pickups" and save the scene in there.

Click on the movie clapper icon to open the Coin scene. You can also open it from the FileSystem window.

To the Coin scene, add a child node of type AnimatedSprite.

In the Inspector window, create a new SpriteFrames on the Frames property.

Rename the default animation to "spin".

Click the grid icon to add frames. Add the coin image.

assets/images/Coin.png

Set the Horizontal to 6 and the Vertical to 1. Click on all 6 frames.

Enable Playing in the Inspector window.

Now let’s add the collision shape. Select the root Coin node. Add a child node of type CollisionShape2D.

Add a new RectangleShape2D. We could add a CircleShape2D since that would match the circular shape of the coin better, but really that difference would not be noticeable in real gameplay. You can use a circle shape if you would prefer.

Change the Extents to 4.


Toss a Coin to Your Level

Let’s add the coin to our level.

Open the Level01 scene. The Coin node we created earlier should already be there.

Let’s place it somewhere the player can reach. To make things easier let’s enable Grid Snap.

Now click-and-drag the coin to somewhere in your level the player can reach.


Pickup Coin

Next let’s implement interactivity. We need to detect when the player and coin are touching. We are going to use Area2D‘s and signals for this.

Area2D‘s are the perfect node for when we need a simple hitbox. They tell you when they overlap another shape.

Open up the Coin scene and attach a script to the Coin node. We are going to attach a signal to the coin.

Select the Coin node again in the Scene window, so that we can it’s properties. We are going to detect when a body enters the Area2D of the coin, as defined by the CollisionShape2D.

Select the Node tab, next to the Inspector window. Make the Signals section is selected. Double-click on the body_entered signal.

Connect the signal.

Godot will generate a method for us that automatically gets called whenever the signal is emitted.

We can test this out. Let’s print out a test string to the console.

Launch the game and make the player touch the coin. The moment they do, our test string should be printed in the Output window.

We are now successfully detecting when the player touches the coin.

Coin Pickup Animation

When the player touches a coin, we want the coin to play an animation and sound to let them know that they picked it up.

Let’s add the pickup animation first. Select the AnimatedSprite node. Create a new animation.

Rename it to "pickup".

Click the grid icon to add frames.

Load the CoinPickup image.

assets/images/CoinPickup.png

Set the Horizontal to 6 and Vertical to 1 and select all 6 frames.

This animation should only play once, so we will disable looping. Let’s also speed the animation up.

We don’t want to this new pickup animation to play by default. So let’s change the coin’s starting animation back to spin.

Coin Pickup Sound

Now let’s add the coin pickup sound. Select the Coin node. Add a child node of type AudioStreamPlayer2D. In the Inspector window, load the coin pickup sound in the Stream property.

assets/audio/sfx/CoinPickup.wav

On the AudioStreamPlayer2D go the Signals section. Double-click the finished() signal and connect it.

Put it All Together

Let’s flesh out the Coin script.

extends Area2D

func _on_Coin_body_entered(body: Node) -> void:
    $AnimatedSprite.play("pickup")
    $AudioStreamPlayer2D.play()

func _on_AudioStreamPlayer2D_finished() -> void:
    queue_free()

When a body enters the coin’s area, we will play the "pickup" animation and play the sound. We wait for the sound to be finished playing and then we queue the coin to be freed from memory.

Go ahead and launch the game and try it out.

HUD and UI

So now the player can pickup coins. What we want is for each coin to have a value that makes the player’s score go up. And we want that score to be displayed and updated on the HUD.

For this tutorial, we will only be displaying the score on the HUD. But we will structure the HUD in such a way to make it easy to add to.

Open the Level01 scene. Add a child node of type Control. Rename it to "HUD".

The Control node is the root node for UI (user interface) elements in Godot, to make things like menus.

Move the HUD up to so that it’s the first child of the Level01 node.

Right-click the HUD node and save it as it’s own scene.

Save it in a new folder called "UI".

Click the movie clapper icon to open the HUD scene.

Let’s make the HUD fullscreen. Click on Layout and select Full Rect.

We don’t want the HUD elements to be put right up against the edges of the screen so we want to create a margin. Add a new child node of type MarginContainer.

Again, make the Layout Full Rect as well.

In the Inspector window, expand the Margin section. We want the margin to be 10 from the edges of the viewport.

HUD Layout

Add a child node of type VBoxContainer. And to that, add a child node of type HBoxContainer. And to that, add a child node of type Label.

In the Inspector window, type something into the Text field.

To create the UI layout you want, you will want to use a combination of VBoxContainers (vertical) and HBoxContainers (horizontal). These two nodes will create columns and rows.

Since we are only creating one HUD element, we won’t be able to visualize how these two nodes truly work. I encourage you to create multiple Labels, VBoxes, and HBoxes and experiment with these nodes.

Custom Font

Let’s use a font that better suits the art style.

With the Label node selected, in the Inspector window, expand the Custom Fonts section and enable Font. Click the drop-down arrow and select Load.

assets/fonts/minecraft.fnt

It’s Not There, Help

Some users have reported a Godot bug, where you can’t load a .fnt file into Godot.

The minecraft.fnt file is still there on your hard-drive. You can see it if you open the project folder in your computer’s file explorer. But for some users, Godot does not recognize it as a valid font.

Load a custom font and navigate to that folder.

assets/font/

In the File: text field at the bottom, type in the file name, "minecraft.fnt".

The Open button will still be grayed out. Instead of clicking that, press the {Enter} key on your keyboard.

If it still doesn’t work, close Godot and reopen the project.

Canvas Layers

Play the game.

You will notice that the HUD is part of the level. When you walk away, the HUD stays where it was placed in the editor.

What we want is for the score label to stay in the top left corner, even when the player moves. To make this happen, we will put the HUD on a new CanvasLayer.

You use a CanvasLayer when you want to some parts of your game to be rendered to a different layer, and therefore move independently of other cameras in your game. You would use CanvasLayer for things like Parallax Backgrounds, UIs, and Transitions.

Open the Level01 scene. Add a child node of type CanvasLayer.

Move the CanvasLayer node up so that it’s the first child of the Level01 node. Make the HUD node a child of the CanvasLayer node.

Launch the game again. The HUD now stays in place.

Godot Signals Event Bus

Now that we have a label for the score, we need to update the label to reflect the current score.

The obvious choice to send data between scenes is signals.

To keep things as modular as possible, we are going to be using an event bus. The idea is that objects in our game will emit signals to a central location, the event bus. Any other object that needs to listen for that signal can find it there.

The primary benefit of structuring our code this way is that if we ever need to change our node tree structure, it will be easy to reconnect broken signal connections since they all go to a single file.

Autoloads and Singletons

Our event bus is going to be a singleton. This means there will always and only be exactly one instance of it.

Any other object in our game could hypothetically have any number of instances. For example, our level will have multiple coins in it for the player to collect. Therefore we will create multiple instances of our Coin scene.

But we only want one event bus. And the event bus will always exist while the game is running, even between levels, even during the main menu. That’s why a singleton is the right choice.

In the FileSystem window, right-click the res:// folder and create a new folder called "autoloads".

Right-click the new autoloads folder and create a new script.

Rename the script to "Events" and create it.

Open the Project Settings.

Go to the Autoload tab. Click the folder icon and load the Events script.

autoloads/Events.gd

Then Add it.

Make sure that Singleton is enabled.

Coin Pickup Update Score Example

Let’s take a moment to explain how the event bus will work with an example.

In this example the coin holds the score value.

There is a signal on the event bus, which any node in our game can trigger.

When the player picks up a coin, the coin triggers the event bus signal.

The score label is listening for that signal to be emitted, and when it does it will update the score.

Connect the Event Bus Signals

Open the Events script.

extends Node

signal score_changed(value)

We will make a signal for when the score gets changed, along with an argument for the value.

Open the Coin script.

Near the top of the file, add a new constant called "score_value".

extends Area2D

const score_value = 1

When the player picks up the coin, the coin will emit that signal.

func _on_Coin_body_entered(body: Node) -> void:
    $AnimatedSprite.play("pickup")
    $AudioStreamPlayer2D.play()
    Events.emit_signal("score_changed", score_value)

Since Events is a singleton we are able to reference it from any script. We will emit the score_changed signal on the Events singleton and pass along the score_value.

Update the Score on the HUD

Open the HUD scene.

Let’s rename the Label to "ScoreLabel".

Attach a script to the HUD node.

extends Control

onready var label = $MarginContainer/VBoxContainer/HBoxContainer/ScoreLabel

var score = 0

func _ready() -> void:
    label.text = str(score)

func _enter_tree() -> void:
    Events.connect("score_changed", self, "_on_score_changed")

func _exit_tree() -> void:
    Events.disconnect("score_changed", self, "_on_score_changed")

func _on_score_changed(value):
    score += value
    label.text = str(score)

First we will store the ScoreLabel node into a variable. This is a speed optimization, so that Godot doesn’t have to read the scene tree every time we access that node. And when we do reference the scene tree in a top-of-the-file variable like this, we need to use the onready keyword. This is equivalent to writing that same line within the _ready() function.

Next we will create a new variable to store the current score.

In the _ready() function we set the text of the label to the starting score.

In the _enter_tree() and _exit_tree() functions, we connect and disconnect the HUD to the Events.score_changed signal. Those two functions are built-in Godot functions that are called when that node enters or exits the scene tree.

Lastly, we write out _on_score_changed() function that gets called when the Events.score_changed signal is emitted. We add the score, cast it as a string, and set the text of the label.

Launch the game. The ScoreLabel should now start off as 0 and should increment when you pick up a coin.


Adding Coins

At this point we can jump around a level and collect coins. We have finished our core gameplay.

Let’s go over how to add more coins to our level.

You can right-click and duplicate the Coin node from the scene tree.

Alternatively, you can instance a scene by clicking on the link icon. This will allow you to instance any custom scene you have defined in your project.

Decorating Our Level

For the rest of this tutorial we will flesh out the visuals and decorate our level with things like plants and trees.

Open the Level01 scene and select the TileMap node. In the Inspector window, click the TileSet.


Click on the image to open the tile set.

So far we have only made one tile named "green". We are going to build out the rest of the tiles.

The primary limitation with the Godot tile editor is that you can only create rectangular regions. So if the source image does not line up into rectangular regions, there will be places that overlap. Let’s go over an example.

Let’s make a tile for the grass blades.

In this image, we can see how the original green tile region overlaps with the region of the grass blades. When you are selecting the regions, you have to be careful not to click on the overlap, otherwise you might select the wrong region.

Go to the Region tab. We are creating a new atlas tile for our grass blades, so that we can choose which grass tile we want to place.

Create a New Atlas.


Click and drag from the upper-right grass blade to the bottom-left, that way we don’t overlap the previously defined green region.

In the Inspector window expand the Selected Tile section and change the Name to "grass".

Let’s do the small plants next. Create a New Atlas tile. Select the plants. Rename the tile to "plants".

Make atlas tiles for the water, stones, tree bases and tree trunks.

I have highlighted these new regions in red.

You may notice that the tree trunk region contains several different parts of the tree. It has tree trunks, dead tree tops, and the lower half of leaves and branches. Again, because the Godot tile editor limits us to rectangular regions, we are not able to create tiles the ideal way.

One possible solution is to photoshop the image and group all the similar parts together. But this is how this art asset came, so we will work with what we have for this tutorial.

Let’s do the clouds next. It would make sense to make them atlas tiles, but the clouds are of different sizes. One of them takes up a 16 by 16 square, and the other take up 32 by 16. So we will make the clouds a single tile.

Create a New Single Tile for every cloud. Name them "cloud1", "cloud2", etc.

Do the same for the tree tops.

I’ve highlighted the single tiles in red.

New Tilemap

We are going to create a new tile map. It will use the same tile set we have already made so we need to save it so that we can reference it again.

We are making two tile maps to keep the level geometry separate from the details and decorations.

Select the TileMap node in the Scene window. Rename it to "MainTileMap".

In the Inspector window, click the drop-down arrow of the TileSet and select Save.

Navigate to the root folder and create a new folder called "tilesets". Save it as "Green.tres".


Select the root Level01 node. Add a child node of type TileMap. Move it above the MainTileMap. Rename it to "DetailTileMap".

In the Inspector window, load the Green.tres tile set we saved.

Feel free to expand the TileMap window.

Now you can build out and decorate your level. Use the MainTileMap for the ground and walls and anything with a collision. Use the DetailTileMap for anything purely visual that doesn’t affect the platforming gameplay.

You can toggle the visibility of the DetailTileMap in the Scene window.


Color Picker

We have one more step. Right now the background of our game is the default Godot background. The tile set actually comes with three intended background colors to choose from.

There’s a Windows program called Instant Eyedropper that I like to use for grabbing colors off my screen.

Feel free to choose any of the three colors. For this tutorial I will be using the middle color.

This is the hex color code of that color. Most software that deals with colors supports this format.

#2A2A2A

Open the project settings.

Project > Project Settings > General > Rendering > Environment > Default Clear Color

Paste the color code there.

Now the game has a background color that matches the art.

The Sequel is Coming!

    We are planning to make follow-up tutorials that build upon this one. Enter your email if you want to be notified when they come out.

    3 Comments

    1. Avatar
      duckduckfakename
      April 26, 2021

      There are code snippets missing code, starting around the player input/movement area. Was enjoying it very much up to that point–thanks for creating it.

      Reply
      1. Avatar
        fakeandbake
        April 26, 2021

        (sorry, they actually start missing code around “connect player animations”)

        Reply
        1. Diego
          Diego
          April 26, 2021

          I believe I fixed the problem. The full text was there on the back-end, but it wasn’t displaying properly in the browser because of a syntax error.

          Thank you for bringing it to my attention. Let me know if you find any other issues.

          Reply

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Scroll to top