Procedural / Random generation for 2D / 3D games

Hey guys! I’ve been searching some information about procedural generation. I’m stuyding videogames development and I want to create somehow a Roguelike. I don’t need the map to be 100% randomly generated, I’be been thinking about creating about 40/50 prefabs with rooms, and randomly place them in an order. That’s the point I can’t get. How can I make some rooms to appear next each other? Also, is there any way to make that script short? My idea was to, manually start using the random.range and conditionals, but that would take the script to a masacre (And it wouldnt be neither optimized).

So, if anyone have any idea please, let’s talk about it! All I’ve searched is about how to randomly generate rooms, but not how to connect them or how to make the props appear too (meaning that they just generate the empty rooms). Hope someone can help me a bit! Cheers ^^

In your 3D tool you’ve got to put marker objects on your entrances by hand, you can make them visible cursor models to aid in debug. Turn to null objects after debug.

These marker objects must all point their X axis along the door frame bottom.
These marker objects must all point their Y axis up.
These marker objects must all point their Z axis outward from the door.
These marker objects must all be positioned exactly halfway along the bottom of the doorframe.

I recommend quantizing all room entrances to grid in 3D tool, makes the job easier.

Now in script you grab a random module prefab, you enumerate it’s children (the marker objects) to find its connection nodes.

You then use vector add/sub to join two modules together such that their markers align.

So lay down first room prefab R1. Pick a random marker on it M1

Position R1 somewhere.

Lay down another random room R2, pick a random marker on it M2.

Make pos/rot or R2 be M1.

Then offset pos/rot of R2 by negative localspace pos/rot of M2.

The hard part is: each new room you lay down you have to check for collisions with existing rooms, various ways to do this, I like ray casting into the scene as it builds. I find bounding rect of room prefab and then I step around the perimeter raycasting down checking for exsisting room colliders. I also step between diagonals.

If room collides pick another random R2 and M1 and try again.

If no collision, destroy M1 and M2, then move on: M2 becomes M1, choose a new M2

After you’ve grown N rooms then finish the map by looking for remaining markers, deleting them and replacing with locked closed doors, cul-de-sacs, walls etc.

To layout objects, I use raycast probing funcs to check for free area with some minimum radius. Alternatively you can also put helper markers on your prefab, then you’d have one group of markers for doors, and another group for viable pickup spawn points. The probing method is more general and scales effortlessly once you’ve coded it though. It can be used to safely spawn any mesh anywhere.

That’s the basics of it.

I did this procedural mapper for mobile VR, only renders (makes visible) rooms as they are entered via the doors, so it can handle many rooms even on low end mobile:

This from 2013 also has stairs, didn’t use raycasts but checked for overlapping box volumes:

I’m doing more procedural layout in my latest game, the road layout use the same marker method I mention above. Building layout uses chain encoding to find road perimeter with ray cast probes, then buildings just advance along chain encoded markers until no collision detected.

damn, too much time procrastinating… gotta do some work…

1 Like

Wow man thats amazing. I think I gotta read this a couple of times more, and start thinking on it. On my first read, I’ve gotten so much, and you’ve helped me a lot. I wonder if I could ask you more questions if I get stuck at any point, because I’m new on this and I’m trying to figure out which method is the best to dont “hardcode”. I don’t need it to be completely procedural, I can do it having X rooms and generating them randomly, but that’s where questions as “how to place them correctly” come.
This answer has given me a lot of information, thank you sir!

1 Like

Sure I can answer some questions. I’ll watch this thread but I can’t guarantee timely replies.

Yes, procedural stuff makes you stop and think and scratch your head often, but it’s very rewarding.

We all have to work to limits and decide how deep we are going to dive, I would love to write a fully procedural planet gen like NoMansSky, maybe one day I will try it, after a lot more reading and learning.

@SiliconDroid wrote some good stuff above there… I find this area of engineering very rewarding, but also very challenging. As noted in the last post, start small because you will be amazed at how wooly things can get in a short timeframe.

