Learn Godot 4 by Making a 2D Platformer — Part 11: Box & Box Spawner

christine - Jul 28 '23 - - Dev Community

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

If we only had our bomb spawner, our game would be pretty easy to beat. That’s why we need some sort of secondary enemy that would make our game more difficult. In this part, we’ll be adding our secondary enemy, which is a box spawner who will throw boxes at us from the side. This spawner is just like our bomb spawner, but it will serve as a threat to us on multiple platforms.


WHAT YOU WILL LEARN IN THIS PART:

  • How to change set properties via code.
  • How to create independent spawners from a singular scene.

STEP 1: BOX SCENE SETUP

Our Box Scene will be the same as our Bomb Scene, so let’s duplicate our Bomb Scene and rename it to “Box”.

Godot 2D Platformer

Godot 2D Platformer

In our newly created Box scene, let’s detach all the existing scripts and signals from our nodes.

Godot 2D Platformer

Godot 2D Platformer

Now, let’s attach a new Script to our Box scene and save it under our Scripts folder.

Godot 2D Platformer

Connect the Area2D node’s (the root node) body_entered() signal to your script.

Godot 2D Platformer

We’ll also go ahead and connect our AnimatedSprite2D node’s animation_finished() signal to our script. We’ll get more into how we’ll be using this newly created _on_animated_sprite_2d_animation_finished() function later on.

Godot 2D Platformer

The Box Scene should look like this:

Godot 2D Platformer

In the AnimatedSprite2D node’s Animations panel, let’s delete the existing animation frames. Don’t delete the animations, just the existing sprites.

Godot 2D Platformer

Change both your moving and explode animations’ FPS value to 1. For your moving animation, navigate to “res://Assets/Kings and Pigs/Sprites/08-Box/Idle.png” and crop out the singular frame for your animation.

Godot 2D Platformer

For your explode animation, navigate to “res://Assets/Kings and Pigs/Sprites/08-Box/Hit.png” and crop out the singular frame for your animation. Disable its looping value.

Godot 2D Platformer

STEP 2: BOXSPAWNER SCENE SETUP

Our BoxSpawner will be similar to our BombSpawner scene. Duplicate the BombSpawner scene and rename the duplicated scene to “BoxSpawner”.

Godot 2D Platformer

In our newly created BoxSpawner scene, let’s detach all the existing scripts and signals from our nodes. We also need to remove the CannonHandler from our scene.

Godot 2D Platformer

It should look like this:

Godot 2D Platformer

Now, let’s attach a new Script to our BoxSpawner scene and save it under our Scripts folder.

Godot 2D Platformer

We also need to connect our Timer node’s timeout() signal to our BoxSpawner script.

Godot 2D Platformer

Instead of spawning our Box onto our Path added to our Main scene, we will spawn it on a Path added directly in our BoxSpawner scene. Add a new Node2D node and rename it to “BoxPath”.

Godot 2D Platformer

Then add the following nodes — just like we did in our Main scenes — as children to your BoxPath node: Path2D > PathFollow2D & AnimationPlayer.

Godot 2D Platformer

The BoxSpawner Scene should look like this:

Godot 2D Platformer

In the AnimatedSprite2D nodes Animations panel, let’s delete all of the existing animations.

Godot 2D Platformer

Create two new animations: pig_idle and pig_throw.

Godot 2D Platformer

For your pig_idle animation, navigate to “res://Assets/Kings and Pigs/Sprites/04-Pig Throwing a Box/Idle (26x30).png” and crop out all 9 frames for your animation. Change its FPS to 9, and leave the looping value enabled.

Godot 2D Platformer

For your pig_throw animation, navigate to “res://Assets/Kings and Pigs/Sprites/04-Pig Throwing a Box/Throwing Box (26x30).png” and crop out all 5 frames for your animation. Make sure the first frame is the same as the last frame by copying and pasting it. Change its FPS to 6, and disable its looping value

Godot 2D Platformer

We also need to add our movement animation in our AnimationPlayer node. We did this before in our Main scene when we created our BombSpawner. Select your AnimationPlayer node in your BoxSpawner scene and create a new animation called “box_movement”.

Godot 2D Platformer

Add a new Property Track animation and connect it to your PathFollow2D node. We want to animate the progress_ratio property of our PathFollow2D node.

Godot 2D Platformer

Add two keyframes at the start and end of your animation. Change the Value property of your last keyframe (at time 1) to be equal to 1.

Godot 2D Platformer

Finally, let’s change the animation time of our timeline. I changed mine to 10 since this is the speed that I want my box to move from point A to point B.

