© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023
C. PittProcedural Generation in Godothttps://doi.org/10.1007/978-1-4842-8795-8_2

2. Generating with Nodes

Christopher Pitt1  
(1)
Durbanville, South Africa
 

In the previous chapter, we talked about why you’d want to generate content rather than craft it. It’s time to get into the code of things.

In this chapter, we’re going to create a fresh project where we can experiment. We’ll follow this up by creating nodes via code and randomizing their behavior.

My version of Godot might differ from yours since I’m writing this a few months before you read it. I’m using an early version of Godot 4, so as long as you’re using Godot 4, we should be good. You can find installation instructions on the Godot website.

Setting Up a New Project

Launch Godot 4. If it’s the first time, you’ll see a message asking if you’d like to import an example project. We’ll ignore this and create a blank project:

A screenshot for the Godot engine page has the tab for local projects selected. It has a pop-up window titled please confirm and an option to open the asset library is highlighted.

Opening Godot 4 for the first time

Clicking CancelNew Project will show a dialog asking for details about your project:

A screenshot of the Godot engine page has the tab for local projects selected. It has a pop-up window to create a new project and an option for version control metadata is highlighted.

Creating a new project

There’s not a lot we need to change here. Give your project a name, and set version control metadata to None. Whatever rendering engine you pick will work for the purposes of this project.

Version control is a good thing, but it’s not in the scope of this book. If you’re familiar with Git, then feel free to use that. I won’t be going into detail about how it works or how to set it up.

Clicking Create & Edit will take you to the default editor view. I usually do the same things when starting a new game to create a solid base:
  • I start by creating a base Screen scene, with a MarginContainer as a main node.

  • I inherit this scene to create screens, including one in which the gameplay happens.

  • I move automatically created configuration files and icons to folders that represent their types.

Here’s what that Screen scene looks like:

A screenshot of the Godot engine screen page has options of scene and filesystem highlighted. The middle panel has a few lines of code for the screen. The right side has the tab for the inspector selected.

Creating the base Screen scene

I prefix my class names so that there’s no risk of their name conflicting with a built-in class. It’s rare for this to happen, but a prefix avoids the issue altogether.

If we switch to the 2D editor tab, we can select the MarginContainer node and expand it to fill all the available screen space. This means that all our screens will fill the available screen space.

A screenshot of the output page of the screen. It has a rectangular box and options for anchor preset at the top right side.

Expanding the MarginContainer node

Since this class has a name, we can inherit from it without a path to the file in our code. Let me show you what a subclass of GameScreen looks like:

A screenshot of the play screen has tabs for the scene and file system selected. The middle panel has a few lines of code for the play screen. The right side has the tab for the inspector selected.

Extending a class

I recommend spending some time looking through the editor settings to get it set up the way you like to use it. I sometimes increase the editor interface size, but you can do what feels natural for you.

To get to this point, I selected the SceneNew Inherited Scene menu option and selected the screen.tscn file I created as the parent scene. After selecting and renaming the Screen node, I went to ScriptNew Script. That shows this dialog:

A screenshot has the tabs for the scene and file system. The middle panel has a pop-up window to attach the node script with an option for inherits. The right side has the option for script selected as screen g d.

Creating a new script

We saw this dialog when we attached a script to the Screen node. This time, we should change where it says MarginContainer to GameScreen. Clicking Create will make the new script. You should see both files (play_screen.tscn and play_screen.gd) in the file explorer, on the left side of the screen.

I like to use MarginContainer as the main node because some mobile devices have camera notches. This requires that we resize the game so that it isn’t hidden behind a notch. We can code the MarginContainer to add padding after the game starts.

When you click the play button, at the top right of the screen, Godot will ask you to set a default scene. Pick the PlayScreen as the default scene:

A screenshot has the tabs for the scene and file system. The middle panel has a pop-up window for the play screen titled please confirm, and an option to select current. The right side has the tab for the inspector.

Configuring the default scene

Loading Experiments

We’re going to use a single experiment project for most of the code we’re going to write. We need a way to load different experiments as we work on them. One way to achieve this is to put each experiment in a Node2D node and center that in the play screen.

Let’s add a CenterContainer and Control to the MarginContainer:

A screenshot has the tabs for the scene, anchor, file system, and play screen. The middle panel has an output image for the play screen. The right side has the tab for the inspector and options of anchor and control.

Centering experiments in the PlayScreen

Now, we can make each experiment a Node2D scene, starting with the base GameExperiment:

A screenshot has the tabs for the scene, experiment, file system, and experiments. The middle panel has a few lines of code for the experiment. The right side has the tab for the inspector.

Creating the GameExperiment class

We can extend this for our first experiment, which is going to be about randomizing nodes. We can call this the NodesExperiment:

A screenshot has the tabs for the scene, items, file system, and node experiments. The middle panel has a few lines of code for the nodes experiment. The right side has options for items and grid container, control, canvas item, and node.

Creating the NodesExperiment class

I’ve also created a 200 × 200 ColorRect background and a 200 × 200 GridContainer, into which we’re going to create 25 child nodes. We should set the GridContainer node to have 5 columns.

The Node2D experiment node will be in the center of the Anchor node, so we should set the size of the background and grid to -100 × -100.

There are a few ways we could bring this experiment into PlayScreen:
  • load("res://nodes/experiments/nodes-experiment.gd")

  • preload("res://nodes/experiments/nodes-experiment.gd")

But both of these suffer from an annoying problem. In fact, it’s the same problem we’ve tried to avoid with custom class names. When files move, those strings aren’t updated.

The safest way to reference other nodes is by class name, or exported variables. Here's what I mean:

This is from nodes/screens/play_screen.gd
extends GameScreen
@export var experiment_scene : PackedScene
@onready var _anchor := $Center/Anchor
func _ready() -> void:
    var experiment = experiment_scene.instantiate()
    _anchor.add_child(experiment)
Godot 4 uses @export and @onready to hint that these variables need special handling:
  • @export variables are available through the property inspector.

  • @onready variables resolve after the parent node is ready.

The instantiate method creates a new instance of the experiment scene so that we can add it to the scene.

_anchor starts with an underscore because I want to hint that it is private to this script. The underscore doesn't affect functionality. It's a pattern that is popular in the Python and GDScript programming languages.

When you go back to the visual editor and select PlayScreen, you should see the variable on the right side of the screen. We call this area the Property Inspector:

A screenshot has the tab for inspectors and options for play screen, filter properties, play screen g d, and experiment scene.

Finding the exported variable

Clicking on Empty will show a couple of ways to pick the experiment. We can drag the NodesExperiment scene onto the drop-down, and it would link them; but this might not be practical in a huge project. A better option is to click EmptyQuick Load.

This will display a searchable list of PackedScene nodes to select from:

A screenshot has the tabs for the scene, play screen, and file system. The middle panel has a pop-up window for resources and options, searches, and matches. The right side has options for play screen g d, control, canvas item, and node.

Selecting the NodesExperiment scene from a searchable list

Once selected and saved, you can click the play button and see the experiment as the game launches:

A screenshot of the experiments page has a dark square block.

Launching the experiment

This a nice, reusable system for packaging and loading our future experiments. We’re going to have another eight of them; so we’re definitely going to get some mileage out of this system.

Creating Nodes via Script

Let’s create a new scene, which we’ll randomize the behavior of. You can imagine this as a part of the game’s environment, a decoration, like a tree or rock. We’re going to keep things simple and use a ColorRect:

A screenshot has the tabs for the scene, file system, and play screen dot t s c n. The middle panel has an output image for doodad. The right side has the tab for the inspector.

Creating the doodad class

ColorRect is a good node type to choose here because the experiment will show it in a GridContainer. It’s not required for all your decorations, unless you’re also going to display them in a similar way.

Set LayoutTransformSize to 40 × 40 and LayoutContainer Sizing to expand × expand. Then, we need to “import” it in a similar way to what we did for the experiments:

This is from nodes/experiments/nodes_experiments.gd
extends GameExperiment
@export var doodad_scene : PackedScene
@onready var _items := $Items
func _ready() -> void:
    for i in range(25):
        var doodad = doodad_scene.instantiate()
        _items.add_child(doodad)

Now, when running the game, we should see a grid of Doodad classes:

A screenshot of the experiments page has a 5 by 5 grid pattern.

Testing the grid rendering

If you’re using a Sprite2D for your decoration, you can swap the GridContainer for position or global_position attributes. We’ll get around to doing that in later chapters. For now, what’s more important is to talk about how we add randomization to this Doodad class.

Randomizing Behavior

There are a couple types of randomization we could use:
  • Seeded Generation with a fixed seed

  • Unseeded Generation based on a random seed

Both are a kind of seeded generation, but the difference is whether we want to know and control the seed or not. For now, we’re not going to control the seed. Chapter 6 is when we’ll start doing that.

Let’s create a script on the Doodad class and randomize the colors of the ColorRect:

This is from nodes/experiments/nodes_experiment/doodad.gd
extends ColorRect
func _ready() -> void:
    color = Color(
        randf_range(0.0, 1.0),
        randf_range(0.0, 1.0),
        randf_range(0.0, 1.0)
    )

Godot 4 automatically calls the randomize function, so we don’t need to call it ourselves. This function seeds the built-in random number generator, so it’s not outputting predictable values.

The randf_range(min, max) function returns a random float value between minimum and maximum values.

There are many rand* functions to choose from:
  • randf

  • randf_range

  • randi

  • randi_range

  • randfn

The randf function is shorthand for randf_range(0.0, 1.0). The randi_range(min, max) function is shorthand for randi() % 100, where min is 0 and max is 99. If that’s a lot, don’t worry. We’re mostly going to deal in integers; and we’ll see plenty of examples that will help clear things up.

Creating Realism with Randomization

So, we can randomize the colors of the squares; but how do we use this knowledge to do something more useful. Say we wanted to make the squares green (for trees) or brown (for rocks) or transparent. We could use randf* or randi* to generate a number and then do different things based on what that number is.

Something like this:

This is from nodes/experiments/nodes_experiment/doodad.gd
extends ColorRect
func _ready() -> void:
    var number = randf()
    if number > 0.9:
        color = Color.DARK_GREEN
    elif number > 0.7:
        color = Color.SADDLE_BROWN
    else:
        color = Color.TRANSPARENT

We start by generating a random number between 0.0 and 1.0. Then we compare it and only make the ColorRect green when the number is above 0.9. This creates a one-in-ten chance that the ColorRect will be green. If that one-in-ten chance fails, there’s a three-in-ten chance that ColorRect will be brown.

A screenshot of the experiments page has a square with a few orange and green square blocks inside.

Trees and rocks with randomization

We can make subtle changes to this, but the general idea will remain the same for all the node randomization we do in the rest of the book.

Summary

In this chapter, we learned about how to create new nodes and randomize values in Godot 4. We set up a new project and covered some habits I generally recommend for structuring project code.

Take some time to experiment with the rand functions. See if you can figure out how to use different thresholds for your random values.

Try to use Sprite nodes, showing and hiding as appropriate.

In the following chapter, we're going to take these techniques further with tile sets.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.107.142