In this lesson, we’ll generate randomized rocks on a grid using two
nested for
loops.
By the end, you’ll have a grid with one rock appearing in each cell.
In our code, we’ll write two functions: one to pick random rocks to instantiate, and another to place those rocks on the grid.
In the FileSystem dock, right-click on the
RandomRocks/
folder and click New Scene… to create
an empty scene. You can name it RandomRocks.
In the Scene dock, click the 2D Scene button to create a
and rename the node to RandomRocks.Right-click the RandomRocks node and select Attach Script to create a new script for it.
In the script editor, we start with a function to get a random rock.
First, we need a list of rocks to instantiate. We prepared three
scenes for you, each with a different rock. You can find them in the
RandomRocks/rocks
folder.
Each scene contains only a
node at the moment, but we’ll add collisions to them in the next chapter.For now, we define an array where we preload the three rock variations.
# 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"),
]
We then define a function to create a random rock from our
ROCKS
array.
# 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()
To pick a random preloaded rock scene, we need a random index. The
randi()
function generates a random integer, but it is
often a huge number.
To calculate a valid index in our ROCKS
array, we use
the %
modulo operator and the array’s size:
randi() % ROCKS.size()
.
Now we can generate random rocks. We need to place them in each cell of a grid.
We want to be able to change the grid size, so we write a function that lets us choose how many columns and rows we want.
We generate cell coordinates using two nested for
loops.
One loop generates the cell’s column, and the other the cell’s row.
# Creates and places a random rock on each cell of the grid, based on the number
# of columns and rows.
func add_rocks_on_grid(columns: int, rows: int) -> void:
for column in range(columns):
for row in range(rows):
var cell := Vector2(column, row)
That’s how you loop over 2D coordinates in GDScript. To create 3D
coordinates, you would nest three for
loops instead.
We then use the get_random_rock()
function to create a
new rock instance and add it as a child.
# Creates and places a random rock on each cell of the grid, based on the number
# of columns and rows.
func add_rocks_on_grid(columns: int, rows: int) -> void:
for column in range(columns):
for row in range(rows):
# ...
var rock := get_random_rock()
add_child(rock)
Finally, we need to place the rock. The cell
variable
represents grid coordinates. To covert these to screen coordinates in
pixels, we multiply them by the CELL_SIZE
.
# This constant is the size of each grid cell in pixels.
const CELL_SIZE := Vector2(128, 128)
# Creates and places a random rock on each cell of the grid, based on the number
# of columns and rows.
func add_rocks_on_grid(columns: int, rows: int) -> void:
for column in range(columns):
for row in range(rows):
# ...
= CELL_SIZE * cell rock.position
Your complete add_rocks_on_grid()
function should look
like this.
# Creates and places a random rock on each cell of the grid, based on the number
# of columns and rows.
func add_rocks_on_grid(columns: int, rows: int) -> void:
for column in range(columns):
for row in range(rows):
var cell := Vector2(column, row)
var rock := get_random_rock()
add_child(rock)
= CELL_SIZE * cell rock.position
We call our new function inside of _ready()
to generate
rocks.
func _ready() -> void:
add_rocks_on_grid(9, 5)
You can now run the scene to see a grid filled with random rocks.
If you run the scene multiple times, you’ll always get the same rocks in the same cells.
That’s because functions to generate random numbers like
randi()
use clever calculations to output numbers that
aren’t exactly random: they are pseudo-random.
Pseudo-random numbers are generated in a deterministic way.
They are a sequence of seemingly random numbers produced from an input number called the seed value. If you give the computer the same seed number, it will produce the same number sequence every time.
This deterministic behavior is helpful, as you can debug your code and compare the output of your procedural generation code as you change it.
It also allows players to share worlds in procedural games like Minecraft.
In Minecraft, when starting a new game, you can enter a seed value. Every player that uses the same given seed will land in the same environment.
In GDScript, we can randomize the seed value by calling the
randomize()
function once in the game. This function uses
the computer’s clock to produce a unique number.
We can call the randomize()
function in the
_ready()
function, before calling
add_rocks_on_grid()
.
func _ready() -> void:
# Start by refreshing the seed value used to generate random numbers so we
# get different random values for each run.
randomize()
add_rocks_on_grid(9, 5)
If you play the scene multiple times, you should now see the rock sprites change each time.
In the next lesson, we’ll offset each rock randomly with a blue noise distribution.
Open the practice Selecting random items.
In this practice, you’re starting with an inventory filled with swords.
You need to edit the get_random_item()
function to
randomize the generated items.
Open the practice Placing gems on a grid.
In this project, we want to generate a grid of randomly-placed gems.
Here’s the complete code listing for RandomRocks.gd
.
extends Node2D
# This constant is the size of each grid cell in pixels.
const CELL_SIZE := Vector2(128, 128)
# 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"),
]
func _ready() -> void:
# Start by refreshing the seed value used to generate random numbers so we
# get different random values for each run.
randomize()
add_rocks_on_grid(9, 5)
# Creates and places a random rock on each cell of the grid, based on the number
# of columns and rows.
func add_rocks_on_grid(columns: int, rows: int) -> void:
for column in range(columns):
for row in range(rows):
var cell := Vector2(column, row)
var rock := get_random_rock()
add_child(rock)
= CELL_SIZE * cell
rock.position
# 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()