using System.Collections.Generic; using System.Linq; using UnityEngine; using Random = UnityEngine.Random; namespace _Project.Scripts.LevelGenerating { public class WallsGenerator : MonoBehaviour { [SerializeField] private int floorsCount; [SerializeField] private int lengthWallMin; [SerializeField] private int lengthWallMax; [SerializeField] private int lengthStartRoom; [SerializeField] private int lengthLiftRoom; [SerializeField] private int widthLiftRoom; [SerializeField] private int lengthFloor; [SerializeField] private int corridorLength; [SerializeField] private int corridorWigth; [SerializeField] private int _minDepthLiftRoom; [SerializeField] private GameObject GameObjectA; [SerializeField] private GameObject GameObjectB; [Header("Генерация")] [SerializeField] private int maxRooms = 15; [SerializeField, Range(0f, 1f)] private float branchChance = 0.7f; [SerializeField] private float roomMargin = 2f; // зазор между комнатами private int _curRooms; private readonly Dictionary _depth = new Dictionary(); private LevelManager levelManager; private static readonly ExitsInRoomType[] AllDirs = { ExitsInRoomType.XAxisP, ExitsInRoomType.XAxisN, ExitsInRoomType.ZAxisP, ExitsInRoomType.ZAxisN }; void Start() { levelManager = LevelManager.Instance; GenerateFloor(); } bool PlaceElevator() { var start = levelManager.GetRooms().First(r => r.rt == RoomType.Start); // максимум шагов, при равенстве выбираем самую дальнюю от старта по прямой var best = _depth .OrderByDescending(p => p.Value) .ThenByDescending(p => (p.Key.Position - start.Position).sqrMagnitude) .First(); if (best.Key == start || best.Value <= _minDepthLiftRoom) { return false; } best.Key.rt = RoomType.Lift; // добавь значение в enum RoomType best.Key.Length = lengthLiftRoom; best.Key.Width = widthLiftRoom; best.Key.exits.Clear(); return true; } void GenerateFloor() { LevelManager.Rooms.Clear(); _depth.Clear();// статический список мог остаться с прошлого запуска _curRooms = 0; CreateStartRoom(); if (!PlaceElevator()) { GenerateFloor(); } Instantiate(GameObjectA, new Vector3(0, -1, 0), Quaternion.identity); foreach (var room in levelManager.GetRooms()) { float x = room.Position.x; float z = room.Position.y; // Vector2.y -> Z в 3D float length = room.Length; float width = room.Width; Instantiate(GameObjectB, new Vector3(x, 0, z), Quaternion.identity); Instantiate(GameObjectB, new Vector3(x + length, 0, z), Quaternion.identity); Instantiate(GameObjectB, new Vector3(x + length, 0, z + width), Quaternion.identity); Instantiate(GameObjectB, new Vector3(x, 0, z + width), Quaternion.identity); } } // ---------- Вспомогательные ---------- private static ExitsInRoomType Opposite(ExitsInRoomType d) { switch (d) { case ExitsInRoomType.XAxisP: return ExitsInRoomType.XAxisN; case ExitsInRoomType.XAxisN: return ExitsInRoomType.XAxisP; case ExitsInRoomType.ZAxisP: return ExitsInRoomType.ZAxisN; default: return ExitsInRoomType.ZAxisP; } } private static EntranceToRoomType ToEntrance(ExitsInRoomType d) { switch (d) { case ExitsInRoomType.XAxisP: return EntranceToRoomType.XAxisN; case ExitsInRoomType.XAxisN: return EntranceToRoomType.XAxisP; case ExitsInRoomType.ZAxisP: return EntranceToRoomType.ZAxisN; default: return EntranceToRoomType.ZAxisP; } } private static Rect RoomRect(Room r) => new Rect(r.Position.x, r.Position.y, r.Length, r.Width); // Прямоугольник коридора, ведущего из prev в направлении dir private Rect CorridorRect(Room prev, ExitsInRoomType dir) { switch (dir) { case ExitsInRoomType.XAxisP: return new Rect(prev.Position.x + prev.Length, prev.Position.y + (prev.Width - corridorWigth) / 2f, corridorLength, corridorWigth); case ExitsInRoomType.XAxisN: return new Rect(prev.Position.x - corridorLength, prev.Position.y + (prev.Width - corridorWigth) / 2f, corridorLength, corridorWigth); case ExitsInRoomType.ZAxisP: return new Rect(prev.Position.x + (prev.Length - corridorWigth) / 2f, prev.Position.y + prev.Width, corridorWigth, corridorLength); default: // ZAxisN return new Rect(prev.Position.x + (prev.Length - corridorWigth) / 2f, prev.Position.y - corridorLength, corridorWigth, corridorLength); } } // Центр стены новой комнаты, куда упирается коридор private Vector2 EntryPoint(Room prev, ExitsInRoomType dir) { switch (dir) { case ExitsInRoomType.XAxisP: return new Vector2(prev.Position.x + prev.Length + corridorLength, prev.Position.y + prev.Width / 2f); case ExitsInRoomType.XAxisN: return new Vector2(prev.Position.x - corridorLength, prev.Position.y + prev.Width / 2f); case ExitsInRoomType.ZAxisP: return new Vector2(prev.Position.x + prev.Length / 2f, prev.Position.y + prev.Width + corridorLength); default: return new Vector2(prev.Position.x + prev.Length / 2f, prev.Position.y - corridorLength); } } private bool IsAreaFree(Rect rect, Room ignore = null) { foreach (var other in levelManager.GetRooms()) { if (other == ignore) continue; if (rect.Overlaps(RoomRect(other))) return false; } return true; } // ---------- Коридор ---------- Room CreateCorridor(Room prev, ExitsInRoomType dir) { Rect r = CorridorRect(prev, dir); Room corridor = new Room(r.position, Mathf.RoundToInt(r.width), Mathf.RoundToInt(r.height), RoomType.Corridor, false, ToEntrance(dir), new List { dir }); LevelManager.Rooms.Add(corridor); return corridor; } private bool FitRoom(Room prev, Room n, ExitsInRoomType dir, out bool shrunk) { Vector2 e = EntryPoint(prev, dir); int origLen = n.Length, origWid = n.Width; switch (dir) { case ExitsInRoomType.XAxisP: case ExitsInRoomType.XAxisN: { float maxAlong = dir == ExitsInRoomType.XAxisP ? lengthFloor - e.x : e.x; float maxCross = 2f * Mathf.Min(e.y, lengthFloor - e.y); n.Length = Mathf.Min(n.Length, Mathf.FloorToInt(maxAlong)); n.Width = Mathf.Min(n.Width, Mathf.FloorToInt(maxCross)); float x = dir == ExitsInRoomType.XAxisP ? e.x : e.x - n.Length; n.Position = new Vector2(x, e.y - n.Width / 2f); break; } default: // Z { float maxAlong = dir == ExitsInRoomType.ZAxisP ? lengthFloor - e.y : e.y; float maxCross = 2f * Mathf.Min(e.x, lengthFloor - e.x); n.Width = Mathf.Min(n.Width, Mathf.FloorToInt(maxAlong)); n.Length = Mathf.Min(n.Length, Mathf.FloorToInt(maxCross)); float y = dir == ExitsInRoomType.ZAxisP ? e.y : e.y - n.Width; n.Position = new Vector2(e.x - n.Length / 2f, y); break; } } shrunk = n.Length != origLen || n.Width != origWid; return n.Length >= lengthWallMin && n.Width >= lengthWallMin; } bool TryPlaceRoom(Room prevRoom, ExitsInRoomType dir) { if (prevRoom == null || _curRooms >= maxRooms) return false; Room newRoom = GenerateRandomRoom(); newRoom.rt = RoomType.Undefined; newRoom.exits = new List(); // заполнится по факту построенных выходов newRoom.entrance = ToEntrance(dir); if (!FitRoom(prevRoom, newRoom, dir, out bool shrunk)) return false; newRoom.isWallable = shrunk; // Пересечения: комната (с зазором, без prevRoom) и коридор к ней Rect roomArea = RoomRect(newRoom); float m = Mathf.Min(roomMargin, corridorLength); roomArea.xMin -= m; roomArea.yMin -= m; roomArea.xMax += m; roomArea.yMax += m; if (!IsAreaFree(roomArea, prevRoom)) return false; if (!IsAreaFree(CorridorRect(prevRoom, dir))) return false; // Всё хорошо - фиксируем levelManager.AddRoom(newRoom); _depth[newRoom] = _depth[prevRoom] + 1; CreateCorridor(prevRoom, dir); _curRooms++; if (!prevRoom.exits.Contains(dir)) prevRoom.exits.Add(dir); // Ветвление: случайный порядок, назад не идём foreach (var d in AllDirs.OrderBy(_ => Random.value)) { if (d == Opposite(dir)) continue; if (Random.value > branchChance) continue; TryPlaceRoom(newRoom, d); } return true; } Room GenerateRandomRoom() { // Range(int,int) не включает верхнюю границу, поэтому +1 int a = Random.Range(lengthWallMin, lengthWallMax + 1); int b = Random.Range(lengthWallMin, lengthWallMax + 1); Room room = new Room(); room.Length = a; room.Width = b; return room; } // ---------- Стартовая комната ---------- void CreateStartRoom() { float randValue = Random.value; int xR, zR; ExitsInRoomType firstDir; Room room = new Room(lengthStartRoom, lengthStartRoom, RoomType.Start); room.exits = new List(); room.entrance = EntranceToRoomType.Null; if (randValue < 0.25f) // 1 квадрант { xR = Random.Range(0, lengthFloor / 2 - lengthStartRoom); if (xR + lengthStartRoom <= lengthFloor / 4) { firstDir = ExitsInRoomType.XAxisP; zR = Random.Range(0, lengthFloor / 2 - lengthStartRoom); } else { firstDir = ExitsInRoomType.ZAxisP; zR = Random.Range(0, lengthFloor / 4 - lengthStartRoom); } } else if (randValue < 0.5f) // 2 квадрант { xR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom); if (xR + lengthStartRoom <= lengthFloor * 3 / 4) { firstDir = ExitsInRoomType.ZAxisP; zR = Random.Range(0, lengthFloor / 4 - lengthStartRoom); } else { firstDir = ExitsInRoomType.XAxisN; zR = Random.Range(0, lengthFloor / 2 - lengthStartRoom); } } else if (randValue < 0.75f) // 3 квадрант { xR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom); if (xR + lengthStartRoom <= lengthFloor * 3 / 4) { firstDir = ExitsInRoomType.ZAxisN; zR = Random.Range(lengthFloor * 3 / 4, lengthFloor - lengthStartRoom); } else { firstDir = ExitsInRoomType.XAxisN; zR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom); } } else // 4 квадрант { xR = Random.Range(0, lengthFloor / 2 - lengthStartRoom); if (xR + lengthStartRoom <= lengthFloor / 4) { firstDir = ExitsInRoomType.XAxisP; zR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom); } else { firstDir = ExitsInRoomType.ZAxisN; zR = Random.Range(lengthFloor * 3 / 4, lengthFloor - lengthStartRoom); } } room.Position = new Vector2(xR, zR); LevelManager.Rooms.Add(room); _depth[room] = 0; _curRooms = 1; // стартовая комната считается // Предпочтительное направление, а при неудаче - остальные if (TryPlaceRoom(room, firstDir)) return; foreach (var d in AllDirs.Where(x => x != firstDir).OrderBy(_ => Random.value)) if (TryPlaceRoom(room, d)) return; } // ---------- Отладка ---------- void OnDrawGizmos() { var rooms = LevelManager.Rooms; if (rooms == null) return; foreach (var r in rooms) { switch (r.rt) { case RoomType.Start: Gizmos.color = Color.green; break; case RoomType.Corridor: Gizmos.color = Color.yellow; break; case RoomType.Lift: Gizmos.color = Color.rebeccaPurple; break; default: Gizmos.color = Color.cyan; break; } // Gizmos.color = r.rt == RoomType.Start ? Color.green // : r.rt == RoomType.Corridor ? Color.yellow : Color.cyan; Vector3 c = new Vector3(r.Position.x + r.Length / 2f, 0, r.Position.y + r.Width / 2f); Gizmos.DrawWireCube(c, new Vector3(r.Length, 0.1f, r.Width)); } } } }