Learn Godot 4 by Making a 2D Platformer — Part 6: Level Creation #3

christine - Jul 22 '23 - - Dev Community

*You can find the links to the previous parts at the bottom of this tutorial.

In the previous part, we created the first level for our game. In this part, we will be finishing up our level creation section of this tutorial by creating our second level, along with adding the final touches to our existing level.


WHAT YOU WILL LEARN IN THIS PART:

  • How to work with Timer nodes.
  • How to create a moving platform.
  • How to work with Tilemap Layers.

LEVEL 1 FINAL TOUCHES

Right now when we run our level, we still have that grey default background at the back of our map. We will change this background to a tiled background with the help of Tilemap layers. Layers allow us to distinguish foreground tiles from background tiles for better organization.

By default, a TileMap node automatically has one premade layer. In your Main scene, select your Tilemap “Level” node and navigate to the Layers property in your Inspector panel.

Godot 2D Platformer

You will see that there is an existing layer, but it has no name. Currently, all of the tiles that we already painted onto our Tilemap exist on this layer. We want to rename this layer to our “Foreground” layer. You will notice that your Layers property in your Tilemap panel below now reflects this change.

Godot 2D Platformer

We want our background to show behind our Foreground layer, so let’s add a New Element to our Layers property and call it “Background”. You will now be able to switch between your layers.

Godot 2D Platformer

Since we want this Background layer to be behind our Foreground layer, we need to rearrange our layers by dragging our Background layer above our Foreground layer property. You can do this by holding and dragging the stacked line icon next to your layer element.

Godot 2D Platformer

Now in your Layers option in your Tilemap panel, make sure your Background layer is selected. If you select a layer, all the tiles that exist on the other layers should be darkened out.

Godot 2D Platformer

Draw in your background. I’m using the plain dark tiles, but you can use any other tile you like. Just make sure that the tile you select does not have any collisions added to it — because this will block the player from running!

Godot 2D Platformer

Also, ensure that you do not paint over your Ladders. We will add another Tilemap node to fill in the spaces behind the Ladders. An alternative fix to this is to drag your Ladders node above your Tilemap node in your Main scene tree — but I don’t like how the ladders stick over the map!

Godot 2D Platformer

Let’s fill in this gap. Duplicate your Tilemap node in your Main scene, and drag it behind your Ladders node. We can rename this duplicated node to “Background”

Godot 2D Platformer

Godot 2D Platformer

Delete this Tilemap’s “Foreground” layer, and simply draw in the rest of your Background to get rid of those awkward grey gaps! (Or simply remove the existing background from your Level’s node, and just draw it in here).

Godot 2D Platformer

You should end up with something like this:

Godot 2D Platformer

Now, let’s add a few windows to our scene as decorations! In your “Level” Tilemap node, add a new Layer titled “Decorations”.

Godot 2D Platformer

In your TileSet panel below, we need to create a new tilesheet for our Decorations. Navigate to “res://Assets/Kings and Pigs/Sprites/14-TileSets/Decorations (32x32).png” and drag the image into your Tiles property.

Godot 2D Platformer

In your Tilemap panel, draw in your windows on your Decorations layer. You can also add other decorations — such as shelves or candles. You can find more decoration objects in the assets provided underneath the “res://Assets/Kings and Pigs/Sprites/7-Objects/” directory.

Godot 2D Platformer

I ended up creating something like this:

Godot 2D Platformer

LEVEL 2 + MOVING PLATFORM

Now that we have our first level set up, we can go ahead and create our second level. If you want — and you have the time — you can create levels 3 & 4 as well. For now, we’ll only create level 2. For this, we can simply duplicate our Main scene. We can name this “Main_2”.

Godot 2D Platformer

In our next level, we already have our Player instanced. This is where our player will spawn when we change our scene to the next level, so make sure they’re placed at the start of your level.

Here is the layout plan for our second level:

  • We will have our barrel spawner at the top. Instead of barrels, our spawner will spawn bombs that will roll on a certain path.

  • We will also have smaller obstacle spawners at the sides, who will throw boxes at our player to make the game harder. These boxes will also follow a certain path.

  • We will have live, attack, and score boost pickups scattered randomly on our level as well.

  • We will have walls that move up and down, blocking the player’s passage.

  • Finally, we will have a player spawn start point and an end point which will complete the level.

Here is an example of what this level could look like:

Godot 2D Platformer

