Issues with procedural room generation

Hello! I’m working on a top down RPG in the style of the old Final Fantasy games or Stardew Valley, and I’m trying to set up a procedural dungeon generator but I’m running into an issue I can’t figure out with my code.

Sorry in advance for such a long post but I’m super stuck on this.

Here is my generator code:

public void StartRoomGeneration()
    {
        // This starts the process of generating a dungeon
        ClearRooms();
        GenerateStarterRoom();
        ConnectRoomsList();
    }
    public void GenerateStarterRoom()
    {
        // Generates a starter room for a new dungeon

        Debug.Log("<color=blue>GenerateRooms called...</color>");      
        GameObject starterRoom = null;  
      
        starterRoom = Instantiate(starterRoomPrefab);

        SetRoomParent(starterRoom);
        starterRoom.transform.localPosition = Vector3.zero;
        roomList.Add(starterRoom);
    }
    private void ConnectRoomsList()
    {
        // Generates rooms for all rooms on the roomList and closes connections that can't find a match

        foreach (GameObject room in roomList)
        {          
            DungeonRoom dungeonRoom = room.GetComponent<DungeonRoom>();
            generateRooms = GenerateRooms(dungeonRoom);
            StartCoroutine(generateRooms);
        }      
    }
    private IEnumerator GenerateRooms(DungeonRoom room)
    {
        Debug.Log("<color=orange>Checking connections for room: </color><color=blue>" + room.gameObject.name + "</color>");

        for (int connectionIndex = 0; connectionIndex < room.RoomConnections.Count; connectionIndex++)
        {          
            if (room.RoomConnections[connectionIndex].IsOpen)
            {
                Debug.Log("<color=green>Open connection found at index: </color><color=blue>" + connectionIndex + "</color>");              

                GameObject adjacentRoom = null;
                bool validRoomFound = false;

                // This shuffles the possibleRooms list so that it's random but every entry only gets tried once
                for (int i = 0; i < possibleRooms.Count; i++)
                {
                    int j = Random.Range(i, possibleRooms.Count);
                    GameObject t = possibleRooms[i];
                    possibleRooms[i] = possibleRooms[j];
                    possibleRooms[j] = t;
                }

                for (int possibleRoomIndex = 0; possibleRoomIndex < possibleRooms.Count; possibleRoomIndex++)
                {
                    adjacentRoom = Instantiate(possibleRooms[possibleRoomIndex]);

                    // After the room is Instantiated, it should be moved to the desired location with the desired offset

                    SetRoomParent(adjacentRoom);
                    adjacentRoom.transform.localPosition = Vector3.zero;

                    DungeonRoom adjacentDungeonRoom = adjacentRoom.GetComponent<DungeonRoom>();

                    Transform targetTransform = room.RoomConnections[connectionIndex].transform;

                    bool validConnection = false;
                    int connection = -1;

                    for (int i = 0; i < adjacentDungeonRoom.RoomConnections.Count; i++)
                    {
                        float xOffset = adjacentRoom.transform.localPosition.x - adjacentDungeonRoom.RoomConnections[i].gameObject.transform.localPosition.x;
                        float yOffset = adjacentRoom.transform.localPosition.y - adjacentDungeonRoom.RoomConnections[i].gameObject.transform.localPosition.y;
                        //Debug.Log("<color=green>room x: " + adjacentRoom.transform.localPosition.x + " waypoint x: " + adjacentDungeonRoom.roomWaypoints[0].waypoint.gameObject.transform.localPosition.x + "</color>");
                        //Debug.Log("<color=green>room y: " + adjacentRoom.transform.localPosition.y + " waypoint y: " + adjacentDungeonRoom.roomWaypoints[0].waypoint.gameObject.transform.localPosition.y + "</color>");
                        //Debug.Log("<color=green>xOffset: " + xOffset + " yOffset: " + yOffset + "</color>");

                        adjacentRoom.transform.localPosition = targetTransform.localPosition + new Vector3(xOffset, yOffset, 0);

                        // collisions should be checked next, once the room is in position

                        yield return new WaitForFixedUpdate();

                        // if the location is valid, the connection is set to valid and the room's connection index is stored
                        if (adjacentDungeonRoom.ValidLocation)
                        {
                            connection = i;
                            validConnection = true;
                            break;
                        }

                    }

                    // if placement is invalid, delete the room
                    // else, keep the room and mark waypoint as complete
                    if (validConnection)
                    {                      
                        Debug.Log("<color=green>Valid location and connection found!</color>");
                        validRoomFound = true;
                        room.CloseConnection(connectionIndex);
                        adjacentDungeonRoom.CloseConnection(connection);
                        break;
                    }
                    else
                    {
                        Debug.Log("<color=orange>Invalid location, destroying room...</color>");
                      
                        DestroyImmediate(adjacentRoom);
                        validRoomFound = false;
                    }
                }
                if (validRoomFound)
                {
                    Debug.Log("<color=orange>Valid location, adding room </color><color=blue>" + adjacentRoom.gameObject.name + "</color><color=orange> to newRoomList...</color>");                  
                    newRoomList.Add(adjacentRoom);
                }
                else
                {
                    Debug.Log("<color=orange>No valid rooms found, closing waypoint </color><color=blue>" + connectionIndex
                            + "</color><color=orange> for room </color><color=blue>" + room.gameObject.name + "</color>");
                    room.CloseConnection(connectionIndex);
                }
            }
        }

        if (newRoomList.Count > 0)
        {
            Debug.Log("<color=#2697ab>newRoomList has rooms to add...</color>");
            foreach (GameObject newRoom in newRoomList)
            {
                roomList.Add(newRoom);
            }
            newRoomList.Clear();
            ConnectRoomsList();
        }
        else
        {
            Debug.Log("<color=#2d2a82>No new rooms, dungeon is complete!</color>");
        }
    }

