Procedural World Generation
A fully deterministic, data-driven rogue-like generation system
The Context
The goal of this project was to build a system capable of generating a simplified version of a rogue-like environment (like Aincrad in Sword Art Online but smaller). The core constraint was strict determinism: the system had to generate a complete set of 15 unique, reproducible levels based entirely on a single starting seed string.
Dynamic generation of the overworld, organic villages, and BSP dungeons.
Architecture & Implementation
01. Data Matrices & Multi-Layered Noise
To optimize performance, the world is not generated by instantiating 3D blocks immediately. Instead, everything is processed as abstract data matrices.
I generate a raw terrain matrix using Perlin noise, normalize it against a water level threshold, and independently generate a biome matrix. These abstract grids are merged and evaluated before a single GameObject is instantiated in the scene.
void RegenerateMap(Vector2 seed)
{
// 1. Dynamic map shrinking based on current floor level (RSO)
int currentWidth = Mathf.Max(width - (runData.currentLevel - 1), 21);
int currentHeight = Mathf.Max(height - (runData.currentLevel - 1), 21);
// 2. Generate and normalize abstract Data Matrices
float[,] rawTerrainMap = GenerateNoiseMap(currentWidth, currentHeight, scale, octaves, seed);
float[,] normalizedTerrain = NormalizeTerrain(rawTerrainMap, waterLevel, seed, scale);
Vector2 biomePosition = seed + new Vector2(10000f, 10000f);
float[,] rawBiomeMap = GenerateNoiseMap(currentWidth, currentHeight, scale, octaves, biomePosition);
float[,] normalizedBiome = NormalizeBiome(rawBiomeMap);
// 3. Merge data before any physical instantiation
float[,] finalMap = MergeTerrainWithBiome(normalizedTerrain, normalizedBiome);
InstantiateMap(finalMap, rawTerrainMap);
GenerateVillage(finalMap, seed, rawTerrainMap);
}
02. Binary Space Partitioning (BSP)
While the overworld village uses an organic growth algorithm to expand naturally, the Dungeon required a more clinical and structured approach.
I implemented a BSP (Binary Space Partitioning) algorithm to recursively subdivide the available space. This guarantees non-overlapping rooms and creates a methodical exploration loop before the player reaches the boss room.
The global space is recursively subdivided into varying-sized areas to guarantee a non-overlapping structure.
Randomly sized rooms are calculated within each leaf node while strictly enforcing safety margins (padding).
The BSP logic tree is traversed bottom-up to mathematically connect the center of each zone with corridors of a defined thickness.
03. Deterministic Seed Derivation
A true rogue-like engine must guarantee that a specific text seed (e.g., "Aincrad") will always generate the exact same 15-floor tower layout. Relying on Unity's default Random.InitState was not robust enough for a multi-layered progression system.
I created a custom Seed Derivation Pipeline. The engine takes the Master Seed string, hashes it into a numeric value, and derives a unique sub-seed for the current floor level. This sub-seed is then injected into an isolated System.Random (PRNG) instance passed down to all generation scripts (World, Village, Dungeon), ensuring absolute mathematical determinism regardless of the execution order.
// --- 1. SEED DERIVATION (In the Manager) ---
private void LaunchCurrentLevel()
{
// Derive a unique seed based on the Master Hash and the Current Level
Vector2 levelSeed = runData.GetDerivedLevelSeed();
// Broadcast the deterministic mathematical constraint to the engine
OnSeedReady?.RaiseEvent(levelSeed);
}
// --- 2. PRNG ISOLATION (In the Generators) ---
void GenerateDungeonDeterministic(Vector2 seed)
{
// Create an isolated PRNG instance tied exclusively to this floor's seed
System.Random prng = new System.Random(seed.GetHashCode());
// Inject the PRNG into the generation pipeline
GenerateDungeon(prng);
}
// Every random decision (BSP splitting, room sizes) now uses 'prng.Next()'
// guaranteeing 100% reproducible results for this specific floor.