In this level, we’ll add more bomb spawners at the sides to make it more difficult, we’ll also be creating a moving wall that will block our player from passing every 3 or so seconds. Now, before we go ahead to create our level, let’s create our moveable wall or platform first. For this, we’ll need to create a new scene with an Area2D node as its root. You can also use a CharacterBody2D node if you want — we just need a root node that can handle collisions and movement.

Godot 2D Platformer

Rename this node to “Platform” and save the scene underneath your Scenes folder.

Godot 2D Platformer

Assign this new scene with a CollisionShape2D node with a shape of type RectangleShape2D. We’ll fix this shape’s width and height later on.

Godot 2D Platformer

Now, copy and paste your Level TileMap from your Main scene to your Platform scene. We copied this node because we want the autotiles (terrains) we created previously. Delete all of the Layers we added (Background, Foreground, and Decorations) so that we can have a clean slate to draw on.

Godot 2D Platformer

Draw a wall that is 2 tiles wide and 5 tiles high. Also, fix the collision shape to outline it.

Godot 2D Platformer

As a tip for the future, make sure that your Tilemap is renamed to something other than “Wall” or “Level” as this will cause the bomb to explode when interacting with this Platform later on.

Now, in your Platform Scene, connect a new script to it and save this script underneath your Scripts folder.

Godot 2D Platformer

We also need to add one last node to our scene — a Timer node. The Timer node creates a countdown timer that counts down a specified interval and emits a signal on reaching 0. We want this timer to change the movement state of our wall after ‘x’ seconds.

Godot 2D Platformer

We also want to connect our Timer node’s timeout() signal to our script. This signal will emit each time our timer reaches 0. So our wall will move, count down to 0, and then move again. This timer will trigger those movement states to change. In your Timer node’s Signals panel, connect the timeout() signal to your script.

Godot 2D Platformer

This will create a func _on_timer_timeout(): function in your script. In this function, we will change our platforms moving states. Before we do that, we first need to define our movement states. Our platform will move vertically, waiting at the bottom, then moving up, waiting at the top, and moving back down, in a loop.

To change our platform’s movement states, we need to create an Enumeration which will store these states. Enums also referred to as enumerations, are a data type that contains a fixed set of constants.



    ### Platform.gd 

    extends Area2D

    #platform movement states
    enum State {WAIT_AT_BOTTOM, MOVING_UP, WAIT_AT_TOP, MOVING_DOWN}


Enter fullscreen mode Exit fullscreen mode

Next, in our _on_timer_timeout(): function, we can change our states. If the platform is in the WAIT_AT_TOP state, it should change its state to MOVING_DOWN. If the platform is in the WAIT_AT_BOTTOM state, it should change its state to MOVING_UP. We’ll need to create a variable that captures our platform’s state when the game starts as well as when this state changes. We’ll initially set it to WAIT_AT_BOTTOM.



    ### Platform.gd 

    extends Area2D

    #platform movement states
    enum State {WAIT_AT_BOTTOM, MOVING_UP, WAIT_AT_TOP, MOVING_DOWN}

    #captures current state of platform
    var current_state = State.WAIT_AT_BOTTOM

    #platform direction changes on timer timeout      
    func _on_timer_timeout():
        if current_state == State.WAIT_AT_TOP:
            switch_state(State.MOVING_DOWN)

        if current_state == State.WAIT_AT_BOTTOM:
            switch_state(State.MOVING_UP)


Enter fullscreen mode Exit fullscreen mode

We’ll also need to capture the y position of our platform when the game starts because it is the y value that we will change when the platform’s movement state changes. Let’s create a new variable that will set the platform’s y position when the game is loaded.



    ### Platform.gd 

    extends Area2D

    #platform movement states
    enum State {WAIT_AT_BOTTOM, MOVING_UP, WAIT_AT_TOP, MOVING_DOWN}

    #captures current state of platform
    var current_state = State.WAIT_AT_BOTTOM

    #movement position 
    var initial_position


Enter fullscreen mode Exit fullscreen mode

We will set the value of this variable in the built-in ready() function because this function is called when the node is added to the scene. In this function, we will set the initial_position variable to the platform’s current y position and set its state to MOVING_UP because our current_state is WAIT_AT_BOTTOM — and we want our state to change as soon as the game loads. We can find the position of a node by accessing the node’s position property.



    ### Platform.gd 

    #older code

    #sets platforms y position on game start and switches the state
    func _ready():
        initial_position = position.y
        switch_state(State.MOVING_UP)


