Can you write this from memory?
Generate a random spawn delay between 0.5 and 2.0 seconds, storing it in a variable called delay.
Randomness is everywhere in GDScript games: spawn locations, loot rolls, crit chances, wave patterns, and subtle variation that makes behavior feel alive.
In Godot 4 you can use the global random functions (randf(), randi(), randf_range(), randi_range()) for quick work — or RandomNumberGenerator when you want explicit seeding for deterministic runs, replays, or testing.
Short answer: Not usually. In Godot 4, the global RNG is automatically randomized on startup.
# Godot 4: Global RNG already seeded — just use it
var damage := randi_range(8, 12)
var spawn_delay := randf_range(0.5, 2.0)
var random_dir := Vector2.from_angle(randf() * TAU)
Call randomize() only if you need to reseed intentionally (and only once). Use seed(12345) for deterministic runs.
Note: RandomNumberGenerator.new() uses a fixed seed by default. Call rng.randomize() if you want non-deterministic behavior from local instances.
var rng := RandomNumberGenerator.new()
func start_run(seed_value: int) -> void:
rng.seed = seed_value # Same seed = same sequence
func roll_loot() -> int:
return rng.randi_range(1, 100)
Use seeded RNG for:
- Replays that play back identically
- Daily challenges (everyone gets the same seed)
- Debugging (reproduce exact sequences)
- Keeping loot/AI separate from cosmetic randomness
- Procedural scene instancing with repeatable layouts
Save and Restore RNG State
For checkpoints or debugging a specific roll without consuming RNG calls:
var rng := RandomNumberGenerator.new()
rng.seed = 999
var checkpoint := rng.state
var a := rng.randi()
rng.state = checkpoint
var b := rng.randi() # b == a
Warning: Only set state to values from the state property itself — never arbitrary values.
Godot 4 provides Array.pick_random() as the straightforward way. For more array patterns like filtering and sorting, see the arrays & loops page.
var enemies := ["slime", "bat", "ghost"]
var enemy := enemies.pick_random()
Tip: Check array.size() > 0 before picking to avoid issues with empty arrays.
Quick Method: rand_weighted()
RandomNumberGenerator.rand_weighted() returns a weighted index given a PackedFloat32Array:
var rng := RandomNumberGenerator.new()
rng.seed = 12345
var items := ["gold", "potion", "rare_gem"]
var weights := PackedFloat32Array([70.0, 25.0, 5.0])
var item := items[rng.rand_weighted(weights)]
Manual Method (Full Control)
For custom logic or when you need to understand the algorithm:
var drop_table := [
{ "item": "gold", "weight": 70 },
{ "item": "potion", "weight": 25 },
{ "item": "rare_gem", "weight": 5 },
]
func pick_drop() -> String:
var total_weight := 0
for entry in drop_table:
total_weight += entry["weight"]
var roll := randi_range(1, total_weight)
var cumulative := 0
for entry in drop_table:
cumulative += entry["weight"]
if roll <= cumulative:
return entry["item"]
return drop_table[-1]["item"] # Fallback
True randomness often feels streaky — you might get the same fruit three times in a row. The shuffle bag pattern fixes this by removing items after selection:
var items_backup := ["apple", "orange", "pear", "banana"]
var items_bag: Array[String] = []
func pick_fair_item() -> String:
if items_bag.is_empty():
items_bag = items_backup.duplicate()
items_bag.shuffle()
return items_bag.pop_front()
Use for: Loot that should feel "fair," wave composition, upgrade pools.
Limitation: Items can still "ping-pong" at bag boundaries (last item of one bag, first of next).
Random directions rely on vector math fundamentals—from_angle() and normalized() are the building blocks.
# Random unit vector (any direction)
var dir := Vector2.from_angle(randf() * TAU)
# Random point in a circle
func random_in_circle(radius: float) -> Vector2:
var angle := randf() * TAU
var dist := sqrt(randf()) * radius # sqrt for uniform distribution
return Vector2.from_angle(angle) * dist
When to Use GDScript Randomness
- Spawning enemies or pickups with natural variation (positions, delays, wave composition)
- Procedural generation (rooms, loot, stats) where you want controlled randomness
- Combat variation (crit chance, damage spread) and "juice" (camera shake offsets, flicker)
Check Your Understanding: GDScript Randomness
Check Your Understanding: Why would you use RandomNumberGenerator instead of randf_range()/randi_range()?
Use the global functions for quick, non-critical randomness. Use RandomNumberGenerator when you need an explicit seed (deterministic runs, replays, daily challenges, tests) or when you want multiple independent RNG streams (e.g., one for loot, one for enemy AI) so one system doesn't affect another.
What You'll Practice: GDScript Randomness
Common GDScript Randomness Pitfalls
- Calling randomize() every frame—seed once at startup (if at all); repeated seeding is unnecessary in Godot 4 and can reduce randomness quality
- Mixing "cosmetic RNG" (camera shake, particles) with "gameplay RNG" (loot, damage) if you care about deterministic replays
- Using randi_range(0, array.size())—the upper bound is inclusive in GDScript; use array.size() - 1, or better yet, use pick_random()
- Similar seeds producing similar streams—if you derive seeds externally (like day numbers), hash them first; Godot's RNG has no avalanche effect
- Setting rng.state to arbitrary values—only restore state values that came from the state property itself
GDScript Randomness FAQ
Why do I keep getting the same random results every run?
In Godot 4, the global RNG is already randomized on startup. If you're seeing repeats, you're likely: (1) setting a fixed seed with seed(123), (2) using a RandomNumberGenerator instance without calling rng.randomize(), or (3) reseeding in a tight loop (same time-based seed).
How do I make randomness deterministic for replays?
Use RandomNumberGenerator and set rng.seed to a known value at the start of the run. Keep all gameplay randomness using that RNG (or a small set of dedicated RNGs). Use rng.state to save/restore checkpoints.
How do I pick a random array element?
Use array.pick_random() in Godot 4. Check array.size() > 0 first to avoid issues with empty arrays.
What's a clean way to get a random direction?
Use Vector2.from_angle(randf() * TAU) for a unit vector in a random direction, then multiply by your speed.
Why does random feel "streaky"?
True randomness often feels streaky to humans—we expect more alternation than actually occurs. If you want "fair" randomness (no long streaks), use shuffle bags or pity systems instead of pure random.
How do I do weighted random picks?
Use rng.rand_weighted(PackedFloat32Array([70.0, 25.0, 5.0])) which returns an index you can use to access your items array. For manual control, sum weights, roll in that range, and iterate until covered.
How do I shuffle a list deterministically?
Array.shuffle() uses the global RNG, so you can't pass it a specific seed. For deterministic shuffles, implement Fisher-Yates manually using your seeded RandomNumberGenerator: iterate backward, swap each element with a random earlier element using rng.randi_range().
What's the difference between randf() and RandomNumberGenerator?
randf() uses a global RNG shared across the entire game (auto-randomized in Godot 4). RandomNumberGenerator is an object with its own seed—useful for isolated, reproducible sequences.
Should I create one RNG or many?
One is fine for most games. Use multiple when you need isolation (e.g., loot RNG separate from cosmetic RNG) so that adding particle effects doesn't change loot rolls in replays.
GDScript Randomness Syntax Quick Reference
var enemies := ["slime", "bat", "ghost"]
var enemy := enemies.pick_random() # Godot 4var rng := RandomNumberGenerator.new()
func _ready() -> void:
rng.seed = 123456 # Fixed seed for replay
func roll_damage() -> int:
return rng.randi_range(8, 12)var rng := RandomNumberGenerator.new()
var items := ["gold", "potion", "rare_gem"]
var weights := PackedFloat32Array([70.0, 25.0, 5.0])
var item := items[rng.rand_weighted(weights)]var backup := ["apple", "orange", "pear"]
var bag: Array[String] = []
func pick_fair() -> String:
if bag.is_empty():
bag = backup.duplicate()
bag.shuffle()
return bag.pop_front()var rng := RandomNumberGenerator.new()
rng.seed = 999
var checkpoint := rng.state
var a := rng.randi()
rng.state = checkpoint
var b := rng.randi() # b == afunc random_spawn_point(center: Vector2, radius: float) -> Vector2:
var dir := Vector2.from_angle(randf() * TAU)
return center + dir * randf_range(radius * 0.5, radius)GDScript Randomness Sample Exercises
Fill in the blank to generate a random float between 0.0 and 100.0.
randf_rangeSeed the random number generator with a hash of the string 'my_level' for reproducible level generation.
seed(hash("my_level"))Calculate a random spawn position on a ring of given radius around the player using Vector2.from_angle(). Store in `spawn_pos`. (Assume player.global_position and radius exist.)
var spawn_pos = player.global_position + Vector2.from_angle(randf() * TAU) * radius+ 5 more exercises