Skip to main content
GameDev.net gamedev.net

PRO Tired of ads? Read GameDev.net ad-free and help keep the community independent with GameDev Pro — $3/month.

Procedural Generation of 2D Tile Maps in Games
Article

Procedural Generation of 2D Tile Maps in Games

The article explains how to build a procedural 2D cave map generator using cellular automata. It covers map representation, random seeding, smoothing, ensuring connectivity with flood fill, and rendering in engines like Unity or Godot. It also suggests extensions (rooms, biomes, streaming).

andrewmurphy
September 24, 2025 2.3k views

Introduction

Procedural content generation allows game developers to create diverse, replayable environments without manually designing each level. In this tutorial, we’ll cover a step-by-step method for generating a cave-like 2D map using cellular automata and show how to integrate the result into your engine of choice.

We’ll touch on:

  • Representing your map in code
  • Seeding and smoothing with CA
  • Ensuring connectivity
  • Rendering and integrating into a game loop
  • Extending with variations

1. Map Representation

We’ll store tiles in a 2D array:

enum TileType { Wall, Floor }
TileType[,] map = new TileType[width, height];

// Initialize all tiles as Wall
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
map[x, y] = TileType.Wall;

Helper functions for bounds and neighbors:

bool InBounds(int x, int y) =>
x >= 0 && x < width && y >= 0 && y < height;

IEnumerable<(int, int)> Neighbors4(int x, int y) {
yield return (x+1, y);
yield return (x-1, y);
yield return (x, y+1);
yield return (x, y-1);
}

2. Seeding with Random Noise

Randomly seed tiles with a ratio of walls to floors.

Random rnd = new Random(seed);
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
map[x, y] = (rnd.NextDouble() < 0.45) ? TileType.Wall : TileType.Floor;

This gives a noisy distribution, ready for smoothing.

3. Cellular Automata Smoothing

Iterate a few times applying neighbor-based rules:

TileType[,] newMap = new TileType[width, height];

for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++) {
int wallCount = CountWallNeighbors8(x, y);

if (map[x, y] == TileType.Wall) {
newMap[x, y] = (wallCount >= 4) ? TileType.Wall : TileType.Floor;
} else {
newMap[x, y] = (wallCount >= 5) ? TileType.Wall : TileType.Floor;
}
}
map = newMap;

After 4–6 passes, blobs begin to look like caves.

4. Ensuring Connectivity

To prevent isolated pockets, run a flood fill from the player’s spawn point and remove unreachable areas.

void FloodFill(int startX, int startY) {
var visited = new bool[width, height];
var q = new Queue<(int,int)>();
q.Enqueue((startX, startY));
visited[startX, startY] = true;

while (q.Count > 0) {
var (cx, cy) = q.Dequeue();
foreach (var (nx, ny) in Neighbors4(cx, cy)) {
if (InBounds(nx, ny) && !visited[nx, ny] && map[nx, ny] == TileType.Floor) {
visited[nx, ny] = true;
q.Enqueue((nx, ny));
}
}
}

// Remove unreachable floors
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
if (map[x, y] == TileType.Floor && !visited[x, y])
map[x, y] = TileType.Wall;
}

5. Rendering in Engine

  • Unity: Use a Tilemap component or instantiate prefabs.
  • Godot: Use a TileMap node with cell placement.
  • Unreal: Render meshes on a grid or use Paper2D for 2D tilemaps.

Walls get colliders; floors are walkable. Add pathfinding using A* for a complete exploration system.

6. Extensions

  • Hybridize with room-based generation for structured layouts.
  • Add biomes or multiple tile types.
  • Store random seeds for reproducibility.
  • Stream large maps chunk-by-chunk.

Conclusion

We’ve built a simple but flexible 2D cave generator using cellular automata. You can extend this system for roguelikes, survival games, or sandbox exploration titles.

Related Tutorials

Discussion

Discussion

Loading comments...