Enter fullscreen mode Exit fullscreen mode

Now, we also need to give our platform a few other variables, which will define its movement speed (how fast it moves), movement range (how far up and down it goes), progress (if it has finished moving or not), and top and bottom wait times (how long it waits before moving up and down). We will export the variables which we want to change in the Inspector panel. This will allow us to change each instanced platform’s movement values individually — i.e., some platforms will move quicker, wait longer, or go up higher than others.



    ### Platform.gd

    extends Area2D

    #platform movement states
    enum State {WAIT_AT_BOTTOM, MOVING_UP, WAIT_AT_TOP, MOVING_DOWN}

    #captures current state of platform
    var current_state = State.WAIT_AT_BOTTOM

    #movement position and movement progress value
    var initial_position
    var progress = 0.0

    #platforms movement speed and range
    @export var movement_speed = 50.0
    @export var movement_range = 50

    #wait times
    @export  var wait_time_at_top = 3.0 # Time in seconds to wait at top
    @export var wait_time_at_bottom = 3.0 # Time in seconds to wait at top


Enter fullscreen mode Exit fullscreen mode

We need to create a function that will continuously switch the state of the platform. Depending on the new state, it should reset the platform’s movement progress, set the timer and its wait_time (which is the seconds it takes to countdown), and changes the current_state variable. We will use a match statement to check and change the states of our platform. A match statement is used to branch the execution of a program. It’s the equivalent of the switch statement found in many other languages but offers some additional features.



    ### Platform.gd 

    #older code

    #changes the platforms movement states
    func switch_state(new_state):
        current_state = new_state
        match new_state:
            #if state is moving up, reset progress
            State.MOVING_UP:
                progress = 0.0

            #if state is waiting at top, start the timer to change the state
            State.WAIT_AT_TOP:
                $Timer.wait_time = wait_time_at_top #will wait x seconds before moving
                $Timer.start()

            #if state is waiting at bottom, start the timer to change the state
            State.WAIT_AT_BOTTOM:
                $Timer.wait_time = wait_time_at_bottom #will wait x seconds before moving
                $Timer.start()

            #if state is moving down, move the platform via the speed and range defined
            State.MOVING_DOWN:
                progress = movement_range / movement_speed


Enter fullscreen mode Exit fullscreen mode

Finally, we can call our newly created switch_state() function in our physics_process() function. This function is called every physics frame. We will use it to check the current state of the platform and move it or wait based on the state. The Lerp function is used to smoothly interpolate between the bottom and top positions.



    ### Platform.gd 

    #older code

    #moves our platform
    func _physics_process(delta):
        match current_state:
            #if its moving up
            State.MOVING_UP:
                progress += delta
                #change its position
                position.y = lerp(initial_position, initial_position - movement_range,   progress / (movement_range / movement_range))
                if progress >= (movement_range / movement_speed):
                    switch_state(State.WAIT_AT_TOP)

            #if its moving down
            State.MOVING_DOWN:
                progress -= delta
                #change its position
                position.y = lerp(initial_position, initial_position - movement_range, progress / (movement_range / movement_speed))
                if progress <= 0:
                    switch_state(State.WAIT_AT_BOTTOM)


Enter fullscreen mode Exit fullscreen mode

In the script, we move our platform vertically between two points, with waiting times at the top and bottom. The movement is smooth due to the use of linear interpolation (lerp), and the states ensure the different phases of the movement are handled correctly.

Your code should look like this.

If you instance your Platform in your Main_2 scene, you can see that you can change its values in the Inspector panel. Make sure that you place it down on the floor and set its movement range to stop when it reaches the floor above it — you want the edges of your platform to connect with the floor above and below. You can test these values by pressing F6 to test out the current scene.

Godot 2D Platformer

It should be placed like this:

Godot 2D Platformer

And its movement range should be changed so that it stops like this:

Godot 2D Platformer

I organized my instanced platforms in a Node2D node and changed each platform’s speed, range, and wait_time to be unique.

Godot 2D Platformer

You can now go ahead and create your map for your second level. You can do this by painting over your existing tiles in your Level node. Remember to leave enough space for your wall to move up and down since it will block two-floor levels.

This is what I ended up creating for my second level:

Godot 2D Platformer

If you run your scene, your player should be able to make it to the top without issues, and your Platforms should move!

