2

I have been working on procedural generating levels. I have created boxes who have a spawn depending on their opening.

If a box has a left spawn. the left spawn will know that it needs to create at least 1 door on the right. This seems to be working, but for some reason, after a while, the rooms start stacking on top of each other. even tho my code does not allow this?

Could this be because the walls are not in the perfect symmetry of eachother? Since I want to have broader and different kind of levels I thought having only the spawn points align would be enough?

This is how the level starts 4 different pathways. Starting image

Still going good Starting image

Still good Starting image

As you can see every entrance from the Starting floor have been blocked. After this they keep stacking op top of eachother giving no end to the generation of levels. Starting image

Room Spawner

public class RoomSpawner : MonoBehaviour
{
    public int openingDirection;
    // 1 --> need bottom door
    // 2 --> need top door
    // 3 --> need left door
    // 4 --> need right door

    private RoomTemplates templates;
    private int rand;
    private bool spawned = false;

    void Start(){
      templates = GameObject.FindGameObjectWithTag("Rooms").GetComponent<RoomTemplates>();
      Invoke("Spawn", 0.5f);
    }

    void Spawn(){
      if(spawned == false){
        if(openingDirection == 1){
            // Need to spawn a room with a BOTTOM door.
            rand = Random.Range(0, templates.bottomRooms.Length);
            Instantiate(templates.bottomRooms[rand], transform.position, templates.bottomRooms[rand].transform.rotation);
          } else if(openingDirection == 2){
            // Need to spawn a room with a TOP door.
            rand = Random.Range(0, templates.topRooms.Length);
            Instantiate(templates.topRooms[rand], transform.position, templates.topRooms[rand].transform.rotation);
          } else if(openingDirection == 3){
            // Need to spawn a room with a LEFT door.
            rand = Random.Range(0, templates.leftRooms.Length);
            Instantiate(templates.leftRooms[rand], transform.position, templates.leftRooms[rand].transform.rotation);
          } else if(openingDirection == 4){
            // Need to spawn a room with a RIGHT door.
            rand = Random.Range(0, templates.rightRooms.Length);
            Instantiate(templates.rightRooms[rand], transform.position, templates.rightRooms[rand].transform.rotation);
          }
          spawned = true;
      }

      void OnTriggerEnter2D(Collider2D other){
        if(other.CompareTag("SpawnPoint")){
          if(other.GetComponent<RoomSpawner>().spawned == false && spawned == false){
            // spawns walls blocking off any opening !
            Instantiate(templates.closedRoom, transform.position, Quaternion.identity);
            Destroy(gameObject);
          }
          spawned = true;
        }
    }
  }
}

Destroyer tag

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Destroyer : MonoBehaviour
{
    void OnTriggerEnter2D(Collider2D other  ){
      Destroy(other.gameObject);
    }
}

Roomtemplates


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RoomTemplates : MonoBehaviour
{
    public GameObject[] bottomRooms;
    public GameObject[] topRooms;
    public GameObject[] leftRooms;
    public GameObject[] rightRooms;

    public GameObject closedRoom;

    public List<GameObject> rooms;
}

mafiaf
  • 99
  • 1
  • 6
  • 3
    Well, you aren't keeping track of what areas have already been filled. When your room that was north went north, *that* room saw that it had two doors: one south and one west, so it spawned two more rooms. You'll have a similar problem if the from from origin going North expands East and the room from origin going East expands North. – Draco18s no longer trusts SE Jul 30 '19 at 18:23

1 Answers1

3

So I came up with the following solution:

