A player sends you a screenshot: the run broke on a level where the boss spawned inside a wall. You would love to see that level. But it was random, and it is gone. Or you want a daily challenge where everyone plays the same dungeon. Or you just changed one line, and want to see whether the bug is fixed on the exact floor that showed it.
All three need the same thing: the ability to make the same "random" level again on purpose. That is what a seed is for. This is part 7 of a series that builds one dungeon generator, pass by pass.
Random numbers that are not random
The numbers a computer game calls random come from a formula. Each new number is worked out from a hidden internal state, and the state moves on one step. That formula is a pseudo-random number generator. Godot's documentation says its RandomNumberGenerator "currently uses PCG32", a generator designed by Melissa O'Neill.
The seed sets that internal state. Set the same seed, and the generator walks the same path through its numbers, every time, on every machine running the same code. In the docs' words: "A given seed will give a reproducible sequence of pseudo-random numbers."
For seed 214, the first three numbers from randi_range(3, 6) are 3, 4 and 4. They will be 3, 4 and 4 tomorrow, and on your phone, and in the demo on this page. The demo's generator reproduces Godot's exactly, so a seed you try here builds the same map in Godot 4.7.2.
The order is part of the seed
A seed fixes the sequence of numbers, not which part of your code gets each one. Your generator takes them in order: the first room's width, its height, its position, the next room, and so on. If anything takes an extra number in the middle, everything after it receives a different number than before.
Here is the most ordinary way that happens. You decide half the rooms should get a torch, and add one line to the room pass:
d.rooms.append(rect) d.carve_room(rect) if torch_rng.randf() < 0.5: # NEW: one extra random call torches.append(rect.get_center())If torch_rng is the same generator that places rooms, that one roll per room shifts every room after the first. Try it below.
Same seed, one extra random call
Interactive, engine-accurateOne extra random call per room, and 148 of 484 cells changed. Every number the layout drew after the first torch roll came out different.
Before
After adding the torch roll
Seed 214 makes the layout stream start with 3, 4, 4 for the first three room sizes, every time, in the browser and in Godot. The second map is the same seed after adding the line if rng.randf() < 0.5: to the room pass. Rooms marked t got a torch.
This runs the GDScript from this article, ported to the browser. The same seed gives the same result in Godot 4.7.2.
On seed 214, with one generator for everything, the torch roll changes 148 of the 484 cells and even the number of rooms, from 8 to 9. It changed the layout on every one of seeds 1 to 500. You added torches, and lost every map you had saved.
The fix: one stream per job
Give the layout and the decoration their own generators. Seed a third, short-lived generator with your seed, and use its first two numbers to seed the other two:
func set_seed(seed_value: int) -> void: var seeds := RandomNumberGenerator.new() seeds.seed = seed_value rng.seed = seeds.randi() decor_rng.seed = seeds.randi()Now the torch roll draws from decor_rng. The layout stream never sees it. Switch the demo to Layout and decoration apart: the same change now moves 0 cells. Seed 214 keeps its map, and gets torches in 4 rooms.
The same idea scales. Give enemy spawns, loot and decoration their own streams, and you can rebalance loot without moving a single wall.
Where seeds still break
Godot's global random functions. randi(), randf() and randi_range() called without a generator use a single global seed, which Godot randomizes when the project starts. Array.shuffle() and Array.pick_random() use that global seed too. The docs say so directly: they use "a common, global random seed". One shuffle() in your generator and your seed stops working. Use your own generator, and shuffle with it, by swapping elements with rng.randi_range().
Using the generator for gameplay too. If enemies roll their attacks on the same generator that builds the next floor, how you fight changes the next floor. Keep level generation on its own streams.
Similar seeds. The docs warn that the generator "does not have an avalanche effect": seeds 1 and 2 can start with similar numbers. If you build seeds from a date or a counter, mix them first. hash() on the number or on a string is an easy way.
Saving mid-run. A seed rebuilds the level from the start. To resume a generator part way through its sequence, save its state property and restore it later. The docs say not to set state to arbitrary values, only ones you saved.
Different engine versions. The numbers come from Godot's code. This page's numbers were checked against Godot 4.7.2 only. A future version could change how a function draws numbers, and old seeds would make new levels. If seeds matter to players, such as shared daily challenges, record the game version with the seed.
Seeds you can share
A seed is a short number that stands for a whole level, which makes it useful outside the generator:
- Bug reports. Show the seed on the pause screen or in the log. "Seed 8812, floor 3" is a reproducible bug.
- Daily challenges. Build the seed from today's date, so every player gets the same dungeon. Everyone needs the same game version, too.
- Testing. Run your playability checks on seeds 1 to 1,000 every time you change the generator, as part 8 does. A seed that broke once can join the list for good.
In Godot: one generator object, seeded once
The smallest example of the whole idea, run in Godot 4.7.2:
func same_seed_same_numbers(seed_value: int) -> Array: var rng := RandomNumberGenerator.new() rng.seed = seed_value return [rng.randi_range(3, 6), rng.randi_range(3, 6), rng.randi_range(3, 6)]Call it twice with 214 and you get [3, 4, 4] both times. In the full generator, generate() seeds both streams before any pass runs:
func generate(seed_value: int) -> void: set_seed(seed_value) clear() place_rooms() connect_chain()To let players type a seed, export it on your dungeon node with @export var dungeon_seed := 214 and pass it to generate(). For a fresh level each run, pick a seed once, then use it: dungeon_seed = randi(). Keep that number, and show it somewhere.
The complete generator, with both streams, is dungeon_layout.gd.
Build it next
Procedural Dungeon & Run
A quest on CodingQuests: 12 interactive lessons that build one part of a game in Godot, in your browser, checked as you go.
Procedural Dungeon & Run does not teach seeds. What it does, from lesson 2, is create its own RandomNumberGenerator and roll every room size and position through it. That generator is exactly the piece you seed.
Add an exported dungeon_seed and set the generator's seed from it before the layout runs, and the course's dungeon becomes repeatable. If you also add decoration rolls, give them a second generator as shown above.
- Lesson 2: Rooms & CorridorsFree
Lessons 1 to 3 are free to open, no account needed. Guild Hall, the membership, opens the other 9.
FAQ
What is a seed in procedural generation?
A seed is the number a random number generator starts from. The same seed produces the same sequence of numbers, so a generator that draws all its choices from that sequence builds the same level every time.
Why does my Godot seed not give the same result every time?
Something in your generator uses a random number that is not from your seeded generator. The usual culprits are the global randi() or randf(), and Array.shuffle() or pick_random(), which all use Godot's global seed. Another cause is an extra random call added earlier in the generator.
How do I make a daily challenge with the same level for everyone?
Turn the date into a seed, for example with hash() on a "2026-10-31" string, and seed the level generator with it. Every player who runs the same game version gets the same level that day.
Sources and testing
- Godot docs: RandomNumberGenerator, on seed, state, PCG32 and the avalanche note
- Godot docs: Random number generation
- Godot docs: Array, where shuffle and pick_random use the global seed
- Melissa O'Neill, PCG, A Family of Better Random Number Generators
- Developer questions this answers: Trouble with seed not working here and Is Random deterministic between platforms? on the Godot Forum, How do I make a daily challenge on a game with randomly generated levels? on Game Development Stack Exchange
How this was tested. Every GDScript block on this page was run in Godot 4.7.2 (headless, on Windows) on 31 October 2026. The demo's random number generator is a port of Godot's, checked number for number against Godot on 7 seeds, and its dungeons are checked against Godot's on 40 seeds. The 500-seed claim comes from the port. Nothing here was checked on another Godot version or on a non-Windows build.

