Determinstic Seeding (Global Seed)
New in 1.4.0 - make enemy population and placement reproducible across runs.
This page explains how to enable and use the Global Seed to get deterministic, debuggable spawn behavior in the Runtime Spawner. With a fixed seed and the same content, the spawner will produce the same spawn choices (per-entry RNG), positions, and group sizes every run- great for QA repros, speedruns, and perf testing.
What the seed controls
A single Global Seed drives a fast, reproducible PCG32 RNG for spawns.
For each spawn request, the spawner derives a stable sub-stream using:
the Global Seed
the
SpawnEntry
’s stable identitycontext hints (e.g.,
SpawnContext.SourceName
such as a wave/table name, or region name)
The derived RNG is used for:
Placement sampling (global/region/wave)
Group size & grouping order (leader + members)
Member offsets around the leader
Result: given the same seed, scene, and timing, spawn locations and groups repeat exactly.
Enabling determinism
In the Inspector
Select your RuntimeSpawner.
Enable Use Deterministic Seed.
Set Global Seed (any int).
From code (recommended for game bootstrap)
Call this before any spawns start (i.e., before StartSpawners()
or before waves fire).
using MegaCrush.Spawner;
using UnityEngine;
public class GameBootstrap : MonoBehaviour
{
[SerializeField] private RuntimeSpawner spawner;
[SerializeField] private int missionSeed = 123456; // set from save/CLI/dev console
void Awake()
{
if (!spawner) spawner = FindAnyObjectByType<RuntimeSpawner>();
// Locks the package into deterministic mode and broadcasts the change.
spawner.SetGlobalSeed(missionSeed);
// Optionally start the system afterward
spawner.Init();
spawner.StartSpawners();
}
}
You can listen to RuntimeSpawner.onSeedChanged
if you mirror the seed to UI/telemetry:
private void OnEnable() => RuntimeSpawner.onSeedChanged += OnSeedChanged;
private void OnDisable() => RuntimeSpawner.onSeedChanged -= OnSeedChanged;
private void OnSeedChanged(int seed)
{
Debug.Log($"Spawner seed now {seed}");
// Update HUD, write to a run log, etc.
}
Picking good seeds
A few simple patterns:
1) Per-run random → saved for repro
int seed = System.Environment.TickCount; // or RandomNumberGenerator for crypto
spawner.SetGlobalSeed(seed);
// Save 'seed' to your run history so QA can replay exactly the same mission.
2) Campaign + node → stable across sessions
int seed = Hash32(playerCampaignSeed, currentNodeId);
spawner.SetGlobalSeed(seed);
// Example non-cryptographic hash
static int Hash32(int a, int b)
{
unchecked { uint h = 2166136261; h = (h ^ (uint)a) * 16777619; h = (h ^ (uint)b) * 16777619; return (int)h; }
}
3) Command line / dev console
-game_seed=987654
Parse once, call SetGlobalSeed(value)
in your bootstrap.
Best practices
Call early. Set the seed before any spawns can occur (including auto-start or
WaveTrigger.StartAutomatically
).Use the same seed everywhere (if your own systems also randomize). For true full-game determinism, route your other RNG-using systems off the same root seed.
Log the seed in your run summary so QA can replay bugs easily.
Don’t mix UnityEngine.Random into spawn decisions you expect to be deterministic. The spawner already supplies a per-request RNG internally; if you extend spawns, use that RNG rather than global state.
Scope & limitations
Determinism covers:
Entry placement sampling (global/region/wave)
Group size and per-member offsets
Spawner-internal randomization that uses the derived PCG32 stream
Not guaranteed deterministic:
Wave entry selection order inside
WaveSpawnLoop
if you rely onUnityEngine.Random
in your own code paths.If you need fully deterministic wave composition, either pre-author explicit sequences, or adapt your selection to route through the same seed (e.g., add a small utility that uses
SpawnerSeedRouter
for wave picking).
External nondeterminism: physics timing, asynchronous content loads, time-based triggers, or anything randomized outside the spawner using other RNGs.
In short: the spawner side is deterministic; the rest of your game must also avoid nondeterministic influences if you want a perfectly repeatable run.
Frequently asked
Q: Can I change the seed mid-mission?
A: You can call SetGlobalSeed(...)
at any time; it will affect subsequent spawn requests. For reproducible runs, set it once at mission start.
Q: Does the seed affect waves?
A: Yes - spawn placement and group behavior within a wave are deterministic. If you also want deterministic which SpawnEntry
a wave picks when randomizing, drive that selection from the same seed (see limitation above).
Q: How does this interact with pooled objects? A: Pooling is unaffected. The RNG decides where/what/how many; the pool just supplies instances.
Q: Do I need to enable anything else? A: No. Turn on Use Deterministic Seed and set a seed. Everything else is built in.
Example: QA repro flow
On mission start, generate/log a seed:
int seed = System.Environment.TickCount; spawner.SetGlobalSeed(seed); Debug.Log($"RUN SEED: {seed}");
If a bug is found, re-launch with
-game_seed=<value>
or paste the seed in your dev console and rerun.The same enemies, placements, and groupings will appear at the same times/places (subject to the limitations above).
API recap
Inspector
Use Deterministic Seed
(bool)Global Seed
(int)
Runtime
void SetGlobalSeed(int seed)
int GlobalSeed { get; }
bool UseDeterminism { get; }
static event Action<int> onSeedChanged
That’s it - flip the switch, set a seed, and enjoy reproducible spawns.
Last updated