Godot 2D Platformer

Godot 2D Platformer

Now let’s create our path. Draw two Points on your Path2D node (select the Curve2D property in your Inspector panel to add new Points) from the start of your pig to the end of the blue border on your scene.

Godot 2D Platformer

STEP 3: BOXSPAWNER & BOMBSPAWNER COLLISIONS

Currently, if our player runs over our BombSpawner at the top of our scene, they’ll just run through our cannon. We don’t want that to be possible. Instead, we want our BoxSpawner and our BombSpawner to be able to block our player so that they can’t just run through them.

In your BombSpawner, add a StaticBody2D node and rename it to “Collisions”. We are using this node since it can hold a CollisionShape2D node without us having to programmatically tell it what to block, and it won’t process any movement.

Godot 2D Platformer

Add a CollisionShape2D node and outline your CannonHandler plus your cannon with it.

Godot 2D Platformer

Do the same for your BoxSpawner scene and outline your pig with your CollisionShape.

Godot 2D Platformer

Godot 2D Platformer

STEP 4: BOX SCENE SCRIPTING

We need our box to only spawn at the start of the box spawner path (progress_ratio == 0). If our box hits our player, the box should be destroyed and removed from our scene. The box should then only be allowed to respawn when the path restarts (progress_ratio == 0). In our Global script, let’s create two new functions that will enable or disable our box’s spawning. In these functions, we will simply switch a boolean variable to indicate if our spawning ability is true or false.

    ### Global.gd

    extends Node

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

    # Indicates if box can be spawned
    var can_spawn = true

    #current scene
    var current_scene_name

    #bomb movement state
    var is_bomb_moving = false

    func _ready():
        # Sets the current scene's name
        current_scene_name = get_tree().get_current_scene().name

    # Function to disable box spawning
    func disable_spawning():
        can_spawn = false

    # Function to enable box spawning
    func enable_spawning():
        can_spawn = true
Enter fullscreen mode Exit fullscreen mode

Now in our Box.gd script, let’s disable spawning if our box hits our player. Disabling our spawner here will prevent our boxes from respawning if it hasn’t finished following the spawner path (progress_ratio == 1). So if a box hits us in the middle of the platform it won’t respawn behind us to continue the path. It will wait until the progress_ratio == 0 again to respawn. We’ll also set our default animation on Box spawning to be our moving animation.

    ### Box.gd

    extends Area2D

    # Default animation on spawn
    func _ready():
        $AnimatedSprite2D.play("moving")

    func _on_body_entered(body):
        # If the bomb collides with the player, play the explosion animation and disable spawning
        if body.name == "Player":
            $AnimatedSprite2D.play("explode")
            # Disable spawning in BoxSpawner
            Global.disable_spawning()
Enter fullscreen mode Exit fullscreen mode

If our box hits our wall, we want to remove it immediately so that our BoxSpawner can respawn a new box.

    ### Box.gd

    extends Area2D

    # Default animation on spawn
    func _ready():
        $AnimatedSprite2D.play("moving")

    func _on_body_entered(body):
        # If the bomb collides with the player, play the explosion animation and disable spawning
        if body.name == "Player":
            $AnimatedSprite2D.play("explode")
            # Disable spawning in BoxSpawner
            Global.disable_spawning()

        # If the bomb collides with our Wall scene, remove so that it can be respawned
        if body.name.begins_with("Wall"):
            queue_free()
Enter fullscreen mode Exit fullscreen mode

Then in our _on_animated_sprite_2d_animation_finished() function, we will check if our explode animation is playing, and if it’s true we will remove the box from the scene. The _on_animated_sprite_2d_animation_finished() function is called when the animation on the AnimatedSprite2D node finishes playing.

We’re removing our box immediately in our Wall conditional since we want to respawn our box immediately. We’re removing our box in our Player conditional via our _on_animated_sprite_2d_animation_finished() function because we want the explode animation to play and then only after it’s finished playing should the box be removed from our scene.

    ### Box.gd

    #older code

    #if the box's explode animation is playing, remove it from the scene
    func _on_animated_sprite_2d_animation_finished():
        if $AnimatedSprite2D.animation == "explode":
            queue_free()
Enter fullscreen mode Exit fullscreen mode

We are done with our Box script for now. In this script, we played the appropriate animations, removed the box from the scene, and disabled our spawning for when the box collides with objects.

STEP 5: BOXSPAWNER SCENE SCRIPTING

