Procedural Dungeon Generator
BSP-based dungeon generator that creates random room layouts with corridors, doors, and spawn points.
How to Use
1
Create floor and wall prefabs (cubes scaled to tile size)
2
Attach DungeonGenerator to a manager object
3
Assign floor and wall prefabs
4
Configure dungeon size, room size range, and split depth
5
Generates automatically on Start (or call Generate() manually)
6
Access rooms: generator.Rooms for room positions/sizes
7
Get spawn points: generator.GetRandomSpawnPoint()
8
Set seed for reproducible layouts (0 = random each time)
Source Code
C#
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// BSP (Binary Space Partitioning) dungeon generator.
/// Creates random room layouts connected by corridors.
/// </summary>
public class DungeonGenerator : MonoBehaviour
{
[Header("Dungeon Size")]
[SerializeField] private int width = 60;
[SerializeField] private int height = 60;
[SerializeField] private float tileSize = 1f;
[Header("Rooms")]
[SerializeField] private int minRoomSize = 6;
[SerializeField] private int maxRoomSize = 15;
[SerializeField] private int maxSplitDepth = 5;
[Header("Corridors")]
[SerializeField] private int corridorWidth = 2;
[Header("Prefabs")]
[SerializeField] private GameObject floorPrefab;
[SerializeField] private GameObject wallPrefab;
[Header("Settings")]
[SerializeField] private bool generateOnStart = true;
[SerializeField] private int seed = 0; // 0 = random
private int[,] map; // 0=empty, 1=floor, 2=wall
private List<RectInt> rooms = new List<RectInt>();
private List<Vector2Int> spawnPoints = new List<Vector2Int>();
private Transform dungeonParent;
/// <summary>Generated rooms.</summary>
public IReadOnlyList<RectInt> Rooms => rooms;
/// <summary>Spawn point positions (centers of rooms).</summary>
public IReadOnlyList<Vector2Int> SpawnPoints => spawnPoints;
private void Start()
{
if (generateOnStart)
Generate();
}
/// <summary>
/// Generate a new dungeon layout.
/// </summary>
public void Generate()
{
if (seed != 0)
Random.InitState(seed);
else
Random.InitState(System.Environment.TickCount);
Clear();
map = new int[width, height];
rooms.Clear();
spawnPoints.Clear();
// BSP split
List<RectInt> partitions = new List<RectInt>();
BSPSplit(new RectInt(1, 1, width - 2, height - 2), maxSplitDepth, partitions);
// Create rooms within partitions
foreach (var partition in partitions)
{
CreateRoom(partition);
}
// Connect rooms with corridors
for (int i = 0; i < rooms.Count - 1; i++)
{
ConnectRooms(rooms[i], rooms[i + 1]);
}
// Add walls
AddWalls();
// Instantiate
BuildDungeon();
}
/// <summary>Clear the generated dungeon.</summary>
public void Clear()
{
if (dungeonParent != null)
Destroy(dungeonParent.gameObject);
}
private void BSPSplit(RectInt area, int depth, List<RectInt> result)
{
if (depth <= 0 || area.width < minRoomSize * 2 || area.height < minRoomSize * 2)
{
result.Add(area);
return;
}
bool splitHorizontal = Random.value > 0.5f;
if (area.width > area.height * 1.5f) splitHorizontal = false;
if (area.height > area.width * 1.5f) splitHorizontal = true;
if (splitHorizontal)
{
int split = Random.Range(area.y + minRoomSize, area.yMax - minRoomSize);
BSPSplit(new RectInt(area.x, area.y, area.width, split - area.y), depth - 1, result);
BSPSplit(new RectInt(area.x, split, area.width, area.yMax - split), depth - 1, result);
}
else
{
int split = Random.Range(area.x + minRoomSize, area.xMax - minRoomSize);
BSPSplit(new RectInt(area.x, area.y, split - area.x, area.height), depth - 1, result);
BSPSplit(new RectInt(split, area.y, area.xMax - split, area.height), depth - 1, result);
}
}
private void CreateRoom(RectInt partition)
{
int roomW = Random.Range(minRoomSize, Mathf.Min(maxRoomSize, partition.width));
int roomH = Random.Range(minRoomSize, Mathf.Min(maxRoomSize, partition.height));
int roomX = Random.Range(partition.x, partition.xMax - roomW);
int roomY = Random.Range(partition.y, partition.yMax - roomH);
RectInt room = new RectInt(roomX, roomY, roomW, roomH);
rooms.Add(room);
Vector2Int center = new Vector2Int(roomX + roomW / 2, roomY + roomH / 2);
spawnPoints.Add(center);
for (int x = room.x; x < room.xMax; x++)
for (int y = room.y; y < room.yMax; y++)
if (x >= 0 && x < width && y >= 0 && y < height)
map[x, y] = 1;
}
private void ConnectRooms(RectInt roomA, RectInt roomB)
{
Vector2Int a = new Vector2Int(roomA.x + roomA.width / 2, roomA.y + roomA.height / 2);
Vector2Int b = new Vector2Int(roomB.x + roomB.width / 2, roomB.y + roomB.height / 2);
// L-shaped corridor
if (Random.value > 0.5f)
{
CarveHorizontal(a.x, b.x, a.y);
CarveVertical(a.y, b.y, b.x);
}
else
{
CarveVertical(a.y, b.y, a.x);
CarveHorizontal(a.x, b.x, b.y);
}
}
private void CarveHorizontal(int x1, int x2, int y)
{
int start = Mathf.Min(x1, x2);
int end = Mathf.Max(x1, x2);
for (int x = start; x <= end; x++)
for (int w = 0; w < corridorWidth; w++)
if (x >= 0 && x < width && y + w >= 0 && y + w < height)
map[x, y + w] = 1;
}
private void CarveVertical(int y1, int y2, int x)
{
int start = Mathf.Min(y1, y2);
int end = Mathf.Max(y1, y2);
for (int y = start; y <= end; y++)
for (int w = 0; w < corridorWidth; w++)
if (x + w >= 0 && x + w < width && y >= 0 && y < height)
map[x + w, y] = 1;
}
private void AddWalls()
{
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
if (map[x, y] != 0) continue;
// Check if adjacent to floor
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
int nx = x + dx, ny = y + dy;
if (nx >= 0 && nx < width && ny >= 0 && ny < height && map[nx, ny] == 1)
{
map[x, y] = 2;
goto nextCell;
}
}
}
nextCell:;
}
}
}
private void BuildDungeon()
{
GameObject parent = new GameObject("Dungeon");
dungeonParent = parent.transform;
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
Vector3 pos = new Vector3(x * tileSize, 0f, y * tileSize);
if (map[x, y] == 1 && floorPrefab != null)
Instantiate(floorPrefab, pos, Quaternion.identity, dungeonParent);
else if (map[x, y] == 2 && wallPrefab != null)
Instantiate(wallPrefab, pos, Quaternion.identity, dungeonParent);
}
}
}
/// <summary>Get a random spawn point in the dungeon.</summary>
public Vector3 GetRandomSpawnPoint()
{
if (spawnPoints.Count == 0) return Vector3.zero;
Vector2Int p = spawnPoints[Random.Range(0, spawnPoints.Count)];
return new Vector3(p.x * tileSize, 0f, p.y * tileSize);
}
}