594 lines
27 KiB
C#
594 lines
27 KiB
C#
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using UnityEngine;
|
||
using Random = UnityEngine.Random;
|
||
|
||
namespace _Project.Scripts.LevelGenerating
|
||
{
|
||
// =====================================================================
|
||
// НЕ ЗАБЫТЬ:
|
||
// 1. Поля инспектора - в МЕТРАХ. Внутри генератора всё в МОДУЛЯХ (0.5 м), см. ReadSettings().
|
||
// 2. Размеры комнат только чётные в модулях (целые метры): метод Even().
|
||
// 3. Данные этажа лежат в FloorData (_data). Статический LevelManager.Rooms больше не используется.
|
||
// 4. TODO: переименовать Room.isWallable (сейчас значит "комнату ужали об край карты").
|
||
// 5. TODO: спавн маркеров в Generate() временный. Он переедет в стадию "оболочка" (стены, пол).
|
||
// 6. Этаж с боссом (крыша) сюда не попадает: это FloorKind.Special, собирается вручную.
|
||
// =====================================================================
|
||
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 minRoomCount;
|
||
[SerializeField] private int _minDepthLiftRoom;
|
||
[SerializeField] private float _minDistanceStartLift; // метры
|
||
[SerializeField] private GameObject GameObjectA;
|
||
[SerializeField] private GameObject GameObjectB;
|
||
[SerializeField] private GameObject Player;
|
||
[SerializeField] private ShellSpawner shellSpawner; // если назначен, строит пол, стены и потолок
|
||
[SerializeField] private RoomGeneratorRegistry roomGenerators; // оформляет комнаты по темам (материалы, свет)
|
||
|
||
[Header("Генерация")]
|
||
[SerializeField] private int maxRooms = 15;
|
||
[SerializeField, Range(0f, 1f)] private float branchChance = 0.7f;
|
||
[SerializeField] private float roomMargin = 2f; // зазор между комнатами, метры
|
||
[SerializeField] private bool liftDoorAnyPosition = false; // true, если дверь лифта может быть не по центру
|
||
|
||
// ---- Те же настройки в модулях (заполняет ReadSettings) ----
|
||
private int _wallMin, _wallMax, _startSize, _liftLen, _liftWid, _floor, _corrLen, _corrWid, _margin;
|
||
private float _minDist;
|
||
|
||
// ---- Состояние генерации ----
|
||
private FloorData _data;
|
||
private int _curRooms;
|
||
private readonly Dictionary<Room, (Room parent, ExitsInRoomType dir)> _parent
|
||
= new Dictionary<Room, (Room, ExitsInRoomType)>();
|
||
private readonly Dictionary<Room, Room> _corridorOf = new Dictionary<Room, Room>();
|
||
|
||
public FloorData Data => _data;
|
||
|
||
private static readonly ExitsInRoomType[] AllDirs =
|
||
{
|
||
ExitsInRoomType.XAxisP, ExitsInRoomType.XAxisN,
|
||
ExitsInRoomType.ZAxisP, ExitsInRoomType.ZAxisN
|
||
};
|
||
|
||
private static readonly RoomType[] ClippedRoles = { RoomType.Laboratory, RoomType.Office, RoomType.Penthouse };
|
||
private static readonly RoomType[] FullRoles = { RoomType.Storage, RoomType.SwitchboardRoom, RoomType.BoilerRoom };
|
||
|
||
void Start()
|
||
{
|
||
ReadSettings();
|
||
var data = Generate(0); // позже этим будет управлять оркестратор
|
||
var pos = data.Start.Center;
|
||
Player.transform.position = new Vector3(pos.x/2f, 1, pos.y/2f);
|
||
}
|
||
|
||
void ReadSettings()
|
||
{
|
||
_wallMin = LevelGrid.FromMeters(lengthWallMin);
|
||
_wallMax = LevelGrid.FromMeters(lengthWallMax);
|
||
_startSize = LevelGrid.FromMeters(lengthStartRoom);
|
||
_liftLen = LevelGrid.FromMeters(lengthLiftRoom);
|
||
_liftWid = LevelGrid.FromMeters(widthLiftRoom);
|
||
_floor = LevelGrid.FromMeters(lengthFloor);
|
||
_corrLen = LevelGrid.FromMeters(corridorLength);
|
||
_corrWid = LevelGrid.FromMeters(corridorWigth);
|
||
_margin = Mathf.RoundToInt(roomMargin * LevelGrid.PerMeter);
|
||
_minDist = _minDistanceStartLift * LevelGrid.PerMeter;
|
||
}
|
||
|
||
void OnValidate()
|
||
{
|
||
lengthWallMax = Mathf.Max(lengthWallMax, lengthWallMin);
|
||
minRoomCount = Mathf.Min(minRoomCount, maxRooms + 1); // +1 за лифт
|
||
|
||
if (lengthFloor < lengthStartRoom + corridorLength + Mathf.Max(lengthLiftRoom, widthLiftRoom))
|
||
Debug.LogWarning("lengthFloor слишком мал для старта + коридора + лифта");
|
||
}
|
||
|
||
// ---------- Генерация этажа ----------
|
||
|
||
void ResetState(int floorIndex)
|
||
{
|
||
_data = FloorData.CreateGenerated(floorIndex, _floor);
|
||
_parent.Clear();
|
||
_corridorOf.Clear();
|
||
_curRooms = 0;
|
||
}
|
||
|
||
public FloorData Generate(int floorIndex)
|
||
{
|
||
bool ok = false;
|
||
|
||
// Фаза 1: все ограничения, без обрезки веток
|
||
for (int i = 0; i < 50 && !ok; i++)
|
||
{
|
||
ResetState(floorIndex);
|
||
CreateStartRoom();
|
||
ok = TryAttachLift(true, _margin, _minDist) && _curRooms >= minRoomCount;
|
||
}
|
||
|
||
// Фаза 2 (аварийная): лифт обязателен, остальное ослабляется
|
||
for (int i = 0; i < 20 && !ok; i++)
|
||
{
|
||
ResetState(floorIndex);
|
||
CreateStartRoom();
|
||
ok = PlaceElevator();
|
||
if (ok) Debug.LogWarning("Этаж собран в аварийном режиме: ограничения ослаблены");
|
||
}
|
||
|
||
if (!ok) Debug.LogError("Лифт не помещается: проверь размеры карты, лифта и коридора");
|
||
|
||
AssignRoles();
|
||
ShellBuilder.Build(_data); // отрезки стен (только данные)
|
||
|
||
if (shellSpawner != null)
|
||
{
|
||
var shell = shellSpawner.Spawn(_data); // меши: пол, потолок, стены по комнатам
|
||
if (shell != null && roomGenerators != null)
|
||
roomGenerators.Run(shell, _data); // оформление каждой комнаты (материалы, свет)
|
||
}
|
||
else SpawnMarkers(); // временно, если спавнер не назначен
|
||
return _data;
|
||
}
|
||
|
||
// Роли для комнат без назначения: ужатые об край карты и обычные берут разные пулы
|
||
void AssignRoles()
|
||
{
|
||
foreach (var r in _data.UnassignedRooms.ToList())
|
||
{
|
||
var pool = r.isClipped ? ClippedRoles : FullRoles;
|
||
r.rt = pool[Random.Range(0, pool.Length)];
|
||
}
|
||
}
|
||
|
||
// Временная визуализация, переедет в стадию "оболочка"
|
||
void SpawnMarkers()
|
||
{
|
||
Instantiate(GameObjectA, new Vector3(0, -1, 0), Quaternion.identity);
|
||
|
||
foreach (var room in _data.Rooms)
|
||
{
|
||
var p = room.Position;
|
||
Instantiate(GameObjectB, LevelGrid.ToWorld(new Vector2Int(p.x, p.y)), Quaternion.identity);
|
||
Instantiate(GameObjectB, LevelGrid.ToWorld(new Vector2Int(p.x + room.Length, p.y)), Quaternion.identity);
|
||
Instantiate(GameObjectB, LevelGrid.ToWorld(new Vector2Int(p.x + room.Length, p.y + room.Width)), Quaternion.identity);
|
||
Instantiate(GameObjectB, LevelGrid.ToWorld(new Vector2Int(p.x, p.y + room.Width)), Quaternion.identity);
|
||
}
|
||
}
|
||
|
||
// ---------- Лифт ----------
|
||
|
||
bool PlaceElevator()
|
||
{
|
||
while (true)
|
||
{
|
||
if (TryAttachLift(true, _margin, _minDist)) return true;
|
||
if (TryAttachLift(false, _margin, _minDist)) return true;
|
||
if (TryAttachLift(false, _margin, 0f)) return true;
|
||
if (TryAttachLift(false, 0, 0f)) return true;
|
||
if (!PruneRandomLeaf()) return false; // последнее средство: освобождаем место
|
||
}
|
||
}
|
||
|
||
// Вход в лифт только в длинную стену
|
||
private ExitsInRoomType[] LiftEntryDirs()
|
||
{
|
||
if (_liftLen > _liftWid) return new[] { ExitsInRoomType.ZAxisP, ExitsInRoomType.ZAxisN };
|
||
if (_liftWid > _liftLen) return new[] { ExitsInRoomType.XAxisP, ExitsInRoomType.XAxisN };
|
||
return AllDirs;
|
||
}
|
||
|
||
// Сдвиги лифта вдоль стены (в модулях), если дверь в префабе не обязательно по центру
|
||
private IEnumerable<int> LiftShifts(ExitsInRoomType dir)
|
||
{
|
||
yield return 0;
|
||
if (!liftDoorAnyPosition) yield break;
|
||
|
||
bool xDir = dir == ExitsInRoomType.XAxisP || dir == ExitsInRoomType.XAxisN;
|
||
int cross = xDir ? _liftWid : _liftLen;
|
||
int max = (cross - _corrWid) / 2;
|
||
for (int s = 1; s <= max; s++) { yield return s; yield return -s; }
|
||
}
|
||
|
||
bool TryAttachLift(bool requireMinDepth, int margin, float minDistance)
|
||
{
|
||
var start = _data.Start;
|
||
if (start == null) return false;
|
||
|
||
var parents = _data.Depth.Keys
|
||
.OrderByDescending(r => _data.Depth[r])
|
||
.ThenByDescending(r => (r.Center - start.Center).sqrMagnitude)
|
||
.ToList();
|
||
|
||
foreach (var parent in parents)
|
||
{
|
||
int liftDepth = _data.Depth[parent] + 1;
|
||
if (requireMinDepth && liftDepth < _minDepthLiftRoom) continue;
|
||
|
||
foreach (var dir in LiftEntryDirs().OrderBy(_ => Random.value))
|
||
{
|
||
if (parent.exits.Contains(dir)) continue;
|
||
|
||
bool xDir = dir == ExitsInRoomType.XAxisP || dir == ExitsInRoomType.XAxisN;
|
||
|
||
foreach (int shift in LiftShifts(dir))
|
||
{
|
||
Room lift = new Room { Length = _liftLen, Width = _liftWid };
|
||
PlaceByEntry(EntryPoint(parent, dir), dir, lift);
|
||
lift.Position += xDir ? new Vector2Int(0, shift) : new Vector2Int(shift, 0);
|
||
|
||
if (minDistance > 0f && Vector2.Distance(lift.Center, start.Center) < minDistance)
|
||
continue;
|
||
|
||
if (!CanPlaceLift(parent, lift, dir, margin)) continue;
|
||
|
||
lift.rt = RoomType.Lift; // тип нужно выставить ДО AddRoom, чтобы FloorData запомнил Lift
|
||
lift.exits = new List<ExitsInRoomType>();
|
||
lift.entrance = ToEntrance(dir);
|
||
lift.isClipped = false;
|
||
|
||
_data.AddRoom(lift, liftDepth);
|
||
_parent[lift] = (parent, dir);
|
||
_corridorOf[lift] = CreateCorridor(parent, dir);
|
||
parent.exits.Add(dir);
|
||
_curRooms++;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool CanPlaceLift(Room parent, Room lift, ExitsInRoomType dir, int margin)
|
||
{
|
||
if (!InBounds(lift)) return false;
|
||
if (!IsAreaFree(CorridorRect(parent, dir))) return false;
|
||
|
||
return IsAreaFree(Inflate(RoomRect(lift), Mathf.Min(margin, _corrLen)), parent);
|
||
}
|
||
|
||
// Удаляет случайную тупиковую комнату вместе с её коридором
|
||
bool PruneRandomLeaf()
|
||
{
|
||
var parentsSet = new HashSet<Room>(_parent.Values.Select(v => v.parent));
|
||
var leaves = _parent.Keys.Where(r => !parentsSet.Contains(r)).ToList();
|
||
if (leaves.Count == 0) return false;
|
||
|
||
Room leaf = leaves[Random.Range(0, leaves.Count)];
|
||
var (p, dir) = _parent[leaf];
|
||
|
||
_data.Remove(leaf);
|
||
if (_corridorOf.TryGetValue(leaf, out var cor))
|
||
{
|
||
_data.Remove(cor);
|
||
_corridorOf.Remove(leaf);
|
||
}
|
||
p.exits.Remove(dir);
|
||
_parent.Remove(leaf);
|
||
_curRooms--;
|
||
return true;
|
||
}
|
||
|
||
// ---------- Геометрия (всё в модулях) ----------
|
||
|
||
private static void PlaceByEntry(Vector2Int e, ExitsInRoomType dir, Room n)
|
||
{
|
||
switch (dir)
|
||
{
|
||
case ExitsInRoomType.XAxisP: n.Position = new Vector2Int(e.x, e.y - n.Width / 2); break;
|
||
case ExitsInRoomType.XAxisN: n.Position = new Vector2Int(e.x - n.Length, e.y - n.Width / 2); break;
|
||
case ExitsInRoomType.ZAxisP: n.Position = new Vector2Int(e.x - n.Length / 2, e.y); break;
|
||
default: n.Position = new Vector2Int(e.x - n.Length / 2, e.y - n.Width); break;
|
||
}
|
||
}
|
||
|
||
private bool InBounds(Room r) =>
|
||
r.Position.x >= 0 && r.Position.y >= 0 &&
|
||
r.Position.x + r.Length <= _floor &&
|
||
r.Position.y + r.Width <= _floor;
|
||
|
||
// Чётное значение (целые метры), чтобы центровка (a - b) / 2 была целой
|
||
private static int Even(int v) => v & ~1;
|
||
|
||
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 RectInt RoomRect(Room r) => new RectInt(r.Position.x, r.Position.y, r.Length, r.Width);
|
||
|
||
private static RectInt Inflate(RectInt r, int m) =>
|
||
new RectInt(r.x - m, r.y - m, r.width + 2 * m, r.height + 2 * m);
|
||
|
||
// Прямоугольник коридора, ведущего из prev в направлении dir
|
||
private RectInt CorridorRect(Room prev, ExitsInRoomType dir)
|
||
{
|
||
switch (dir)
|
||
{
|
||
case ExitsInRoomType.XAxisP:
|
||
return new RectInt(prev.Position.x + prev.Length,
|
||
prev.Position.y + (prev.Width - _corrWid) / 2,
|
||
_corrLen, _corrWid);
|
||
case ExitsInRoomType.XAxisN:
|
||
return new RectInt(prev.Position.x - _corrLen,
|
||
prev.Position.y + (prev.Width - _corrWid) / 2,
|
||
_corrLen, _corrWid);
|
||
case ExitsInRoomType.ZAxisP:
|
||
return new RectInt(prev.Position.x + (prev.Length - _corrWid) / 2,
|
||
prev.Position.y + prev.Width,
|
||
_corrWid, _corrLen);
|
||
default: // ZAxisN
|
||
return new RectInt(prev.Position.x + (prev.Length - _corrWid) / 2,
|
||
prev.Position.y - _corrLen,
|
||
_corrWid, _corrLen);
|
||
}
|
||
}
|
||
|
||
// Центр стены новой комнаты, куда упирается коридор
|
||
private Vector2Int EntryPoint(Room prev, ExitsInRoomType dir)
|
||
{
|
||
switch (dir)
|
||
{
|
||
case ExitsInRoomType.XAxisP:
|
||
return new Vector2Int(prev.Position.x + prev.Length + _corrLen,
|
||
prev.Position.y + prev.Width / 2);
|
||
case ExitsInRoomType.XAxisN:
|
||
return new Vector2Int(prev.Position.x - _corrLen,
|
||
prev.Position.y + prev.Width / 2);
|
||
case ExitsInRoomType.ZAxisP:
|
||
return new Vector2Int(prev.Position.x + prev.Length / 2,
|
||
prev.Position.y + prev.Width + _corrLen);
|
||
default:
|
||
return new Vector2Int(prev.Position.x + prev.Length / 2,
|
||
prev.Position.y - _corrLen);
|
||
}
|
||
}
|
||
|
||
private bool IsAreaFree(RectInt rect, Room ignore = null)
|
||
{
|
||
foreach (var other in _data.Rooms)
|
||
{
|
||
if (other == ignore) continue;
|
||
if (rect.Overlaps(RoomRect(other))) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ---------- Коридор ----------
|
||
|
||
Room CreateCorridor(Room prev, ExitsInRoomType dir)
|
||
{
|
||
RectInt r = CorridorRect(prev, dir);
|
||
|
||
// Конструктор Room принимает (width = размер по Z, length = размер по X).
|
||
// RectInt: width = размер по X, height = размер по Z - поэтому аргументы переставлены.
|
||
Room corridor = new Room(r.position, r.height, r.width,
|
||
RoomType.Corridor, false,
|
||
ToEntrance(dir), new List<ExitsInRoomType> { dir });
|
||
|
||
_data.AddCorridor(corridor);
|
||
return corridor;
|
||
}
|
||
|
||
// ---------- Размещение комнаты ----------
|
||
|
||
// Задаёт размер и позицию комнаты от точки входа: вдоль оси выхода комната растёт на полную
|
||
// длину, поперёк - симметрично (центр на коридоре). Если не влезает, ужимает.
|
||
private bool FitRoom(Room prev, Room n, ExitsInRoomType dir, out bool shrunk)
|
||
{
|
||
Vector2Int e = EntryPoint(prev, dir);
|
||
int origLen = n.Length, origWid = n.Width;
|
||
|
||
switch (dir)
|
||
{
|
||
case ExitsInRoomType.XAxisP:
|
||
case ExitsInRoomType.XAxisN:
|
||
{
|
||
int maxAlong = dir == ExitsInRoomType.XAxisP ? _floor - e.x : e.x;
|
||
int maxCross = 2 * Mathf.Min(e.y, _floor - e.y);
|
||
n.Length = Even(Mathf.Min(n.Length, maxAlong));
|
||
n.Width = Even(Mathf.Min(n.Width, maxCross));
|
||
int x = dir == ExitsInRoomType.XAxisP ? e.x : e.x - n.Length;
|
||
n.Position = new Vector2Int(x, e.y - n.Width / 2);
|
||
break;
|
||
}
|
||
default: // Z
|
||
{
|
||
int maxAlong = dir == ExitsInRoomType.ZAxisP ? _floor - e.y : e.y;
|
||
int maxCross = 2 * Mathf.Min(e.x, _floor - e.x);
|
||
n.Width = Even(Mathf.Min(n.Width, maxAlong));
|
||
n.Length = Even(Mathf.Min(n.Length, maxCross));
|
||
int y = dir == ExitsInRoomType.ZAxisP ? e.y : e.y - n.Width;
|
||
n.Position = new Vector2Int(e.x - n.Length / 2, y);
|
||
break;
|
||
}
|
||
}
|
||
|
||
shrunk = n.Length != origLen || n.Width != origWid;
|
||
return n.Length >= _wallMin && n.Width >= _wallMin;
|
||
}
|
||
|
||
bool TryPlaceRoom(Room prevRoom, ExitsInRoomType dir)
|
||
{
|
||
if (prevRoom == null || _curRooms >= maxRooms) return false;
|
||
|
||
Room newRoom = GenerateRandomRoom();
|
||
newRoom.rt = RoomType.Undefined;
|
||
newRoom.exits = new List<ExitsInRoomType>(); // заполнится по факту построенных выходов
|
||
newRoom.entrance = ToEntrance(dir);
|
||
|
||
if (!FitRoom(prevRoom, newRoom, dir, out bool shrunk)) return false;
|
||
newRoom.isClipped = shrunk;
|
||
|
||
// Пересечения: комната (с зазором, без prevRoom) и коридор к ней
|
||
RectInt roomArea = Inflate(RoomRect(newRoom), Mathf.Min(_margin, _corrLen));
|
||
if (!IsAreaFree(roomArea, prevRoom)) return false;
|
||
if (!IsAreaFree(CorridorRect(prevRoom, dir))) return false;
|
||
|
||
// Всё хорошо - фиксируем
|
||
_data.AddRoom(newRoom, _data.Depth[prevRoom] + 1);
|
||
_parent[newRoom] = (prevRoom, dir);
|
||
_corridorOf[newRoom] = 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);
|
||
return new Room { Length = LevelGrid.FromMeters(a), Width = LevelGrid.FromMeters(b) };
|
||
}
|
||
|
||
// ---------- Стартовая комната ----------
|
||
|
||
void CreateStartRoom()
|
||
{
|
||
float randValue = Random.value;
|
||
int xR, zR;
|
||
ExitsInRoomType firstDir;
|
||
|
||
Room room = new Room(_startSize, _startSize, RoomType.Start);
|
||
room.exits = new List<ExitsInRoomType>();
|
||
room.entrance = EntranceToRoomType.Null;
|
||
|
||
if (randValue < 0.25f) // 1 квадрант
|
||
{
|
||
xR = Random.Range(0, _floor / 2 - _startSize);
|
||
if (xR + _startSize <= _floor / 4)
|
||
{
|
||
firstDir = ExitsInRoomType.XAxisP;
|
||
zR = Random.Range(0, _floor / 2 - _startSize);
|
||
}
|
||
else
|
||
{
|
||
firstDir = ExitsInRoomType.ZAxisP;
|
||
zR = Random.Range(0, _floor / 4 - _startSize);
|
||
}
|
||
}
|
||
else if (randValue < 0.5f) // 2 квадрант
|
||
{
|
||
xR = Random.Range(_floor / 2, _floor - _startSize);
|
||
if (xR + _startSize <= _floor * 3 / 4)
|
||
{
|
||
firstDir = ExitsInRoomType.ZAxisP;
|
||
zR = Random.Range(0, _floor / 4 - _startSize);
|
||
}
|
||
else
|
||
{
|
||
firstDir = ExitsInRoomType.XAxisN;
|
||
zR = Random.Range(0, _floor / 2 - _startSize);
|
||
}
|
||
}
|
||
else if (randValue < 0.75f) // 3 квадрант
|
||
{
|
||
xR = Random.Range(_floor / 2, _floor - _startSize);
|
||
if (xR + _startSize <= _floor * 3 / 4)
|
||
{
|
||
firstDir = ExitsInRoomType.ZAxisN;
|
||
zR = Random.Range(_floor * 3 / 4, _floor - _startSize);
|
||
}
|
||
else
|
||
{
|
||
firstDir = ExitsInRoomType.XAxisN;
|
||
zR = Random.Range(_floor / 2, _floor - _startSize);
|
||
}
|
||
}
|
||
else // 4 квадрант
|
||
{
|
||
xR = Random.Range(0, _floor / 2 - _startSize);
|
||
if (xR + _startSize <= _floor / 4)
|
||
{
|
||
firstDir = ExitsInRoomType.XAxisP;
|
||
zR = Random.Range(_floor / 2, _floor - _startSize);
|
||
}
|
||
else
|
||
{
|
||
firstDir = ExitsInRoomType.ZAxisN;
|
||
zR = Random.Range(_floor * 3 / 4, _floor - _startSize);
|
||
}
|
||
}
|
||
|
||
room.Position = new Vector2Int(xR, zR);
|
||
_data.AddRoom(room, 0); // rt = Start, поэтому FloorData запомнит _data.Start
|
||
_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()
|
||
{
|
||
if (_data == null) return;
|
||
|
||
foreach (var r in _data.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;
|
||
case RoomType.Laboratory: Gizmos.color = Color.white; break;
|
||
case RoomType.Office: Gizmos.color = Color.blue; break;
|
||
case RoomType.Penthouse: Gizmos.color = Color.magenta; break;
|
||
case RoomType.Storage: Gizmos.color = new Color(1f, 0.5f, 0f); break;
|
||
case RoomType.SwitchboardRoom: Gizmos.color = Color.red; break;
|
||
case RoomType.BoilerRoom: Gizmos.color = Color.gray; break;
|
||
default: Gizmos.color = Color.cyan; break;
|
||
}
|
||
|
||
Vector3 c = LevelGrid.ToWorld(r.Center);
|
||
Gizmos.DrawWireCube(c, new Vector3(r.Length * LevelGrid.Module, 0.1f, r.Width * LevelGrid.Module));
|
||
}
|
||
|
||
// Отрезки стен: чёрные линии на высоте 1 м
|
||
Gizmos.color = Color.black;
|
||
foreach (var w in _data.Walls)
|
||
{
|
||
bool alongX = w.Facing == WallFacing.ZP || w.Facing == WallFacing.ZN;
|
||
Vector2Int end = w.Start + (alongX ? new Vector2Int(w.Length, 0) : new Vector2Int(0, w.Length));
|
||
Gizmos.DrawLine(LevelGrid.ToWorld(w.Start, 1f), LevelGrid.ToWorld(end, 1f));
|
||
}
|
||
}
|
||
}
|
||
} |