Not only that but there’s many different ways of generating stuff. The prefabs-as-chunks approach you suggest is a great compromise between fully random and fully pre-scripted and a lot of games use variations on that.

The cool thing about Unity is that it is easy to try 10 different ways of constructing things, and yet have the same character controller (and AI controllers) work fine in all the ways you’re trying, so you can have a lot of broad variety: a city level, a wide-open plains level, a dungeon, etc.

1 Like

I’d like to show you my code, and ask for some suggestions.

public GameObject[] Rooms;
    public int maxRoomsinLevel = 2;

    GameObject[] puertas;
    //GameObject[] entradas;
    //GameObject[] salas;
    float chance;
    int rng;

    void Start () {

        chance = 100 / Rooms.Length; // SET THE SAME CHANCE TO ALL ROOMS TO APPEAR // DA LA MISMA PROBABILIDAD A TODAS LAS SALAS DE APARECER


        for (int i = 0; i < maxRoomsinLevel; i++) // FOR Nº 1 // GENERATES X ROOMS, BEING X "maxRoomsinLevel" // GENERA X SALAS, SIENDO X "maxRoomsinLevel"
        {
            rng = Random.Range (0, 100); Debug.Log (rng);
            puertas = GameObject.FindGameObjectsWithTag ("Puerta");
            //salas = GameObject.FindGameObjectsWithTag ("Sala");
            //entradas = GameObject.FindGameObjectsWithTag ("Entrada");

            for (int j = 0; j < Rooms.Length; j++) // FOR Nº 1.1 // GENERATES A ROOM DEPENDING ON THE CHANCE // GENERA UNA SALA DEPENDIENDO DEL PORCENTAJE
            {
              
                if (rng < chance * (j+1) && rng >= chance * j)
                {
                    Debug.Log ("J: " + j);
                    Instantiate (Rooms [j], puertas [i].transform.position, transform.rotation);
                }

            } // END OF FOR Rooms.Length

        } // END OF FOR maxRoomsinLevel

    } // END OF START

So, this generates “random” rooms. My problem is that I dont know how to make them appear next to the last one. I’ve set empty game objects at the entrance, and the next room appears there, but I can’t move it so that room’s entrance get stick to the other.

I also want to know, how could I set more entrances so the rooms could appear just in 1 or both.

Thanks! ^^

I’m sure droid will chime in on this more but basically if you are stitching the entrance transform of room A to the entrance transform of room B, you need to do these steps, assuming everything is in the X/Z plane:

  1. spin Room B around the Z axis until its entrance is precisely facing opposite Room A’s entrance
  2. translate Room B a distance of (Room A Entrance minus Room A root)
  3. translate Room B additional distance of (Room B root minus Room B Entrance)

Basically:

Step 1 spins them in space so they will mate
Step 2 takes into account Room A’s entrance distance from Room A’s origin
Step 3 takes into account Room B’s entrance distance from Room B’s origin

You can also make hallway segments to space out your rooms a bit, and these have endpoints as well.

I already generate the center 2nd room on the exit of the 1st. I just need to move the 2nd one, but I don’t know how to do it. Also, how could I do hallways to match between both? It’s a 3D project, so idk how to do it.

Well I’m not sure if the new room you instantiate has door markers on it?

If it does then you need to choose one of its markers randomly and connect the two rooms so their markers align.

I will leave that as an exercise to the reader to work out the simple vector additions/subtractions to do that. It may help to cut out rooms from graph paper and mark them up with pen, then step though the rotations and translations needed to get those entrances to align.

If you can’t get over that part then, well… hit up the asset store for something as far rockier paths await.

I could post my code functions for all this stuff, but then parasites will just use it to paste into unity and spam up the play store, so meh.

That’s it in a nutshell. Typo in 1. tho: spin in Y. defo a typo, you know it’s Y lol.

To calculate the Y rot needed it’s RotB.euler.y - RotA.euler.y + 180

You can also roll 2 and 3 into one step:
translate Room B additional distance of (Room A Entrance minus Room B Entrance)

1 Like

I know man, I don’t neither want you to copy paste your code, it just wouldnt be fair. I just wanna know how it works so I can expand it. Ill tell you what am I doing now.