Just to walk you through my current setup, the starterRoom is a room with 4 connections, one in each direction, and then my possibleRooms include 6 rooms: 1 dead end room for each direction, and 2 hallways that have 2 connections (examples in the screenshot below).


Green boxes represent colliders, and ignore the fact that the dead end rooms don’t look like they connect (need to redo their art).

So the main issue I’m having is that for a reason that I can’t pin down, when a room get’s placed, it seems that something funny is going on with the roomList because then it seems like it won’t place down that room again. For example, the hallways (which I have 2 of in the possibleRooms list, as seen above) only ever get placed twice, no matter how many times I run the generator. So it seems that each room is only ever getting place ones. I tested this by making the possibleRooms list consist of only the two hallway rooms, and sure enough, they both got placed and then every other connection was closed off saying it couldn’t find a matching room.

This is not my intention and I can’t seem to figure out why that’s happening. I’m sure there’s some logic that’s off somewhere in my code but I can’t seem to figure it out.

The other issue I’m having is that for whatever reason, the hallways can’t seem to find a fitting room to their far connection, when in fact, the dead end rooms should be working just fine for that. I think this is a separate issue, because in the example above, the north and east dead end rooms aren’t even being used, but they aren’t connecting to the hallways for whatever reason.

I’m sorry for such a long post, but I’ve been working on this for a while now and I genuinely can’t figure it out so if anyone has any ideas I would love to hear them! Thanks!

Line 100 closes the room.

Line 38 requires the room .IsOpen

Is that the problem?

Looking at it now…

Edit: First off, thanks for the reply! So I’m not sure that’s the issue moving that line around and shutting it off altogether didn’t really change much behavior (except that the hallways end connections stayed open but empty).

What I’m trying to do there is check if the room’s connections are open and if they are add a room or close them if no rooms fit.


This is a good way to generate rooms I have used it didn’t had a problem I think its your solution. Also its very simple

So I might be on to something. I tried to make the generator work with only 2 possibleRooms, both hallway rooms, because I was trying to figure out why in the world only the 1st and second connections on the starter room (starting from the top and going clockwise) were generating hallways.

I added Debug.Break() sections in different parts of my code to get a better look at things and I think I may have found the issue.

I thought something weird might be happening with the possibleRooms list, like maybe each room was only getting used once or something but I think what’s happening is that there might be something off with my position calculation when attempting to add a new room’s connection at another room’s connection.

Check out this screenshot:

The southern hallway, which is what I would expect to be added now that only hallways are on the possibleRooms list is being positioned incorrectly here.

Notice the purple connection marker on the starterRoom, that means that’s the connection currently being tested, but the hallways northern connection is above it, rather than right on in, so the rooms are overlapping, and no match is being found, thus, the final result is a starter room with only hallways on the north and east.

Hopefully that’s clear, so anyway, I experimented with lines 72 and 73 in my code and by changing the sign from - to + I get the opposite effect (empty North and East connections on the starterRoom and hallways on the South and West).

So I’m thinking that maybe something is wrong with my math positioning the rooms. I’m looking through it all now, but I’m not sure what’s wrong so if anyone has any tips feel free to chime in!

Now you’re engineering! Delete everything until you find the problem. Simplify, simplify, simplify.

I would carry the above even further, and simplify it so you can Debug.Log() out the values going into each position computation and debug them, and if there’s only a few of them it’s easier to reason about why some are correct and some are wrong.

Debug.Break() and Debug.Log() are the most valuable and powerful timeless debugging tools there are. You will soon find the issue, I’m sure. You’re already well on the way.

So I skimmed through these before I even started this whole process during the “research” phase which is usually watching a ton of youtube videos haha

But my general understanding of this particular series is that since it uses rooms that are all the same size, the measurements of the rooms are not needed to be taken into account, and so it’s a bit of a different situation.

Did you have a different take on this?

If I understand you right then your problem is that all the rooms in the tutorial have the same size so therefore its mandatory? Because its not you only have to place your spawnpoints right

Looking at your posted graphics this does not seem to be the case at all. Not only are they different sizes, but they can be aligned at least two different ways, horizontally and vertically.

I’ll take another look at this then, thanks a lot for the suggestion!

Ok, so I figured out the issues, and there were more than I anticipated haha though I can’t say I’m too surprised…

I’ll go through them here in case anyone finds this helpful at all:

  1. First issue was that when checking a new room’s connections to an existing room’s (starting in line 70) I wasn’t doing a good job of marking each connection.

  2. One of the big issues that really got me was that when a room’s connection yield a failed result (that is, when OnTriggerEnter2D was called) the room’s location was being marked as invalid, and then upon trying a new connection for that room, even though I started making sure that the room’s validity was set back to true before the new location was attempted, sometimes the move would happen inside the previous collision, but since OnTriggerEnter2D only gets called when a collider enters another, it wasn’t getting called again. This resulted in some connections being marked as valid even though they should be. I resolved this by deactivating the room’s game object and reactivating it immediately after it was moved to it’s new position, before the physics update happened. (This took me forever to figure out hahaha)

  3. As suspected, the math for connecting the rooms was off. It worked fine for the starter room’s 1st connections because the starter room was at 0, 0, but future rooms that weren’t didn’t align right with their new rooms. I fixed this by adding their offset from the center into part of the calculation for where the room should be placed.

So, quite a lot more wrong than I though haha, but I think it’s pretty much working now, thanks to everyone who helped out here.

I’m now in the process of creating more room art so that they can all line up properly, maybe I’ll post a pic with the finished product as an update to this post later, again, thanks a ton to everyone!!!

2 Likes