345 lines
16 KiB
C#
345 lines
16 KiB
C#
using System.Collections.Generic;
|
||
using System.ComponentModel.Design.Serialization;
|
||
using System.Linq;
|
||
using UnityEngine;
|
||
namespace _Project.Scripts.LevelGenerating
|
||
{
|
||
public class WallsGenerator : MonoBehaviour
|
||
{
|
||
[SerializeField] private int roomCountMin;
|
||
[SerializeField] private int roomCountMax;
|
||
[SerializeField] private int floorsCount;
|
||
[SerializeField] private int lengthWallMin;
|
||
[SerializeField] private int lengthWallMax;
|
||
[SerializeField] private int lengthStartRoom;
|
||
[SerializeField] private int lengthFloor;
|
||
[SerializeField] private int corridorLength;
|
||
[SerializeField] private int corridorWigth;
|
||
[SerializeField] private GameObject GameObjectA;
|
||
[SerializeField] private GameObject GameObjectB;
|
||
|
||
|
||
|
||
|
||
private int RoomCount;
|
||
private LevelManager levelManager;
|
||
|
||
void Start()
|
||
{
|
||
levelManager = LevelManager.Instance;
|
||
CreateStartRoom();
|
||
}
|
||
|
||
void GenerateRooms()
|
||
{
|
||
RoomCount = Random.Range(roomCountMin, roomCountMax);
|
||
LevelManager.Rooms.Add(new Room(Vector2.zero,lengthStartRoom, lengthStartRoom,RoomType.Start));
|
||
|
||
}
|
||
Room CreateCorridor(Vector2 posRoom, int Length, int Wigth, ExitsInRoomType exitType)
|
||
{
|
||
Vector2 corridorPos = Vector2.zero;
|
||
int cLength = 0;
|
||
int cWidth = 0;
|
||
EntranceToRoomType entranceToRoomType = EntranceToRoomType.Null;
|
||
List<ExitsInRoomType> exitsInRoomType = new List<ExitsInRoomType>();
|
||
switch (exitType)
|
||
{
|
||
// Выход направо (+X)
|
||
case ExitsInRoomType.XAxisP:
|
||
// Начинается у правой стены комнаты (posRoom.x + Length)
|
||
// Центрируем коридор по высоте/оси Z: смещаем на половину разницы ширин
|
||
exitsInRoomType.Add (ExitsInRoomType.XAxisP);
|
||
entranceToRoomType = EntranceToRoomType.XAxisN;
|
||
corridorPos = new Vector2(
|
||
posRoom.x + Length,
|
||
posRoom.y + (Wigth - corridorWigth) / 2f
|
||
);
|
||
cLength = corridorLength;
|
||
cWidth = corridorWigth;
|
||
|
||
break;
|
||
|
||
// Выход налево (-X)
|
||
case ExitsInRoomType.XAxisN:
|
||
// Начинается слева от комнаты, отступая на длину коридора
|
||
exitsInRoomType .Add (ExitsInRoomType.XAxisN);
|
||
entranceToRoomType = EntranceToRoomType.XAxisP;
|
||
corridorPos = new Vector2(
|
||
posRoom.x - corridorLength,
|
||
posRoom.y + (Wigth - corridorWigth) / 2f
|
||
);
|
||
cLength = corridorLength;
|
||
cWidth = corridorWigth;
|
||
break;
|
||
|
||
// Выход вперед/вверх (+Z / в Vector2 это Y)
|
||
case ExitsInRoomType.ZAxisP:
|
||
entranceToRoomType = EntranceToRoomType.ZAxisN;
|
||
|
||
exitsInRoomType.Add (ExitsInRoomType.ZAxisP);
|
||
// Начинается у верхней стены комнаты (posRoom.y + Wigth)
|
||
// Центрируем коридор по горизонтали/оси X:
|
||
corridorPos = new Vector2(
|
||
posRoom.x + (Length - corridorWigth) / 2f,
|
||
posRoom.y + Wigth
|
||
);
|
||
// По оси X коридор узкий, по оси Z — длинный:
|
||
cLength = corridorWigth;
|
||
cWidth = corridorLength;
|
||
break;
|
||
|
||
// Выход назад/вниз (-Z / в Vector2 это Y)
|
||
case ExitsInRoomType.ZAxisN:
|
||
entranceToRoomType = EntranceToRoomType.ZAxisP;
|
||
|
||
exitsInRoomType.Add (ExitsInRoomType.ZAxisN);
|
||
// Начинается ниже комнаты, отступая на длину коридора
|
||
corridorPos = new Vector2(
|
||
posRoom.x + (Length - corridorWigth) / 2f,
|
||
posRoom.y - corridorLength
|
||
);
|
||
cLength = corridorWigth;
|
||
cWidth = corridorLength;
|
||
break;
|
||
}
|
||
|
||
// Создаем коридор как отдельную комнату
|
||
|
||
Room corridor = new Room(corridorPos,
|
||
cLength, cWidth, RoomType.Corridor,false,
|
||
EntranceToRoomType.Null,exitsInRoomType);
|
||
|
||
LevelManager.Rooms.Add(corridor);
|
||
|
||
// Визуализация коридора (расставляем маркеры по углам коридора для наглядности)
|
||
Instantiate(GameObjectB, new Vector3(corridorPos.x, 0, corridorPos.y), Quaternion.identity);
|
||
Instantiate(GameObjectB, new Vector3(corridorPos.x + cLength, 0, corridorPos.y), Quaternion.identity);
|
||
Instantiate(GameObjectB, new Vector3(corridorPos.x + cLength, 0, corridorPos.y + cWidth), Quaternion.identity);
|
||
Instantiate(GameObjectB, new Vector3(corridorPos.x, 0, corridorPos.y + cWidth), Quaternion.identity);
|
||
return corridor;
|
||
}
|
||
|
||
void TryPlaceRoom(Room corridor)
|
||
{
|
||
// Базовая защита: если коридора нет или у него нет направления выхода
|
||
if (corridor == null || corridor.exits.Count == 0) return;
|
||
|
||
ExitsInRoomType corridorDirection = corridor.exits.Last();
|
||
|
||
// Создаем новую комнату со случайными размерами
|
||
Room newRoom = GenerateRandomRoom();
|
||
newRoom.rt = RoomType.Undefined;
|
||
|
||
Vector2 roomPos = Vector2.zero;
|
||
EntranceToRoomType entrance = EntranceToRoomType.Null;
|
||
|
||
// 1. Стыкуем вход новой комнаты с выходом из коридора
|
||
switch (corridorDirection)
|
||
{
|
||
case ExitsInRoomType.XAxisP: // Коридор шел вправо (+X)
|
||
roomPos = new Vector2(
|
||
corridor.Position.x + corridor.Length,
|
||
corridor.Position.y + (corridor.Wigth - newRoom.Wigth) / 2f
|
||
);
|
||
entrance = EntranceToRoomType.XAxisN; // Вход в комнату с левой стены (-X)
|
||
break;
|
||
|
||
case ExitsInRoomType.XAxisN: // Коридор шел влево (-X)
|
||
roomPos = new Vector2(
|
||
corridor.Position.x - newRoom.Length,
|
||
corridor.Position.y + (corridor.Wigth - newRoom.Wigth) / 2f
|
||
);
|
||
entrance = EntranceToRoomType.XAxisP; // Вход с правой стены (+X)
|
||
break;
|
||
|
||
case ExitsInRoomType.ZAxisP: // Коридор шел вверх (+Z)
|
||
roomPos = new Vector2(
|
||
corridor.Position.x + (corridor.Length - newRoom.Length) / 2f,
|
||
corridor.Position.y + corridor.Wigth
|
||
);
|
||
entrance = EntranceToRoomType.ZAxisN; // Вход с нижней стены (-Z)
|
||
break;
|
||
|
||
case ExitsInRoomType.ZAxisN: // Коридор шел вниз (-Z)
|
||
roomPos = new Vector2(
|
||
corridor.Position.x + (corridor.Length - newRoom.Length) / 2f,
|
||
corridor.Position.y - newRoom.Wigth
|
||
);
|
||
entrance = EntranceToRoomType.ZAxisP; // Вход с верхней стены (+Z)
|
||
break;
|
||
}
|
||
|
||
// 2. Проверяем: влезает ли комната в границы и не накладывается ли на другие комнаты/коридоры
|
||
if (!IsAreaClear(roomPos, newRoom.Length, newRoom.Wigth))
|
||
{
|
||
// Не влезла — эта ветка лабиринта затухает сама
|
||
return;
|
||
}
|
||
|
||
// Если всё чисто, сохраняем параметры и добавляем в список
|
||
newRoom.Position = roomPos;
|
||
newRoom.entrance = entrance;
|
||
LevelManager.Rooms.Add(newRoom);
|
||
|
||
// Визуализация новой комнаты (маркеры по углам)
|
||
Instantiate(GameObjectB, new Vector3(newRoom.Position.x, 0, newRoom.Position.y), Quaternion.identity);
|
||
Instantiate(GameObjectB, new Vector3(newRoom.Position.x + newRoom.Length, 0, newRoom.Position.y), Quaternion.identity);
|
||
Instantiate(GameObjectB, new Vector3(newRoom.Position.x + newRoom.Length, 0, newRoom.Position.y + newRoom.Wigth), Quaternion.identity);
|
||
Instantiate(GameObjectB, new Vector3(newRoom.Position.x, 0, newRoom.Position.y + newRoom.Wigth), Quaternion.identity);
|
||
|
||
// 3. РЕКУРСИЯ: получаем 3 свободные стены (все кроме той, где вход)
|
||
List<ExitsInRoomType> availableSides = GetThreeAvailableSides(entrance);
|
||
|
||
// Перемешиваем стороны, чтобы ветвление шло в случайном порядке
|
||
availableSides = availableSides.OrderBy(x => Random.value).ToList();
|
||
|
||
foreach (var exitSide in availableSides)
|
||
{
|
||
// Шанс ветвления 65%: чтобы этаж не забивался сплошным ковром комнат,
|
||
// а оставались тупики и коридорные развилки
|
||
if (Random.value < 0.65f)
|
||
{
|
||
// Пытаемся построить коридор в эту сторону
|
||
Room nextCorridor = CreateCorridor(newRoom.Position, newRoom.Length, newRoom.Wigth, exitSide);
|
||
|
||
if (nextCorridor != null)
|
||
{
|
||
// Рекурсивный шаг: пытаемся пристроить комнату к концу нового коридора
|
||
TryPlaceRoom(nextCorridor);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Вспомогательный метод 1: возвращает 3 стены, исключая стену входа
|
||
List<ExitsInRoomType> GetThreeAvailableSides(EntranceToRoomType entrance)
|
||
{
|
||
List<ExitsInRoomType> sides = new List<ExitsInRoomType>();
|
||
|
||
if (entrance != EntranceToRoomType.XAxisN) sides.Add(ExitsInRoomType.XAxisN);
|
||
if (entrance != EntranceToRoomType.XAxisP) sides.Add(ExitsInRoomType.XAxisP);
|
||
if (entrance != EntranceToRoomType.ZAxisN) sides.Add(ExitsInRoomType.ZAxisN);
|
||
if (entrance != EntranceToRoomType.ZAxisP) sides.Add(ExitsInRoomType.ZAxisP);
|
||
|
||
return sides;
|
||
}
|
||
|
||
// Вспомогательный метод 2: проверка на границы этажа и пересечение с существующими объектами
|
||
bool IsAreaClear(Vector2 pos, int length, int wigth)
|
||
{
|
||
// Проверка границ этажа [0 .. lengthFloor]
|
||
if (pos.x < 0 || pos.y < 0 ||
|
||
pos.x + length > lengthFloor ||
|
||
pos.y + wigth > lengthFloor)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
Rect newRect = new Rect(pos.x, pos.y, length, wigth);
|
||
|
||
// Проверка наложения на все уже созданные комнаты и коридоры
|
||
foreach (var existing in LevelManager.Rooms)
|
||
{
|
||
Rect existingRect = new Rect(existing.Position.x, existing.Position.y, existing.Length, existing.Wigth);
|
||
if (newRect.Overlaps(existingRect))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
Room GenerateRandomRoom()
|
||
{
|
||
int a = Random.Range(lengthWallMin, lengthWallMax);
|
||
int b = Random.Range(lengthWallMin, lengthWallMax);
|
||
Room room = new Room();
|
||
room.Length = a;
|
||
room.Wigth = b;
|
||
return room;
|
||
}
|
||
|
||
void CreateStartRoom()
|
||
{
|
||
float randValue = Random.value;
|
||
int xR, zR;
|
||
Vector2Int posStartRoom = new Vector2Int();
|
||
|
||
Room room = new Room( lengthStartRoom, lengthStartRoom, RoomType.Start);
|
||
if (randValue < 0.25f) //1 квадрант
|
||
{
|
||
xR = Random.Range(0,lengthFloor/2-lengthStartRoom);
|
||
if (xR + lengthStartRoom <= lengthFloor / 4)
|
||
{
|
||
room.exits.Add(ExitsInRoomType.XAxisP);
|
||
zR = Random.Range(0, lengthFloor/2-lengthStartRoom);
|
||
}
|
||
else
|
||
{
|
||
room.exits.Add(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)
|
||
{
|
||
room.exits.Add(ExitsInRoomType.ZAxisP);
|
||
zR = Random.Range(0, lengthFloor/4-lengthStartRoom);
|
||
}
|
||
else
|
||
{
|
||
room.exits.Add(ExitsInRoomType.XAxisN);
|
||
zR = Random.Range(0, lengthFloor/2-lengthStartRoom);
|
||
}
|
||
}
|
||
else if (randValue < 0.75f)
|
||
{
|
||
xR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom);
|
||
if (xR + lengthStartRoom <= lengthFloor * 3 / 4)
|
||
{
|
||
room.exits.Add(ExitsInRoomType.ZAxisN);
|
||
zR = Random.Range(lengthFloor * 3 / 4, lengthFloor - lengthStartRoom);
|
||
}
|
||
else
|
||
{
|
||
room.exits.Add(ExitsInRoomType.XAxisN);
|
||
zR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
xR = Random.Range(0, lengthFloor / 2 - lengthStartRoom);
|
||
if (xR + lengthStartRoom <= lengthFloor / 4)
|
||
{
|
||
room.exits.Add(ExitsInRoomType.XAxisP);
|
||
zR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom);
|
||
}
|
||
else
|
||
{
|
||
room.exits.Add(ExitsInRoomType.ZAxisN);
|
||
zR = Random.Range(lengthFloor * 3 / 4, lengthFloor - lengthStartRoom);
|
||
}
|
||
}
|
||
|
||
room.entrance = EntranceToRoomType.Null;
|
||
LevelManager.Rooms.Add(room);
|
||
Instantiate(GameObjectA, new Vector3(0,-1,0), Quaternion.identity);
|
||
|
||
Instantiate(GameObjectB, new Vector3(xR,0,zR), Quaternion.identity);
|
||
Instantiate(GameObjectB, new Vector3(xR+lengthStartRoom,0,zR), Quaternion.identity);
|
||
Instantiate(GameObjectB, new Vector3(xR+lengthStartRoom,0,zR+lengthStartRoom), Quaternion.identity);
|
||
Instantiate(GameObjectB, new Vector3(xR,0,zR+lengthStartRoom), Quaternion.identity);
|
||
|
||
CreateCorridor(new Vector2Int(xR, zR),lengthStartRoom,lengthStartRoom,room.exits.First());
|
||
Room firstCorridor = CreateCorridor(new Vector2(xR, zR), lengthStartRoom, lengthStartRoom, room.exits.First());
|
||
if (firstCorridor != null)
|
||
{
|
||
TryPlaceRoom(firstCorridor);
|
||
}
|
||
}
|
||
|
||
}
|
||
} |