Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. GDScript
  3. GDScript Randomness Practice
GDScript8 exercises

GDScript Randomness Practice

randf_range, randi_range, RandomNumberGenerator, pick_random, rand_weighted, and shuffle bags. Master randomness for spawns, loot, procedural content, and fair variation.

Common ErrorsQuick ReferencePractice
Warm-up1 / 2

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.

On this page
  1. 1Godot 4 randomize(): Do I Still Need It?
  2. 2Deterministic (Seeded RNG)
  3. Save and Restore RNG State
  4. 3Pick a Random Array Element (pick_random)
  5. 4Weighted Loot (rand_weighted)
  6. Quick Method: rand_weighted()
  7. Manual Method (Full Control)
  8. 5"Fair" Randomness (Shuffle Bag)
  9. 6Random Directions
Godot 4 randomize(): Do I Still Need It?Deterministic (Seeded RNG)Pick a Random Array Element (pick_random)Weighted Loot (rand_weighted)"Fair" Randomness (Shuffle Bag)Random Directions

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.

Related GDScript Topics
GDScript Scene InstancingGDScript Vector MathGDScript Arrays & LoopsGDScript Procedural Generation

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.


Ready to practice?

Start practicing GDScript Randomness with spaced repetition

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

Prompt

Check Your Understanding: Why would you use RandomNumberGenerator instead of randf_range()/randi_range()?

What a strong answer looks like

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

Generate floats and ints with randf_range() / randi_range() for spawn timers, damage spread, and position jitterUse Array.pick_random() for simple random selectionCreate deterministic RNG with rng.seed for replays, daily seeds, or debuggingSave and restore RNG state for checkpointsUse rng.rand_weighted() for weighted loot tablesImplement shuffle bags for "fair" randomness without streaksCreate random directions with Vector2.from_angle(randf() * TAU)

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

Pick random array element
var enemies := ["slime", "bat", "ghost"]
var enemy := enemies.pick_random()  # Godot 4
Deterministic RNG stream
var rng := RandomNumberGenerator.new()

func _ready() -> void:
	rng.seed = 123456  # Fixed seed for replay

func roll_damage() -> int:
	return rng.randi_range(8, 12)
Weighted loot (rand_weighted)
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)]
Shuffle bag (fair randomness)
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()
Save/restore RNG state
var rng := RandomNumberGenerator.new()
rng.seed = 999

var checkpoint := rng.state
var a := rng.randi()
rng.state = checkpoint
var b := rng.randi()  # b == a
Random spawn point around player
func 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

Example 1Difficulty: 1/5

Fill in the blank to generate a random float between 0.0 and 100.0.

randf_range
Example 2Difficulty: 2/5

Seed the random number generator with a hash of the string 'my_level' for reproducible level generation.

seed(hash("my_level"))
Example 3Difficulty: 3/5

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

Practice in Build a Game

Arena Survivor: Part 3: Survive the SwarmBuild a GameRoguelike: Part 3: The Dungeon BreathesBuild a GameRoguelike: Drunkard's WalkBuild a GameRoguelike: Cellular AutomataBuild a GameRoguelike: BSP DungeonsBuild a GameRoguelike: Noise TerrainBuild a Game

Further Reading

  • GDScript Dictionary map() and map_in_place12 min read
  • Facade Pattern in Godot 4 GDScript: Taming "End Turn" Spaghetti12 min read

Start practicing GDScript Randomness

Free daily exercises with spaced repetition. No credit card required.

← Back to GDScript Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.