(Limit - if a room is already spawned it is still possible that it might get surrounded by other spawning rooms so its doors get blocked)

  1. Have a proper enum flag type

    #if UNITY_EDITOR // exclude this from a build
    using Unity.Editor;
    #endif
    
    [Flags]
    public enum DoorType
    {
        Top = 0x01,
        Right = 0x02,
        Bottom = 0x04,
        Left = 0x08
    }
    
    public class EnumFlagsAttribute : PropertyAttribute
    {
        public EnumFlagsAttribute() { }
    }
    
    #if UNITY_EDITOR // exclude this from a build
    [CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
    public class EnumFlagsAttributeDrawer : PropertyDrawer
    {
        public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
        {
            _property.intValue = EditorGUI.MaskField(_position, _label, _property.intValue, _property.enumNames);
        }
    }
    #endif
    

    this allows you to choose one or multiple values from the flag via the Inspector.

    enter image description here

  2. Change your RoomTemplate script like

    public class RoomTemplates : MonoBehaviour
    {
        public RoomSpawner[] bottomRooms;
        public RoomSpawner[] topRooms;
        public RoomSpawner[] leftRooms;
        public RoomSpawner[] rightRooms;
    
        [Space]
    
        public RoomSpawner closedRoomTop;
        public RoomSpawner closedRoomRight;
        public RoomSpawner closedRoomBottom;
        public RoomSpawner closedRoomLeft;
    
        [Space]
    
        public List<GameObject> rooms;
    }
    

    this gives direct access to the values of RoomSpawner on the prefabs.

    enter image description here

  3. Use the flag instead of the int for defining the next door direction on the prefabs.

    Then everytime spawing a new room it ads its own positions to the occupiedPositions so no other room can be spawned here anymore.

    Additionally check in which directions the room that is about to be added can even go and only pick a random room from that list using Linq Where.

    If there is no way left to go use the closed Room instead. (You could ofcourse still add it to the prefab lists if you also want the possibility of a randomly picked closed room)

    public class RoomSpawner : MonoBehaviour
    {
        [EnumFlags] public DoorType openingDirections;
    
        // Keep track of already used positions
        private static List<Vector2Int> occupiedPositions = new List<Vector2Int>();
    
        // store own room position
        private Vector2Int roomFieldPosition;
    
        private RoomTemplates templates;
        private bool spawned = false;
    
        private void Start()
        {
            templates = FindObjectOfType<RoomTemplates>();
    
            roomFieldPosition = new Vector2Int(Mathf.RoundToInt(transform.localPosition.x), Mathf.RoundToInt(transform.localPosition.z));
    
            occupiedPositions.Add(roomFieldPosition);
    
            Invoke("Spawn", 0.5f);
        }
    
        private static DoorType GetPossibleDirections(Vector2Int position)
        {
            DoorType output = 0;
    
            if (!occupiedPositions.Contains(new Vector2Int(position.x, position.y + 1))) output |= DoorType.Top;
            if (!occupiedPositions.Contains(new Vector2Int(position.x, position.y - 1))) output |= DoorType.Bottom;
    
            if (!occupiedPositions.Contains(new Vector2Int(position.x + 1, position.y))) output |= DoorType.Right;
            if (!occupiedPositions.Contains(new Vector2Int(position.x - 1, position.y))) output |= DoorType.Left;
    
            return output;
        }
    
        private void SpawnRoom(DoorType type)
        {
            Vector2Int nextPosition;
            RoomSpawner[] templateArray;
            RoomSpawner closedRoom;
    
            switch (type)
            {
                case DoorType.Top:
                    nextPosition = new Vector2Int(roomFieldPosition.x, roomFieldPosition.y + 1);
                    templateArray = templates.topRooms;
                    closedRoom = templates.closedRoomTop;
                    break;
    
                case DoorType.Right:
                    nextPosition = new Vector2Int(roomFieldPosition.x + 1, roomFieldPosition.y);
                    templateArray = templates.rightRooms;
                    closedRoom = templates.closedRoomRight;
                    break;
    
                case DoorType.Bottom:
                    nextPosition = new Vector2Int(roomFieldPosition.x, roomFieldPosition.y - 1);
                    templateArray = templates.bottomRooms;
                    closedRoom = templates.closedRoomBottom;
                    break;
    
                case DoorType.Left:
                    nextPosition = new Vector2Int(roomFieldPosition.x - 1, roomFieldPosition.y);
                    templateArray = templates.leftRooms;
                    closedRoom = templates.closedRoomLeft;
                    break;
    
                default:
                    return;
            }
    
            if (occupiedPositions.Contains(nextPosition)) return;
    
            var directions = GetPossibleDirections(nextPosition);
    
            var prefabs = new List<RoomSpawner>();
            foreach (var doorType in (DoorType[])Enum.GetValues(typeof(DoorType)))
            {
                if (!directions.HasFlag(doorType)) continue;
    
                prefabs.AddRange(templateArray.Where(r => r.openingDirections.HasFlag(doorType)));
            }
    
            if (prefabs.Count == 0)
            {
                prefabs.Add(closedRoom);
            }
    
            // Need to spawn a room with a BOTTOM door.
            var rand = Random.Range(0, prefabs.Count);
            Instantiate(prefabs[rand], new Vector3(nextPosition.x, 0, nextPosition.y), Quaternion.identity);
        }
    
        private void Spawn()
        {
            if (spawned) return;
    
            if (openingDirections.HasFlag(DoorType.Top)) SpawnRoom(DoorType.Top);
            if (openingDirections.HasFlag(DoorType.Bottom)) SpawnRoom(DoorType.Bottom);
    
            if (openingDirections.HasFlag(DoorType.Right)) SpawnRoom(DoorType.Right);
            if (openingDirections.HasFlag(DoorType.Left)) SpawnRoom(DoorType.Left);
    
            spawned = true;
        }
    }
    

    enter image description here

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Sorry for the late response! This is beautiful! I really love what you did with the script. Although your version seems to spawn A LOT of rooms and considering the spawn points it can only recreate rooms of that precise width and height. Would it be possible to also set a minimum and maximum amount of rooms? And to be able to combine this with rooms that are of different width and height? And again Thank you for your response! – mafiaf Jul 31 '19 at 23:04
  • 1
    well this wasn't really part of your question and your example used only square equally sized rooms. Of course it would be possible somehow but would get way more complex and to broad for this community I guess. For the maximum amount of rooms or steps you can ofcourse simply implement a static counter – derHugo Aug 01 '19 at 09:19