Dungeon Generator for Dungeons and Dragons

Room layout

To layout the rooms I’ve used something similar to Spelunky’s room generation. The generator choses the final boss room as its start and then chooses left or right. It then starts generating its parents room in that direction. Every time it does, it has a 50% chance to go up. When it does, it generates a room upwards and goes the opposite direction (from left to right and right to left).

However, the first time we can go left or right, we go the respective direction and we can’t go more than three times in those directions and instead go up.

After it is done generating to the entrance, it generates its side paths in a random direction from its parent’s room. This can be any direction, even down.

The code tries its generation from the beginning again if it can’t find an open position, but we haven’t gone far into the generation yet anyways so this shouldn’t be a problem.

// Instantiate and Declare
Node node = GetBossRoom();
node.position = new Vector2(0, 0);
bool left;
int horizontalIndex = 0;

node.positionAssigned = true;
left = GenerateRandomBool();

// While we aren't at the entrance-
while (node != entranceNode)
{
    // Randomly go up
    if (horizontalIndex != 0 && (GenerateInt(0, 2) < 1 || horizontalIndex >= 3))
    {
        node.GetParentNode().position = node.position + Vector2.up;
        left = !left;
        horizontalIndex = 0;
    }
    else
    { 
        // Go left or right
        node.GetParentNode().position = node.position + -Vector3.right * (left ? 1 : -1);
        horizontalIndex++;
    }

    // Node now has a position
    node.GetParentNode().positionAssigned = true;

    // Generate from parent
    node = node.GetParent();
}
Vector2 newPosition;
bool recursion;

// For every node, if it hasn't gotten a position yet-
for (int i = 0; i < nodes.Count; i++)
{
    if (!nodes[i].positionAssigned)
    {
        do
        {
            // Reset overlap
            recursion = false;

            // Generate random direction up, down, left or right from parent
            newPosition = GenerateAdjacentNode(nodes[i].GetParent(), GenerateDirection());

            // Check if the new position would overlap with another node. If it does-
            for (int j = 0; j < nodes.Count; j++)
            {
                if (Overlap(newPosition, nodes[j].position))
                {
                    ControlRecursion();
                    recursion = true;
                }
            }
        } while (recursion);

        // Node now has adjacent position
        nodes[i].positionAssigned = true;
        nodes[i].position = newPosition;
    }
}

The differences from my generator and Spelunky’s generator is that my generator isn’t bound to a width or height. Seperated, closed off rooms also can’t be generated as that is very redundant in Dungeons and Dragons. My generator always turns directions every time it goes up and has a minimum and maximum amount of times it can go left or right at once.

The reason for this method is that the combined methods result in a somewhat realistic result. Ignore the mimimum or maximum we need to go left or right, and you get linear designs. Ignore turning when going up and you get linear designs again. This long design can only happen so many times before it becomes noticeable to the players.

Figure 4. The red dot is the boss room and thus the start. The black arrow is the main path. The red arrow can’t happen, because we always go left or right first after going up. the orange arrow can’t happen, because we went right 3 times. The blue arrow/yellow dots is a side path.
Figure 5. An example of Spelunky’s layout generator (Game Maker’s Toolkit, 2016).
Figure 6. Cragmaw Castle. A dungeon that is not just square (Wizards of the Coast, 2014) from Lost Mine of Phandelver.
Figure 7. Visualization of the rooms and the difference between limiting how many times we can go up, left and right at once. The left is more compact and goes in different directions while the right is very linear even with two side rooms.
Figure 8. Binding of Isaac generation which is too random with linear pathing for what I want (Boris, 2020).

Related Posts