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:
Opening Godot 4 for the first time
Clicking Cancel ➤ New Project will show a dialog asking for details about your project:
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.
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:
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.
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:
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 Scene ➤ New 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 Script ➤ New Script. That shows this dialog:
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:
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:
Centering experiments in the PlayScreen
Now, we can make each experiment a Node2D scene, starting with the base GameExperiment:
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:
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.
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:
@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:
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 Empty ➤ Quick Load.
This will display a searchable list of PackedScene nodes to select from:
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:
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:
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 Layout ➤ Transform ➤ Size to 40 × 40 and Layout ➤ Container Sizing to expand × expand. Then, we need to “import” it in a similar way to what we did for the experiments:
Now, when running the game, we should see a grid of Doodad classes:
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
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:
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.
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:
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.
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.