Yet Another C# Tile Dungeon

hacj
5 min readMay 20, 2020

--

OK so picture this, you’re in a game jam, and you’re making a rogue-lite in Unity just like everyone else.

You need some dungeon generation code so you can focus on your low poly pixel art and/or chiptune music.

Googling “C# dungeon generation code” and “C# roguelike dungeon code” only turns up 3,000 line monstrosities or s*** that doesn’t compile.

So, here you go. A super-simple one class C# script that generates a basic roguelike dungeon. You can customize the size the dungeon, the number of rooms, and the size of the rooms.

Use it with a console app:

Or in your Unity Game:

The Code (MIT license):

/*
* YATD (yet another tile dungeon)
* A super-simple one-class script to generate a basic roguelike-esque dungeon, designed for a roguelike-esque game
* Drop this into your project and get right to work on your gameplay.
* It generates a variety of square rooms and puts corridors between each.
* Also generates an "entry" and "exit" tile in distant rooms.
* */

using System;
using System.Collections.Generic;

namespace hacj
{
public class TileDungeon
{
public enum Tile
{
FLOOR,
INTERIOR_WALL,
EXTERIOR_WALL,
ENTRY,
EXIT
}

private Tile[,] tiles;

// Seed used to generate dungeon -> using the same seed always results in the same layout.
private int seed;

// max dim of dungeon
public int width { get; private set; }
public int height { get; private set; }

public int[] start { get; private set; }
public int[] exit { get; private set; }

// room sizes
private int minRoomSize;
private int maxRoomSize;

// room count min and max
private int minRoomCount;
private int maxRoomCount;

// Some default args for a dungeon I think looks nice for my use case. Overwrite them if you like!
public TileDungeon(int seed, int width = 35, int height = 35, int minRoomCount = 7, int maxRoomCount = 9, int minRoomSize = 3, int maxRoomSize = 5)
{
this.seed = seed;
this.width = width;
this.height = height;
this.minRoomSize = minRoomSize;
this.maxRoomSize = maxRoomSize;
this.minRoomCount = minRoomCount;
this.maxRoomCount = maxRoomCount;

// Set up tile grid with all walls
fillInWalls();

// Carve out rooms
List<int[]> rooms = addRooms();

// Connect rooms, add doors
addCorridors(rooms);

// Add the entry and exit tiles.
addEntryExit(rooms);

// Make distiction for intertor and exterior walls.
determineWallTypes();
}

// Public functions

// Better not go out of bounds lmao
public Tile getTileAt(int x, int y)
{
return tiles[x, y];
}

// Generation stuff

public void fillInWalls()
{
tiles = new Tile[width, height];

for (int x = 0; x != width; ++x)
{
for (int y = 0; y != height; ++y)
{
tiles[x, y] = Tile.EXTERIOR_WALL;
}
}
}

public List<int[]> addRooms()
{
List<int[]> roomCenters = new List<int[]>();

int roomCount = randomRange( minRoomCount, maxRoomCount);
for (int i = 0; i != roomCount; ++i)
{
int x = randomRange(maxRoomSize, width - maxRoomSize);
int y = randomRange(maxRoomSize, height - maxRoomSize);
int w = randomRange(minRoomSize, maxRoomSize);
int h = randomRange(minRoomSize, maxRoomSize);
carveOpen(x - (w/2), y - (h/2), w, h);
roomCenters.Add(new int[2] { x, y });
}
return roomCenters;
}

public void addCorridors(List<int[]> centers)
{
for(int i = 0; i != centers.Count - 1; ++i)
{
// Go from room to room, adding corridor between each.
// carve only accepts positive width, height so find the lower x/y coords and go higher.
carveOpen(Math.Min(centers[i][0], centers[i + 1][0]), centers[i][1], 1 + Math.Abs(centers[i + 1][0] - centers[i][0]), 1);
carveOpen(centers[i+1][0], Math.Min(centers[i][1], centers[i + 1][1]), 1, 1 + Math.Abs(centers[i + 1][1] - centers[i][1]));
}
}

public void addEntryExit(List<int[]> centers)
{
int distHi = 0;
int startIdx = -1;
int endIdx = -1;

// Pick the two rooms that are farthest away from each other.
for (int i = 0; i != centers.Count; ++i)
{
for (int j = 0; j != centers.Count; ++j)
{
int dist = Math.Abs(centers[i][0] - centers[j][0]) + Math.Abs(centers[i][1] - centers[j][1]);

if (dist > distHi)
{
distHi = dist;
startIdx = i;
endIdx = j;
}
}
}

start = centers[startIdx];
exit = centers[endIdx];

tiles[start[0], start[1]] = Tile.ENTRY;
tiles[exit[0], exit[1]] = Tile.EXIT;
}

public void determineWallTypes()
{
for (int x = 0; x != width; ++x)
{
for (int y = 0; y != height; ++y)
{
if(tiles[x,y] != Tile.FLOOR)
{
continue;
}

for (int dx = -1; dx <= 1; ++dx)
{
for (int dy = -1; dy <= 1; ++dy)
{
// If there's an exterior wall, mark it as interior, instead.
if(tiles[x+dx,y+dy] == Tile.EXTERIOR_WALL)
{
tiles[x + dx, y + dy] = Tile.INTERIOR_WALL;
}
}
}
}
}
}

// Fill part of the map with "floor", either as a rectangle
private void carveOpen(int x, int y, int width, int height)
{
if(width < 1 || height < 1)
{
return;
}
for (int dx = x; dx != x + width; ++dx)
{
for (int dy = y; dy != y + height; ++dy)
{
tiles[dx, dy] = Tile.FLOOR;
}
}
}

// Portable method to derive a hashed value from an integer, which is useful to generate a "random" number
private static int inthash(ref int x)
{
if (x == 0)
{
x = 1;
}
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}

// Helper to get random value from seed
private int randomRange(int min, int max)
{
int range = inthash(ref seed) % ((max + 1) - min);
return min + range;
}
}
}

Follow me on twitter please I live off clout

--

--