*You can find the links to the previous parts at the bottom of this tutorial.
We now have a bomb in our game, but we still need a spawner for it. The spawner will spawn the bomb on a certain path — and it will respawn a new bomb each time the bomb collides with the player or with the wall/tilemap. Instead of placing the bomb down on the maps ourselves, the spawner will take care of it for us.
WHAT YOU WILL LEARN IN THIS PART:
- How to work with Path2D nodes.
- How to work with PathFollow2D nodes.
- How to work with AnimationPlayer nodes.
We will be creating a pre-defined path for our bomb using the Path2D node. This Path2D node allows us to draw points on the map which will serve as our bomb path. After we’ve created our path points, we will use the PathFollow2D node to move our spawned Bomb scene along this path. We will need to use the AnimationPlayer node to animate our bomb’s movement as it follows this path.
Let’s create a new scene for our Bomb Spawner! Our root node should be a Node2D node — which you can rename as “BombSpawner”. Save this scene under your Scenes folder.
This BombSpawner scene will need a Timer node, which will spawn a bomb each time the bomb count falls below zero (if there are no bombs, the timer will spawn a new one).
In the Timer node’s Inspector panel, we need to enable the “Autostart” option. This will start the timer automatically when the BombSpawner enters the Main scene tree. The other option “one-shot” fire off the timer once, and the timer will stop when reaching 0.
Finally, this scene will need an AnimatedSprite2D node. This AnimatedSprite2D node will contain the animations for our cannon which will shoot the bomb. We will add the cannon handler in the next part!
Let’s create two new animations for our cannon called “cannon_idle” and “cannon_fired”.
For our cannon_idle animation, navigate to “res://Assets/Kings and Pigs/Sprites/10-Cannon/Idle.png” and add the only frame to your animation. Leave the looping and FPS values as the default values for now.
For our cannon_fired animation, navigate to “res://Assets/Kings and Pigs/Sprites/10-Cannon/Shoot (44x28).png” and add all 4 frames to your animation. Add the first frame twice, so that the cannon animation starts and ends with the same frame. Turn the looping off, and leave change its FPS value to 4.
We also want our cannon to face the other way (right), so in the AnimatedSprite2D node’s Inspector panel, underneath Offset, enable the “Flip H” property.
We’ll come back to our BombSpawner scene in a minute, but for now, let’s head back to our Main scene and add a new Node2D node to our scene. Rename it to “BombPath”. Also, delete any Bomb scene that you’ve previously instanced from your scene.
Add a Path2D node as a child of the BombPath node. This node will allow us to draw path points for our bomb’s movement.
If you select your Path2D node, you will see in the Inspector panel that there is an option to add Points. If you add an element to the Points property, you will see that it created a diamond shape on your game window at (x: 0, y: 0).
You can drag this point anywhere on your map where you want your bomb to spawn. This is the starting point of your bomb path. You can move it around later on.
If you add another element, it will create another point.
You can also click on the line now to add points, and if you want to move points you can simply select them and drag them to where you want to place them. To delete a point, you can right-click on it with your mouse or delete it in your Inspector panel.
Let’s create a path from the top to the bottom for our bomb. The bomb will follow this path each time it spawns, and if it reaches the end of this path or collides with our player, it will respawn from the top and make its way down to the end again. Make sure that you leave enough space for your player to be able to jump over the bomb, and if you made your bomb collide with your Tilemap (option #1) — ensure that you have enough space for your bomb to flow without exploding.
For our bomb to follow this path after it has been spawned on it, we need to add a PathFollow2D node as a child of the Path2D node.
We also need to add an AnimationPlayer node as a child of the Path2D node. An animation player is used for general-purpose playback of Animation resources. It contains a dictionary of AnimationLibrary resources and custom blend times between animation transitions. You will use this AnimationPlayer node whenever you need to animate non-sprite items, such as labels or colors.
We add animations to the AnimationPlayer node in the Animation panel below.
To add a new Animation, click on the Animation property and select the option “New”. Call this animation “bomb_movement”.
This will create an animation consisting of four parts:
The controls (i.e. add, load, save, and delete animations)
The tracks listing.
The timeline with keyframes — which defines the value of a property at a point in time.
The timeline and track controls, where you can zoom the timeline and edit tracks, for example. You can also zoom in and out on the timeline by holding down CRTL and scrolling with your mouse wheel.
Let’s change our animation length to 5. This will be the time it takes for our bomb to move from the first point on our Path2D node to the last point. We can change this later to make it slower or faster.
Let’s create a new Track. Each track stores a path to a node and its affected property. Click on “Add Track” and select “Property Track”. We are choosing Property Track because we want to change the property value of a certain node (properties that we can find in the Inspector panel).
We want to animate the property of the progress ratio of our PathFollow2D node so that we can move the Bomb across the path according to that progress ratio. Since we want to animate the PathFollow2D node, we need to select it in the popup window that arises.
We can now select the property of the PathFollow2D node that we want to animate. Select “progress ratio”. This progress ratio refers to the distance along the path as a number in the range of 0.0 (for the first vertex) to 1.0 (for the last). This is just another way of expressing the progress within the path, as the offset supplied is multiplied internally by the path’s length.
Let’s add two keyframes: one at the start and one at the end of the animation timeline. To add a new keyframe, right-click on your mouse and select “Insert Key”. Add a key at point 0 and point 5 on your timeline. 0 is our start location, where the bomb will spawn and start moving, and 5 is the end location, where the bomb will be destroyed and stop moving.
Right now the animation won’t move because the progress ratio is moving from value 0 to value 0. If you select your keyframe, you can see this value in the Inspector panel. We need to change the Value property of our keyframe “5” to 1. This means our animation will play from 0 to 1, so our bomb will move along the path and not just stay stuck in place. Select the keyframe that you placed on time 5 and change its value to 1. We can’t test this animation yet, but we need it or else our Bomb won’t move on our path!
Redo the same steps as above (for our BombPath) for your second level in your Main_2 scene.
Now, back in our BombSpawner scene, we need to add a new script to our scene. Save it in your Scripts folder.
In our newly created script, we need to define a few variables. First, we’ll need to create a reference to our Bomb scene. We can do this via the global preload method, which loads our Scene resource as early as possible to front-load the “loading” operations and avoid loading resources while in the middle of performance-sensitive code. Preloading makes sure that the scene is loaded into memory before the game starts, which can help to avoid delays later.
### BombSpawner.gd
extends Node2D
#bomb scene reference
var bomb = preload("res://Scenes/Bomb.tscn")
Since we have more than one level we’ll need to find a way to dynamically change the name of the current scene so that we can get its path accordingly. In other words, if we are in our Main scene, we want our bomb to spawn on our “/root/Main/BombPath/Path2D/PathFollow2D” path, and if we are in our Main_2 scene, we want our bomb to spawn on our “/root/Main_2/BombPath/Path2D/PathFollow2D” path.
We can use our Global script to keep track of which main scene is currently active. We’ll use our ready() function to get the name of the current scene that our Player is in (Main or Main_2). We’ll do this via the current_scene() object.
### Global.gd
extends Node
#movement states
var is_attacking = false
var is_climbing = false
var is_jumping = false
#current scene
var current_scene_name
func _ready():
# Sets the current scene's name
current_scene_name = get_tree().get_current_scene().name
Now, back in our BombSpawner script let’s define some variables that will store our current_scene_path value (Main or Main_2), our bomb path (current_scene_path + /BombPath/Path2D/PathFollow2D), and our bomb animation path (current_scene_path + /BombPath/Path2D/AnimationPlayer).
### Bombspawner.gd
extends Node2D
#Bomb scene reference
var bomb = preload("res://Scenes/Bomb.tscn")
#references to our scene, PathFollow2D path, and AnimationPlayer path
var current_scene_path
var bomb_path
var bomb_animation
We will initiate the values of these variables in our ready() function. We also need to set the default load animation of our cannon to “cannon_idle”.
### Bombspawner.gd
#older code
#when it's loaded into the scene
func _ready():
#default animation on load
$AnimatedSprite2D.play("cannon_idle")
#initiates paths
current_scene_path = "/root/" + Global.current_scene_name + "/" #current scene
bomb_path = get_node(current_scene_path + "/BombPath/Path2D/PathFollow2D") #PathFollow2D
bomb_animation = get_node(current_scene_path + "/BombPath/Path2D/AnimationPlayer") #AnimationPlayer
To spawn our bomb we’ll need to create a new function that will create an instance of the bomb scene and return it. To create an instance of our scene reference we use the instantiate() method. We also need to play our cannon_fired animation, which will play each time our function is called to spawn a new bomb.
### Bombspawner.gd
#older code
#spawns bomb instance
func shoot():
#play cannon shoot animation each time the function is fired off
$AnimatedSprite2D.play("cannon_fired")
#returns spawned bomb
var spawned_bomb = bomb.instantiate()
return spawned_bomb
To spawn a bomb via our shoot function, we’ll need to connect our Timer node’s timeout() signal to our script.
In the _on_timer_timeout() function we need to check if there are no bombs currently spawned on our scene, and if it is true, we will call our shoot() function and spawn a bomb onto our bomb_path. We can check the children of our “/BombPath/Path2D/PathFollow2D/” node via the get_child or get_child_count methods. You can add a node to a defined path (such as our bomb_path) by using the add_child method. This will create a Bomb under our path, so our path will end up looking like “/BombPath/Path2D/PathFollow2D/Bomb”.
### Bombspawner.gd
#older code
#shoot and spawn bomb onto path
func _on_timer_timeout():
#reset animation before shooting
$AnimatedSprite2D.play("cannon_idle")
#spawns a bomb onto our path if there are no bombs available
if bomb_path.get_child_count() <= 0:
bomb_path.add_child(shoot())
Finally, we need to check the path for any changes. If the bomb has reached the end of the path (when the animation reaches the 1 value), we need to reset the path value back to 0 so that the animation can restart, as well as spawn a new bomb.
For this to work properly, we need to create a Global variable that will change the state of our bomb movement. If our bomb is spawned, it should be moving, and if it collides with our player, it should not be moving.
### Global.gd
extends Node
#movement states
var is_attacking = false
var is_climbing = false
var is_jumping = false
#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
### Bomb.gd
#older code
func _on_body_entered(body):
#if the bomb collides with the player, play the explosion animation and start the timer
if body.name == "Player":
$AnimatedSprite2D.play("explode")
$Timer.start()
Global.is_bomb_moving = false
#OPTION 1
#if the bomb collides with our Level Tilemap (floor and walls).
if body.name == "Level" and !body.name.begins_with("Platform"):
$AnimatedSprite2D.play("explode")
$Timer.start()
Global.is_bomb_moving = false
#OPTION 2
#if the bomb collides with our Wall scene, explode and remove
if body.name.begins_with("Wall"):
$AnimatedSprite2D.play("explode")
$Timer.start()
Global.is_bomb_moving = false
### BombSpawner.gd
#older code
#spawns bomb instance
func shoot():
#play cannon shoot animation each time the function is fired off
$AnimatedSprite2D.play("cannon_fired")
#sets the bomb to moving and plays bomb animation
Global.is_bomb_moving = true
bomb_animation.play("bomb_movement")
#returns spawned bomb
var spawned_bomb = bomb.instantiate()
return spawned_bomb
Now, in our _on_timer_timeout() function, we can check if our bomb is not moving and if true we need to remove all of our bombs and stop our animation from playing so that our progress ratio can reset back to 0. We can do this by running a For Loop which will count the children inside our bomb_path, and then remove them all if any exist.
### BombSpawner.gd
#older code
#shoot and spawn bomb onto path
func _on_timer_timeout():
#reset animation before shooting
$AnimatedSprite2D.play("cannon_idle")
#spawns a bomb onto our path if there are no bombs available
if bomb_path.get_child_count() <= 0:
bomb_path.add_child(shoot())
# Clear all existing bombs
if Global.is_bomb_moving == false:
for bombs in bomb_path.get_children():
bomb_path.remove_child(bombs)
bombs.queue_free()
bomb_animation.stop()
Finally, we need our AnimationPlayer from our level scenes to play our bomb_movement animation at the start. This will allow the bomb to move when our BombSpawner enters the scene.
### Bombspawner.gd
#older code
#when it's loaded into the scene
func _ready():
#default animation on load
$AnimatedSprite2D.play("cannon_idle")
#initiates paths
current_scene_path = "/root/" + Global.current_scene_name + "/" #current scene
bomb_path = get_node(current_scene_path + "/BombPath/Path2D/PathFollow2D") #PathFollow2D
bomb_animation = get_node(current_scene_path + "/BombPath/Path2D/AnimationPlayer") #AnimationPlayer
#starts bomb movement
bomb_animation.play("bomb_movement")
Now, in your Main scenes, instance your BombSpawner scene. You want to place your cannon in front of your bomb path.
Main.tscn:
Main_2.tscn:
Now if you run your scene, your bomb should spawn, and if it explodes along the path it should respawn. The bomb is moving a bit too fast, so let’s change the bomb_movement animation in our Main scene’s length to be a little higher. You’ll have to test this value out depending on the length of your path.
Main.tscn:
Main_2.tscn:
If you notice that your player keeps colliding with the bomb (i.e., they can’t jump over it), consider changing your player’s jump_height variable to a value that’s a little lower such as -110. You can also change your Bomb node’s collision shape to be slightly smaller. You can also do the same for your Player’s collision shape.
Let’s do one last thing for this part. Let’s make the Bomb roll if it is moving. We can do this by adding to its rotate value in the physics_process() function. In your Bomb script, create a new variable and set its speed to the speed at which you want your bomb to rotate.
### Bomb.gd
extends Area2D
var rotation_speed = 10
Then, in your physics_process() function, let’s rotate the bomb if it is moving.
### Bomb.gd
#older code
#rolls the bomb
func _physics_process(delta):
# Rotate the bomb if it hasn't exploded
if Global.is_bomb_moving == true:
$AnimatedSprite2D.rotation += rotation_speed * delta
Your code should look like this.
Now if you run your scenes, your bomb should roll to the endpoint. If it collides with the colliders that you defined in your Bomb script, it should remove itself from the scene and the spawner should respawn a new bomb. Your player should also be able to jump over the bomb. There is a little spawn delay when the cannon first fires off when the game starts, but it isn’t deal-breaking.
Congratulations on creating your bomb spawner! In the next part, we will create a cannon handler that will have a speech bubble in which it will taunt us — other than that its purpose in our game is purely for visuals!
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!
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!
This book will be updated continuously to fix newly discovered bugs, or to fix compatibility issues with newer versions of Godot 4.
Top comments (0)