游戏关卡意外地相互叠加

Game levels are unexpectedly generating on top of each other

我一直在研究程序生成关卡。 我已经创建了根据打开方式生成的盒子。

如果一个盒子有一个左边的重生点。左边的出生点会知道它需要在右边创建至少一扇门。 这似乎行得通,但出于某种原因,过了一会儿,房间开始堆叠在一起。即使我的代码不允许这样做?

这会不会是因为墙壁没有完全对称?因为我想要更广泛和不同类型的关卡,我认为只有重生点对齐就足够了吗?

这就是关卡启动 4 条不同路径的方式。

仍然很好

还不错

如您所见,起始层的所有入口都已被封锁。 在此之后,他们不断堆叠 op 彼此顶部,从而永无止境地生成关卡。

房间生成器

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

所以我想出了以下解决方案:

(限制 - 如果一个房间已经生成,它仍然有可能被其他生成房间包围,因此它的门被挡住)

  1. 具有适当的枚举标志类型

    #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. 使用标志而不是整数来定义预制件上的隔壁方向。

    然后每次生成一个新房间时,它都会将自己的位置广告到 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;
        }
    }