Our BoxSpawner will work similarly to our BombSpawner. Let’s define the variables (just like in our BombSpawner script) that will hold the BoxPath nodes and reference our Box scene. We’ll also initiate their variables in our ready() function.

    ### BoxSpawner.gd

    extends Node2D

    # Box scene reference
    var box = preload("res://Scenes/Box.tscn")

    # References to our scene, PathFollow2D path, and AnimationPlayer path
    var box_path
    var box_animation

    # When it's loaded into the scene
    func _ready():
        # Initiates paths
        box_path = $BoxPath/Path2D/PathFollow2D
        box_animation = $BoxPath/Path2D/AnimationPlayer
        #enables box movement on path on load
        box_animation.play("box_movement")
        #default animation on load
        $AnimatedSprite2D.play("pig_throw")
Enter fullscreen mode Exit fullscreen mode

Then, in our _on_timer_timeout() function, we will spawn a box instance on our box_path if our box is enabled to spawn and if no boxes have been spawned onto the path already. We did something similar to this in our BombSpawner script.

    ### BoxSpawner.gd

    #older code

    # Shoot and spawn box onto path
    func _on_timer_timeout():
        # Reset animation back to idle if not throwing
        $AnimatedSprite2D.play("pig_idle")

        # Spawns a box onto our path if there are no boxes available and can_spawn is True
        if box_path.get_child_count() <= 0 and Global.can_spawn == true:
            var spawned_box = box.instantiate()
            box_path.add_child(spawned_box)
Enter fullscreen mode Exit fullscreen mode

Now, since we’ll be putting our pigs on each side of our map to throw boxes at us from the left and right sides, we’ll need to find a way to flip our AnimatedSprite2D node around so that our pig can face the other side. We can do this by enabling/disabling the Flip_H and Flip_V values of our scene.

  • bool flip_h — If true texture is flipped horizontally.

  • bool flip_v — If true texture is flipped vertically.

    ### BoxSpawner.gd

    #older code

    # Allows us to flip our pigs around in the editor
    @export var flip_h = false
    @export var flip_v = false
Enter fullscreen mode Exit fullscreen mode

Now if you click on the root node of your BoxSpawner, you can enable/disable their Flip_H and Flip_V values, but this won’t do anything yet.

Godot 2D Platformer

Godot 2D Platformer

We’ll change the flip_h and flip_v values of our AnimatedSprite2D node in our process() function. We’ll also check if our path has reached the end of the progress_ratio value (>=1) and if true, we will enable the Box’s spawning value and respawn a new box.

    ### BoxSpawner.gd

    #older code

    func _process(delta):
        #allow us to flip the pigs around in editor
        $AnimatedSprite2D.flip_h = flip_h
        $AnimatedSprite2D.flip_v = flip_v

        # Check if the boxes have reached the end of the path
        if box_path.progress_ratio >= 1:
            #respawn box
            box_path.progress_ratio = 0
            Global.enable_spawning()
            box_animation.play("box_movement")  

        #play throwing animation if path resets 
        if box_path.progress_ratio == 0:    
            $AnimatedSprite2D.play("pig_throw")
Enter fullscreen mode Exit fullscreen mode

Your code should look like this.

STEP 6: ADDING BOXSPAWNERS IN OUR LEVEL

In your Main scene, you can now instance your BoxSpawner. You can instance as many BoxSpawners as you’d like — just make sure to organize them underneath a Node2D node called “BoxSpawners” for better organization.

Godot 2D Platformer

You can flip the sprite of your selected BoxSpawner around by enabling/disabling its flip_h and flip_v values in the Inspector panel. For the spawners on the left side of the map, I enabled my Flip H value. For the spawners on the right side of the map, I enabled my Flip H and Flip V values.

Godot 2D Platformer

We’re enabling the spawners on the left side of the map’s Flip_H values because we need to rotate them around to -180 degrees. This is because we need their Path2D node to spawn boxes towards the right, and not the left. You can rotate the spawners that you place on the left side of your map via the rotate tool, or you can change their rotation value to -180 in the Inspector panel underneath Transform.

Godot 2D Platformer

This is what I ended up with in my Main scene:

Godot 2D Platformer

This is what I ended up with in my Main_2 scene:

Godot 2D Platformer

Now if you run your scene, your boxes should spawn and your BoxSpawners should be spawning and facing in the right direction. If your player runs into the box, the box should be removed and only respawned when the path restarts (after all the other boxes reach the end of their paths).

Congratulations on creating your secondary enemy! In the next part, we’ll be setting up the functionalities for our player to be damaged by the boxes and the bombs. 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.

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