using System.Collections.Generic; using UnityEngine; public class ProceduralLevelGenerator : MonoBehaviour { [Header("Настройки генерации")] public int numberOfRooms = 8; public Vector2Int minRoomSize = new Vector2Int(5, 5); public Vector2Int maxRoomSize = new Vector2Int(12, 12); public float tileSize = 2f; public float wallHeight = 8f; // Ты указал высоту 8 [Header("Префабы строительных блоков")] public GameObject floorPrefab; public GameObject wallPrefab; // Структура клетки private class Cell { public int roomID; public int floorHeight; } // Структура для прохода (двери) private class Passage { public Vector2Int cellA; // Клетка из одной комнаты public Vector2Int cellB; // Клетка из другой комнаты } private Dictionary levelMap = new Dictionary(); private List rooms = new List(); // Список всех прорубленных проходов private List passages = new List(); void Start() { GenerateLayout(); ApplyVerticality(); BuildMesh(); } void GenerateLayout() { // --- ШАГ 1: Стартовая комната (10x10) --- int startWidth = 10; int startHeight = 10; int startX = -startWidth / 2; int startY = -startHeight / 2; RectInt startRoom = new RectInt(startX, startY, startWidth, startHeight); rooms.Add(startRoom); SaveRoomToMap(startRoom, 0); // --- ШАГ 2: Первая комната с врагами --- int firstEnemyWidth = Random.Range(minRoomSize.x, maxRoomSize.x); int firstEnemyHeight = Random.Range(minRoomSize.y, maxRoomSize.y); int firstEnemyX = startRoom.x + Random.Range(-firstEnemyWidth / 2, startRoom.width - firstEnemyWidth / 2); int firstEnemyY = startRoom.yMax; RectInt firstEnemyRoom = new RectInt(firstEnemyX, firstEnemyY, firstEnemyWidth, firstEnemyHeight); rooms.Add(firstEnemyRoom); SaveRoomToMap(firstEnemyRoom, 1); // ПРОРУБАЕМ ПРОХОД между стартовой и первой вражеской комнатой CreatePassage(startRoom, firstEnemyRoom); // --- ШАГ 3: Остальные комнаты --- for (int i = 2; i < numberOfRooms; i++) { bool roomPlaced = false; int attempts = 0; while (!roomPlaced && attempts < 50) { attempts++; // Выбираем случайную комнату (кроме стартовой) int baseRoomIndex = Random.Range(1, rooms.Count); RectInt baseRoom = rooms[baseRoomIndex]; int newWidth = Random.Range(minRoomSize.x, maxRoomSize.x); int newHeight = Random.Range(minRoomSize.y, maxRoomSize.y); int side = Random.Range(0, 4); RectInt newRoom = new RectInt(); if (side == 0) // Север newRoom = new RectInt(baseRoom.x + Random.Range(-newWidth/2, baseRoom.width - newWidth/2), baseRoom.yMax, newWidth, newHeight); else if (side == 1) // Юг newRoom = new RectInt(baseRoom.x + Random.Range(-newWidth/2, baseRoom.width - newWidth/2), baseRoom.yMin - newHeight, newWidth, newHeight); else if (side == 2) // Восток newRoom = new RectInt(baseRoom.xMax, baseRoom.y + Random.Range(-newHeight/2, baseRoom.height - newHeight/2), newWidth, newHeight); else if (side == 3) // Запад newRoom = new RectInt(baseRoom.xMin - newWidth, baseRoom.y + Random.Range(-newHeight/2, baseRoom.height - newHeight/2), newWidth, newHeight); if (!CheckOverlap(newRoom)) { rooms.Add(newRoom); SaveRoomToMap(newRoom, i); // ПРОРУБАЕМ ПРОХОД между базовой комнатой и новой CreatePassage(baseRoom, newRoom); roomPlaced = true; } } } } // Выбирает одну случайную общую стену между двумя комнатами и делает там проход void CreatePassage(RectInt room1, RectInt room2) { List potentialPassages = new List(); // Проходим по всем клеткам первой комнаты for (int x = room1.xMin; x < room1.xMax; x++) { for (int y = room1.yMin; y < room1.yMax; y++) { Vector2Int cellA = new Vector2Int(x, y); Vector2Int[] dirs = { Vector2Int.up, Vector2Int.down, Vector2Int.left, Vector2Int.right }; // Проверяем соседей foreach (var dir in dirs) { Vector2Int cellB = cellA + dir; // Если соседняя клетка находится во второй комнате — это потенциальная дверь! if (cellB.x >= room2.xMin && cellB.x < room2.xMax && cellB.y >= room2.yMin && cellB.y < room2.yMax) { potentialPassages.Add(new Vector2Int[] { cellA, cellB }); } } } } // Если нашли точки соприкосновения, берем ОДНУ случайную и делаем там проход if (potentialPassages.Count > 0) { Vector2Int[] chosen = potentialPassages[Random.Range(0, potentialPassages.Count)]; passages.Add(new Passage { cellA = chosen[0], cellB = chosen[1] }); } } void ApplyVerticality() { for (int i = 1; i < rooms.Count; i++) { RectInt room = rooms[i]; if (Random.value > 0.5f) { int platformWidth = room.width / 2; int platformHeight = room.height / 2; int offsetX = Random.Range(1, room.width - platformWidth - 1); int offsetY = Random.Range(1, room.height - platformHeight - 1); for (int x = 0; x < platformWidth; x++) { for (int y = 0; y < platformHeight; y++) { Vector2Int pos = new Vector2Int(room.x + offsetX + x, room.y + offsetY + y); if (levelMap.ContainsKey(pos)) { levelMap[pos].floorHeight = 1; } } } } } } void BuildMesh() { Vector2Int[] directions = { Vector2Int.up, Vector2Int.down, Vector2Int.right, Vector2Int.left }; float[] wallRotations = { 0f, 180f, 90f, -90f }; foreach (var kvp in levelMap) { Vector2Int pos = kvp.Key; Cell cell = kvp.Value; // Вычисляем высоту (чтобы балконы были логичной высоты, умножаем на половину высоты стены) float worldHeight = cell.floorHeight * (wallHeight * 0.5f); Vector3 floorPos = new Vector3(pos.x * tileSize, worldHeight, pos.y * tileSize); GameObject floor = Instantiate(floorPrefab, floorPos, Quaternion.identity, this.transform); floor.name = $"Floor_{pos.x}_{pos.y}"; for (int i = 0; i < 4; i++) { Vector2Int neighborPos = pos + directions[i]; bool needsWall = false; if (!levelMap.ContainsKey(neighborPos)) { // Соседа нет -> глухая внешняя стена needsWall = true; } else { Cell neighborCell = levelMap[neighborPos]; if (neighborCell.roomID != cell.roomID) { // Это граница двух комнат. Проверяем, есть ли здесь проход. if (IsPassage(pos, neighborPos)) { needsWall = false; // ПРОПУСКАЕМ СТЕНУ (оставляем дыру для двери) } else { needsWall = true; // Ставим стену } } else if (neighborCell.floorHeight > cell.floorHeight) { // Сосед выше, нужна стена-ступенька внутри комнаты needsWall = true; } } if (needsWall) { Vector3 wallPos = floorPos + new Vector3(directions[i].x * (tileSize / 2), wallHeight / 2, directions[i].y * (tileSize / 2)); GameObject wall = Instantiate(wallPrefab, wallPos, Quaternion.Euler(0, wallRotations[i], 0), this.transform); wall.name = $"Wall_{pos.x}_{pos.y}_{i}"; } } } } // Проверяет, является ли граница между двумя клетками проходом bool IsPassage(Vector2Int a, Vector2Int b) { foreach (var p in passages) { if ((p.cellA == a && p.cellB == b) || (p.cellA == b && p.cellB == a)) { return true; } } return false; } void SaveRoomToMap(RectInt room, int id) { for (int x = room.xMin; x < room.xMax; x++) { for (int y = room.yMin; y < room.yMax; y++) { levelMap[new Vector2Int(x, y)] = new Cell { roomID = id, floorHeight = 0 }; } } } bool CheckOverlap(RectInt newRoom) { foreach (RectInt room in rooms) { if (newRoom.xMin < room.xMax && newRoom.xMax > room.xMin && newRoom.yMin < room.yMax && newRoom.yMax > room.yMin) { return true; } } return false; } }