advanced Utilities

Procedural Dungeon Generator

BSP-based dungeon generator that creates random room layouts with corridors, doors, and spawn points.

Unity 2022.3+ · 4.0 KB · DungeonGenerator.cs

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

DungeonGenerator.cs
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);
    }
}