Godot 2D Platformer

Troubleshooting: Fixing The Player’s Jumping Glitch

If you’ve tested your game, you would probably have noticed a slight glitch that occurs when your player jumps and changes directions. The player jumps but if you press the inputs to turn left or right it changes directions slightly before continuing to jump. We want the player to first finish the jumping animation, and then run in the new direction, instead of changing direction mid-jump. To do this, we need to fix our player_animations() function to only run if the player is not jumping. Before we can do this, let’s create a new variable in our Global script to keep track of our jumping state.



    ### Global.gd

    extends Node

    #movement states
    var is_attacking = false
    var is_climbing = false
    var is_jumping = false


Enter fullscreen mode Exit fullscreen mode

Then, in our player_animations() function we only want to fire off our run animation if our player is not jumping.



    ### Player.gd

    #older code

    #animations
    func player_animations():
        #on left (add is_action_just_released so you continue running after jumping)
        if Input.is_action_pressed("ui_left") && Global.is_jumping == false:
            $AnimatedSprite2D.flip_h = true
            $AnimatedSprite2D.play("run")
            $CollisionShape2D.position.x = 7

        #on right (add is_action_just_released so you continue running after jumping)
        if Input.is_action_pressed("ui_right") && Global.is_jumping == false:
            $AnimatedSprite2D.flip_h = false
            $AnimatedSprite2D.play("run")
            $CollisionShape2D.position.x = -7

        #on idle if nothing is being pressed
        if !Input.is_anything_pressed():
            $AnimatedSprite2D.play("idle")


Enter fullscreen mode Exit fullscreen mode

Next, we’ll need to change the state of our is_jumping variable to true if our player is jumping. We’ll also need to reset this value back to false when our gravity value resets.



    ### Player.gd

    #older code

    #singular input captures
    func _input(event):
        #on attack
        if event.is_action_pressed("ui_attack"):
            Global.is_attacking = true
            $AnimatedSprite2D.play("attack")        

        #on jump
        if event.is_action_pressed("ui_jump") and is_on_floor():
            velocity.y = jump_height
            $AnimatedSprite2D.play("jump")

        #on climbing ladders
        if Global.is_climbing == true:
            if Input.is_action_pressed("ui_up"):
                $AnimatedSprite2D.play("climb") 
                gravity = 100
                velocity.y = -160
                Global.is_jumping = true

        #reset gravity
        else:
            gravity = 200
            Global.is_climbing = false  
            Global.is_jumping = false


Enter fullscreen mode Exit fullscreen mode

If you run your scene now your player should jump and change running animations appropriately!

Congratulations on creating your second level with moving platforms! You also learned to work with Layers, and hopefully, you ended up with a cool map! You can go ahead and create a 3rd and 4th level with more platforms, and more spaces for enemies if you’d like. Speaking of enemies, we will create our Bomb and the Bomb Spawner in the next part. This bomb spawner will spawn a bomb that will travel along a certain path and explodes if it reaches the end or if it collides with the player.

Now would be a good time to save your project and make a backup of your project so that you can revert to this part if any game-breaking errors occur. Go back and revise what you’ve learned before you continue with the series, and once you’re ready, I’ll see you in the next part!


Next Part to the Tutorial Series

The tutorial series has 24 chapters. I’ll be posting all of the chapters in sectional daily parts over the next couple of weeks. You can find the updated list of the tutorial links for all 24 parts of this series on my GitBook. If you don’t see a link added to a part yet, then that means that it hasn’t been posted yet. Also, if there are any future updates to the series, my GitBook would be the place where you can keep up-to-date with everything!

Godot 2D Platformer


Support the Series & Gain Early Access!

If you like this series and would like to support me, you could donate any amount to my KoFi shop or you could purchase the offline PDF that has the entire series in one on-the-go booklet!

The booklet gives you lifelong access to the full, offline version of the “Learn Godot 4 by Making a 2D Platformer” PDF booklet. This is a 451-page document that contains all the tutorials of this series in a sequenced format, plus you get dedicated help from me if you ever get stuck or need advice. This means you don’t have to wait for me to release the next part of the tutorial series on Dev.to or Medium. You can just move on and continue the tutorial at your own pace — anytime and anywhere!

Godot 2D Platformer

This book will be updated continuously to fix newly discovered bugs, or to fix compatibility issues with newer versions of Godot 4.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .