Souljack/Assets/_Project/Scripts/LevelGenerating/ProceduralLevelGenerator.cs

272 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Vector2Int, Cell> levelMap = new Dictionary<Vector2Int, Cell>();
private List<RectInt> rooms = new List<RectInt>();
// Список всех прорубленных проходов
private List<Passage> passages = new List<Passage>();
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<Vector2Int[]> potentialPassages = new List<Vector2Int[]>();
// Проходим по всем клеткам первой комнаты
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;
}
}