Уровни игры неожиданно генерируются друг над другом

Я работал над процедурной генерацией уровней. Я создал ящики, спавн которых зависит от их открытия.

Если у ящика есть левый спавн. левый спавн будет знать, что ему нужно создать как минимум 1 дверь справа. Вроде работает, но по какой-то причине через некоторое время комнаты начинают накладываться друг на друга. даже если мой код не позволяет этого?

Может ли это быть из-за того, что стены не находятся в идеальной симметрии друг к другу? Поскольку я хочу иметь более широкие и разнообразные уровни, я подумал, что будет достаточно выравнивания только точек возрождения?

Вот как уровень начинает 4 разных пути. Начальное изображение

Все идет хорошо Starting image

Все еще хорошо Starting image

Как видите, все входы со стартового этажа заблокированы. После этого они продолжают накладываться друг на друга, не прекращая генерацию уровней. Начальное изображение

Создатель комнаты

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;
        }
    }
  }
}

Метка эсминца

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

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

Шаблоны комнат


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;
}


person mafiaf    schedule 30.07.2019    source источник
comment
Ну, вы не отслеживаете, какие области уже были заполнены. Когда ваша комната, которая была на севере, пошла на север, та комната увидела, что в ней две двери: одна южная и одна западная, поэтому она породила еще две комнаты. У вас будет аналогичная проблема, если исходная точка, идущая на север, расширяется на восток, а комната, исходящая из исходной точки, идущей на восток, расширяется на север.   -  person Draco18s no longer trusts SE    schedule 30.07.2019


Ответы (1)


Поэтому я придумал следующее решение:

(Ограничение - если комната уже создана, все еще возможно, что она может быть окружена другими комнатами появления, поэтому ее двери будут заблокированы)

  1. Иметь правильный тип флага enum

    #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
    

    это позволяет вам выбрать одно или несколько значений флага через Инспектор.

    введите здесь описание изображения

  2. Измените свой RoomTemplate скрипт как

    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;
    }
    

    это дает прямой доступ к значениям RoomSpawner в префабах.

    введите здесь описание изображения

  3. Используйте флаг вместо int для определения направления следующей двери на префабах.

    Затем каждый раз, когда создается новая комната, он добавляет свои собственные позиции в occupiedPositions, поэтому здесь больше не может быть создана другая комната.

    Кроме того, проверьте, в каком направлении может двигаться помещение, которое собирается добавить, и выберите только случайное помещение из этого списка, используя Linq Where.

    Если больше некуда идти, используйте вместо этого закрытую Комнату. (Конечно, вы все еще можете добавить его в списки сборных элементов, если вам также нужна возможность случайного выбора закрытой комнаты)

    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;
        }
    }
    

    введите здесь описание изображения

person derHugo    schedule 31.07.2019
comment
Извините за поздний ответ! Это прекрасно! Мне очень нравится то, что ты сделал со сценарием. Хотя ваша версия, кажется, порождает МНОГО комнат, и, учитывая точки возрождения, она может воссоздать только комнаты такой же точной ширины и высоты. Можно ли также установить минимальное и максимальное количество комнат? И иметь возможность совмещать это с комнатами разной ширины и высоты? И еще раз Спасибо за отклик! - person mafiaf; 01.08.2019
comment
ну, на самом деле это не было частью вашего вопроса, и в вашем примере использовались только квадратные комнаты одинакового размера. Конечно, это было бы возможно каким-то образом, но я думаю, это стало бы намного сложнее и шире для этого сообщества. Для максимального количества комнат или шагов вы, конечно, можете просто реализовать статический счетчик. - person derHugo; 01.08.2019