Torchlife

Software Used: Unity C#, Blender 2.8
Solo Project

Torchlife is a dark and scary first person dungeon exploration game. The player’s objective is to keep their torch lit at all times to avoid being trapped by lurking creatures. I created the dungeon system along with traps, and a boss called “Spikeman."

Procedural Dungeon Room Generation System

In Torchlife, the rooms and hallways are procedurally generated using an additive procedural generation system. A collision detection system was developed so the rooms do not intersect each other. Each room exit has a spawner to determine which room spawns next or, to seal off a dungeon wall when there no more rooms on the level.

A more detailed look on how rooms are generated.

This is a spawner object. It is responsible for spawning a room or a wall off of the exit of another room. It was created as a child object of the room to the right of it. If the spawner is inside of another room, then the spawner is destroyed because there is already a room in that location. If the spawner is inside of another spawner, then every spawner, except for one, is destroyed.

This is the sensor that is attached to the spawner in the first note. It is responsible for scanning in front of the spawner to determine what the maximum size is for spawning a room. If the sensor collides with another room, then only a single size room can be spawned. Else, a double sized room can be spawned. As larger room sizes are created in the future, more sensors will be added.

This is a spawner who’s sensor (see 4.) is inside of another room. The sensor sends back information to the spawner telling the spawner to limit generation options to only single size rooms.

This is a sensor which is inside of another room. Because of this, it will send information back to it’s spawner (see 3.) about the maximum size that a spawner can generate.

See sample code of room generated spawned script.

 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
    /* This method determines what room to spawn. 
     * Details: When a spawner determines what room to spawn, it first references
     * it's sensors to determine what the maximum size that it can spawn is. 
     * If the sensor has detected a collision, then the spawner can only
     * spawn a single size room. Then, it determines a room based on weighted
     * chance. 
     * This happens unless force generation is detected at this 
     * particular sequence. (more details on that in the method)
    */
    public GameObject CalcRoom(GenData genData)
    {
        /*
         * This handles if any rooms need to be force generated.
         * forceGenData takes data from genData about force generation.
         * Force generation generates rooms that are specified to be generated in a specific sequence.
         */
        List<Vector2> forceGenData = genData.forceGen.forceGenRooms;

        if (forceGenData.Count != 0)
        {
            foreach (Vector2 data in forceGenData)
            {
                //If the sequence count matches the force generation sequence.
                if (genData.currentRooms == data.y)
                {   
                    /*
                     * Determines the room ID to be force generated.
                     * 0-99     : Single room
                     * 100-199  : Double Room
                     * 200-299  : Trap Room (single size)
                     * 1000-inf : Special Room
                     * */
                    if (data.x < 100 && data.x >= 0)
                    {
                        return genData.roomGeneration.singleRooms[(int)data.x];
                    }
                    else if (data.x >= 100 && data.x < 200)
                    {
                        return genData.roomGeneration.doubleRooms[(int)(data.x - 100)];
                    }
                    else if (data.x >= 200 && data.x < 300)
                    {
                        return genData.roomGeneration.trapRooms[(int)(data.x - 200)];

                    } else if(data.x >= 1000) {

                        return genData.roomGeneration.specialRooms[(int)(data.x - 1000)];
                    }                    
                }
            }
        }

        //If the sequence number is the final sequence number, return the final room.
        if (genData.currentRooms == genData.maxRooms)
        {            
            return genData.roomGeneration.specialRooms[1];
        }

        //Weighted chances based on GenData chances which were specified in the inspector. 
        int dat0 = genData.roomGeneration.chances[0];
        int dat1 = genData.roomGeneration.chances[1];

        //This is the value which will be compared against the weighted chances.
        int randomChance = Random.Range(1, 101);

        //Single Room
        if (randomChance <= dat0)
        {
            int size = genData.roomGeneration.singleRooms.Length;

            //There are different types of single rooms, so pick a random one. 
            int retInt = Random.Range(0, size);

            return genData.roomGeneration.singleRooms[retInt];
        }
        else if (randomChance <= dat1) //Double Room
        {
            if (maxSize == 1)
            {
                int size = genData.roomGeneration.doubleRooms.Length;

                //There are different types of double rooms, so pick a random one. 
                int retInt = Random.Range(0, size);

                return genData.roomGeneration.doubleRooms[retInt];
            }
            int size2 = genData.roomGeneration.singleRooms.Length;

            //There are different types of single rooms, so pick a random one. 
            int retInt2 = Random.Range(0, size2);

            return genData.roomGeneration.singleRooms[retInt2];
        } else //Trap room
        {
            int size = genData.roomGeneration.trapRooms.Length;

            //There are different types of trap rooms, so pick a random one. 
            int retInt = Random.Range(0, size);

            return genData.roomGeneration.trapRooms[retInt];
        }
    }