puertas[ ] is the array of empty gameobjects that im using to position the next generated room. So anytime I generate a room, the gameobject tagged “Puerta” is set as part of the array. Next step is generating the next room in that position, and repeat the process. But this just work with 1 exit, I cannot generate more rooms in other walls. Also, when a room is generated, its position is centered to the exit (puerta gameobject), and I’ve tried taking another gameobject, and placing it where the entrance goes, but I can’t get it from the script, neither move the position of the whole new room.

I’ve used Rooms_.transform.position -= entrance*.transform.position (meaning “i” as the new generated room), but it won’t work.*_

In this case I would not recommend grabbing things with tags, get things by their child position, it’s faster and your code will be cleaner and more general.

Also I would not be tempted to make lists or arrays, use the scene heirachy as your container as you build it, that will save many headaches as you scale: makes for easier debug because you can look at heirachy in scene editor. The array of game object prefabs you are using for your room selection is OK; I suppose you are dragging objects into the script in unity inspector. Myself I code everything and store prefabs in a Resources folder.

So…

In Editor:
Make multiple room module prefabs, door marker objects must be children of rooms.

In Script:
Spawn 2 random rooms prefabs with child door markers, these GameObjects are oRoom1 and oRoom2, just leave them both at (0,0,0).

Call the func below on them.

If it returns true you got a good connection and you can proceed to loop:

  • Make oRoom1 be a random one of the existing rooms in the scene.
  • Make oRoom2 be a new random room prefab.
  • Call the func below on them.
  • if it returns true goto 1. until you have N rooms.
  • If it returns false, destroy oRoom2 and goto 1.

Don’t loop for the required number of rooms, some connections will fail (when you get to collision detect part) and you will need to try a new connect.

So loop for some maximum try amount like 1000, and increment an int each time you get good connect, when int == roomsRequired you’re done.

You may want to add all succesfully joined rooms to a common parent GameObject helper called oRooms or something. This way you can easily select random room by selecting a random child of it.

So your job is to figure out this func, this thread contains all the info you need:

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //    THIS FUNC WILL CONNECT ROOM2 TO ROOM1, RANDOM DOORS WILL BE USED
        private bool ConnectRooms(GameObject oRoom1, GameObject oRoom2)
        {
            //  ROOM 1 GOT DOOR/S ?
            int iDoors1 = oRoom1.transform.childCount;
            if (iDoors1 == 0)
            {
                return false;
            }
            //  ROOM 2 GOT DOOR/S ?
            int iDoors2 = oRoom2.transform.childCount;
            if (iDoors2 == 0)
            {
                return false;
            }
            //  PICK A RANDOM DOOR1
            Transform tDoor1 = oRoom1.transform.GetChild(Random.Range(0, iDoors1));

            //  PICK A RANDOM DOOR2
            Transform tDoor2 = oRoom2.transform.GetChild(Random.Range(0, iDoors2));

            //   TRANSLATE AND ROTATE oRoom2 SO THAT tDoor1 IS FACING tDoor2
            //   THE ANSWER TO THIS PUZZLE IS IN THIS THREAD

            //  KILL CONNECTED DOOR MARKERS
            DestroyImmediate(tDoor1);
            DestroyImmediate(tDoor2);

            //  DONE
            return true;
        }

Hmmm so I’m getting if there is any door counting the room’s children. But my prefab is composed by many objects, appart from the door object. First, I have the floor and walls, also other decorative gameobjects, and, finally, the door gameobject.
So, if I count its children, it won’t just count the door, that’s why I was using tags. How could I save the whole room (with all items inside) to make it don’t have any child? So the only child there is the door gameobject.
And one last thing, do I need to have all the rooms placed in order to call them? I don’t know how to don’t use an array list, as it gives me the possibility to make a list with all rooms I want to be in the level. Thanks for your time ^^

I would still grab a door by grabbing a child.

If you don’t then you’re going to end up with load of arrays to manage to hold avaialable doors in all different rooms you have placed.

You could place one child object on each room, a gameobject with name (not tag) “doors”.

