12.coding-the-spawners

Coding the spawners

In this lesson, we will create spawner nodes.

Spawners are little utility nodes whose function is to create other nodes. For example, it could be a pit or a portal that periodically makes new mobs appear in a game level.

But let’s first talk about why we would need these spawners.

Why do we need a spawner?

You’ve spawned random scenes before. In the Random Rocks series, you wrote this code:

# We use the `ROCKS` array to pick a random scene to vary the visuals that we
# place on the grid.
const ROCKS := [
    preload("rocks/Rock1.tscn"),
    preload("rocks/Rock2.tscn"),
    preload("rocks/Rock3.tscn"),
]
# ...
# Creates and returns a new random rock instance.
func get_random_rock() -> Sprite:
    # We first calculate a random index in the ROCKS array.
    var rock_random_index := randi() % ROCKS.size()
    # Then, we get the preloaded scene in the array and create a new instance of
    # it.
    return ROCKS[rock_random_index].instance()

And it works perfectly fine. But what if we wanted to reuse this code to spawn different things, like mobs or pickups?

One thing we could do is export the array of scenes to spawn. Then, it’d be possible to edit it without code, directly from the Inspector:

# Notice the export keyword.
export var rock_scenes := [
    preload("rocks/Rock1.tscn"),
    preload("rocks/Rock2.tscn"),
    preload("rocks/Rock3.tscn"),
]

This is what a spawner is! A generic, reusable node that will spawn anything from a list of scenes you give it.

Our spawner will differ a bit in behavior from that code you wrote before:

In this lesson, we will:

  1. Write the code for the spawner.
  2. Create different scenes with different spawners: one of the mobs, one for pickups, and a few others.

Let’s start!

Writing the spawner code

Open the directory res://rooms/spawners/. You will find several .png image files there. Each of these represents a different spawner we’ll set up. They will all share the same code.

We made basic images on purpose because the spawners themselves will not be visible in the running game.

The images only help us visualize the spawning point while designing levels.

Create a new script in the spawners/ directory.

Call it Spawner.gd and make it inherit the Sprite class.

Then double-click the file to start editing it.

Note: If you did not specify the Inherits property, you can just replace the text at the top from extends Node to extends Sprite.

We give the script a class_name to make it convenient to refer to in the project. At the top, write:

class_name Spawner
extends Sprite

Then let’s export a variable to edit the list of spawn options in the Inspector.

export(Array, PackedScene) var list := []

The export hint in parentheses tells Godot that we want an array of scene resources rather than an array of anything.

It will make the array easier to edit in the Inspector as it’ll only accept scene files.

Let’s write our spawn() function now:

func spawn() -> void:
    if not list:
            return

    # Get a random scene resource from the list array.
    var random_scene_index := randi() % list.size()
    var scene: PackedScene = list[random_scene_index]

We have an additional check to make.

With the rocks, we knew the array was entirely filled with scenes. In this case, it’s not guaranteed.

For example, an exported array could look like this:

So, let’s check for holes:

func spawn() -> void:
    # ...
    # The array might have holes. We need to make sure we got an item.
    if not scene:
        return
    var node: Node2D = scene.instance()

Finally, we instance the node and add it as a child of the spawner’s parent node. Then, we move the node to match the spawner’s position.

The role of our spawner is just to make one scene instance. After doing this, we can free it.

But if we added the spawned instance as a child of the spawner, it would get destroyed along with the spawner.

func spawn() -> void:
    # ...
    get_parent().add_child(node)
    node.global_position = global_position

That’s the whole code wrapped up. Let’s now test our spawner.

Testing the spawner

Open res://rooms/TestRoom.tscn. If you had added things there, such as a mob, or items, you can remove them so the scene is clean.

Add a node. In the dialog, start writing “spawner.” You can directly create your new node from here!

Notice how it appears under Sprite in the node inheritance tree because it extends Sprite.

Once you’ve added it, add a texture to it. For example, drag the spawner_mob.svg image to it. And then move it towards the center of the scene.

With the spawner still selected, go to the Inspector. Click on the Array, and increase the size of the array by at least 1.

Then, drag a scene onto it.

We picked Dummy.tscn in this example, but you could pick anything! Why not try Shield.tscn or Robot.tscn?

All that’s left to do is trigger the spawning behavior.

Add a script to the room. Keep the default TestRoom.gd name.

In the room’s script, write:

onready var spawner := $Spawner

func _ready() -> void:
    spawner.spawn()

Once that’s done, run the scene with F6. If everything works, you should see the scene you picked spawn!

Only one thing left to do: hide the spawner itself.

In Spawner.gd, write this:

func _ready() -> void:
    texture = null

Here’s the entire code for the spawner:

class_name Spawner
extends Sprite

export(Array, PackedScene) var list := []


func _ready() -> void:
    # we hide the spawner in the running game
    texture = null


func spawn() -> void:
    if not list:
            return

    # Get a random scene resource from the list array.
    var random_scene_index := randi() % list.size()
    var scene: PackedScene = list[random_scene_index]
    # The array might have holes. We need to make sure we got an item.
    if not scene:
        return
    var node: Node2D = scene.instance()

    get_parent().add_child(node)
    node.global_position = global_position

Challenge: We want to add a spawning_chance variable, between 0 and 100, which will dictate if the spawner spawns or not. Can you think of a way to do that?

Creating the scenes

Every time we want to create a spawner for mobs, we have to add a node, set scenes in its List property, and set a texture. It’s a bit too much repetition.

Instead, we will create four preset scenes for spawners:

You can then use these spawners in the rooms we create. If we still want a custom spawner, we can always add it manually, as we did for our test.

We will create the mob spawner first.

Create a scene inside the spawners/ directory. Call it SpawnerMob, and select a Spawner as the root node.

Drag the spawner_mob.svg file as a texture, and then add the Dummy.tscn mob and the Shield.tscn to the List array.

If you made the bomb and the sword, you can also add them.

Don’t forget to name the node SpawnerMob too. Save it.

That’s our first Spawner! Do the same with the three others.

The SpawnerRobot and SpawnerTeleporter should have only one scene in their list.

We’re ready to use these!