Then make all you doors be children of that.

To pick a random door, loop through all available children until gameobjet.name.contains “doors”. Then pick one of its children to make a door.

Yes you can use an array or list to hold your initial selection of room types. Or you could runtime load them from a resources folder, provided that resources folder is subfolder of assets:

        // USE FUNC LIKE:
        GameObject oRoom = CreateObject("Resources/MyRooms/Room1")

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //    LOAD AND RETURN A GAMEOBJECT
        public GameObject CreateObject(string sPrefabPath)
        {
            //  LOAD PREFAB
            UnityEngine.Object oPrefab = Resources.Load(sPrefabPath);
            if (oPrefab == null)
            {
                LOGW("CANT LOAD PREFAB " + sPrefabPath);
                return null;
            }

            //  INSTANTIATE PREFAB
            GameObject oGameObj = (GameObject)Instantiate(oPrefab); ;
            if (oGameObj == null)
            {
                LOGW("CANT INSTANTIATE PREFAB!: " + sPrefabPath);
                return null;
            }
            return oGameObj;
        }

Alright gotcha. I won’t get into resources folders yet, I’ll go step by step. I’ve created another script in order to pick random doors from all avaliable ones in the prefab (I’ve created 2 children, 1 to doors and another one to the structure of the room). What I’m doing is pick the 1st child (which is the door one) and try to pick a random door, but I’m having issues. At the moment, it gives me a null reference exception on the 28th line:

public GameObject[] Rooms;
    public int maxRooms = 2;

    GameObject[] nextDoor;
    GameObject[] generatedRooms;
    GameObject[] doors;
    int countDoors;

    // Use this for initialization
    void Start ()
    {
        StartCoroutine ("generator");
    }

    IEnumerator generator()
    {
        for (int i = 0; i < maxRooms; i++)
        {

            generatedRooms = GameObject.FindGameObjectsWithTag ("Sala");

            countDoors = generatedRooms [i].transform.GetChild (0).childCount;

            int numberofDoors = Random.Range (1,countDoors);
            for (int k = 0; k < numberofDoors; k++)
            {
                int randomDoor = Random.Range (0, countDoors-1);
                nextDoor [k] = generatedRooms [i].transform.GetChild (0).transform.GetChild(randomDoor).gameObject;
            }

            for (int j = 0; j < nextDoor.Length; j++)
            {
                int rng = Random.Range (0, Rooms.Length-1);

                Instantiate (Rooms [rng], nextDoor[j].transform.position, transform.rotation);
            }
             
            //yield return new WaitForSeconds (0.1f);
        }

        yield return new WaitForSeconds (0);
    }

You want your room prefab heirachy to be like this:

ROOM_PREFAB_OBJECT
    |
    +----DOORS
    |      +------DOOR1
    |      +------DOOR2
    +----OTHER_STUFF...
    +----OTHER_STUFF...
    +----OTHER_STUFF...

Grab the DOORS child.
Then pick one of it’s children.

You’re still trying to do everything in the loop, and you’re still looping over maxrooms.

I suggest you use the method I post above, you should put different parts of the problem into functions, break it down.

private bool ConnectRooms(GameObject oRoom1, GameObject oRoom2)

If you’re having a hard time wrapping your head round it then try this: write a function to spawn a load of rooms randomly and then write function to change the name of all your door markers at runtime, naming them like (“room_x_door_y”), then pause your game and manually look into the scene heirachy to see what you’re renaming, if you’re renaming them all, if the names are all correct and in order. I’m talking about names here not tags, that’s the name that appears in unity scene heirachy.

1 Like

I just wrote a procedural indoor map generator:

For anyone interested in the general method:
The highest level procedural function is:

PROCEDURAL FUNC

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //    DO PROCEDURAL STUFF
        private bool DoProcedural(int iRoomsRequired)
        {
            int iRoomsCreated = 0;
            int iMaxIterations = iRoomsRequired * K_I_ITERATIONS_PER_ROOM_MAX;
            int iLoop = 0;
            Transform tNodeOld = null;
            Transform tNodeNew = null;
            Transform tRoomNew = null;
            Vector3 vRotate = new Vector3(0, 0, 0);

            bool bRoomPlacedOK;

            for (; iLoop < iMaxIterations; iLoop++)
            {
                //  GET NEW ROOM
                if ((iRoomsCreated % 5) == 0)
                {
                    tRoomNew = GetRoomWithNodeCount(4);
                }
                else if (iRoomsCreated < iRoomsRequired * 0.75f)
                {
                    tRoomNew = GetRoomWithAtLeastThisManyNodes(2);
                }
                else
                {
                    //GET ANY RANDOM ROOM
                    tRoomNew = main.g.cLibMesh.ChildGetCopy(v.tModels_Rooms);
                }

                if (!tRoomNew)
                {
                    LOGW("CANT MAKE NEW RANDOM ROOM");
                    break;
                }
                bRoomPlacedOK = true;

                //  SEED ROOM
                if (iRoomsCreated == 0)
                {
                    tRoomNew.SetPositionAndRotation(Vector3.zero, Quaternion.Euler(Vector3.zero));
                }
                //  NOT SEED ROOM
                else
                {
                    //  GET EXISTING NODE
                    tNodeOld = GetRandomNode_Old();
                    if (!tNodeOld)
                    {
                        LOGW("CANT GET RANDOM OLD NODE");
                        break;
                    }

                    //  GET NEW NODE
                    tNodeNew = GetRandomNode_New(tRoomNew);
                    if (!tNodeNew)
                    {
                        LOGW("CANT GET RANDOM NEW NODE");
                        break;
                    }

                    //  ALIGN NEW ROOM TO OLD
                    vRotate.y = 180 + tNodeNew.rotation.eulerAngles.y - tNodeOld.rotation.eulerAngles.y;
                    tRoomNew.Rotate(vRotate, Space.World);
                    tRoomNew.Translate(tNodeOld.position - tNodeNew.position, Space.World);

                    //  DOES NEW ROOM COLLIDE WITH ANY EXISTING ROOMS
                    if (RoomCollides(tRoomNew))
                    {
                        bRoomPlacedOK = false;
                    }

                }

                //  ROOM PLACED OK
                if (bRoomPlacedOK)
                {
                    //  REMOVE NODES AND ADD GATE
                    DoneRoomPlacement(tRoomNew, tNodeOld, tNodeNew);

                    //  FINISHED?
                    if (++iRoomsCreated >= iRoomsRequired)
                    {
                        break;
                    }
                }
                //  ROOM NOT PLACED OK
                else
                {
                    DestroyImmediate(tRoomNew.gameObject);
                }
            }

            //  FINISH
            FinishEnds();
            FinishMesh();

            //  LOG
            const string K_S_PAD = "  |  ";
            int iMillis = (int)v.oStopwatch.ElapsedMilliseconds;
            v.oStopwatch.Stop();

            string sLog = "";
            sLog += "SEED: " + v.iRandomSeed + K_S_PAD;
            sLog += "REQUIRED: " + iRoomsRequired + K_S_PAD;
            sLog += "CREATED: " + iRoomsCreated + K_S_PAD;
            sLog += "MILLIS: " + iMillis + K_S_PAD;
            sLog += "ITERATIONS: " + iLoop + K_S_PAD;
            sLog += "ITER/ROOM: " + (float)iLoop / (float)iRoomsCreated;

            if (iLoop < iMaxIterations && iRoomsCreated == iRoomsRequired)
            {
                LOGM(sLog);//MESSAGE
            }
            else
            {
                LOGW(sLog);//WARNING
            }

            //  DONE
            return true;
        }

The grunt work is prepping all your room modules with door markers.
I also prepped with bounds info, from this each room has an array of Bounds[ ] built in script and this can be used for quick room collision detect when placing rooms:


Below you can see the heirachy of one room model.
The bounds meshes “b” (i make by copying selected faces from model mesh).
The door markers “n” (i use little visible cursor models).

Bounds info is also good for deriving minimaps into a Texture2D in script.