diff --git a/Assets/Scenes/Main.unity b/Assets/Scenes/Main.unity index 473014a..c3d2559 100644 --- a/Assets/Scenes/Main.unity +++ b/Assets/Scenes/Main.unity @@ -13842,15 +13842,15 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Assembly-CSharp::_Project.Scripts.LevelGenerating.WallsGenerator floorsCount: 10 - lengthWallMin: 10 - lengthWallMax: 25 + lengthWallMin: 20 + lengthWallMax: 40 lengthStartRoom: 10 lengthLiftRoom: 10 widthLiftRoom: 5 lengthFloor: 100 corridorLength: 5 corridorWigth: 4 - _minDepthLiftRoom: 0 + _minDepthLiftRoom: 7 GameObjectA: {fileID: 8066171868259172852, guid: e46748b8eb8a6074d9bb87701cebe84b, type: 3} GameObjectB: {fileID: 4641718306729093824, guid: 3905eab8a5f6b3049a83378b1acf1ebc, type: 3} maxRooms: 15 diff --git a/Assets/_Project/Scripts/LevelGenerating/WallsGenerator.cs b/Assets/_Project/Scripts/LevelGenerating/WallsGenerator.cs index 18c6e25..34a1632 100644 --- a/Assets/_Project/Scripts/LevelGenerating/WallsGenerator.cs +++ b/Assets/_Project/Scripts/LevelGenerating/WallsGenerator.cs @@ -26,6 +26,10 @@ namespace _Project.Scripts.LevelGenerating private int _curRooms; private readonly Dictionary _depth = new Dictionary(); + private readonly Dictionary _parent + = new Dictionary(); + [SerializeField] private bool liftDoorAnyPosition = false; // true, если дверь в префабе может быть не по центру + private readonly Dictionary _corridorOf = new Dictionary(); private LevelManager levelManager; private static readonly ExitsInRoomType[] AllDirs = @@ -40,40 +44,153 @@ namespace _Project.Scripts.LevelGenerating GenerateFloor(); } - bool PlaceElevator() + bool PlaceElevator() +{ + while (true) + { + if (TryAttachLift(true, roomMargin)) return true; // норма: глубина >= минимума + if (TryAttachLift(false, roomMargin)) return true; // без требования глубины + if (TryAttachLift(false, 0f)) return true; // без зазора + if (!PruneRandomLeaf()) return false; // освобождаем место и повторяем + } +} + +private ExitsInRoomType[] LiftEntryDirs() +{ + if (lengthLiftRoom > widthLiftRoom) return new[] { ExitsInRoomType.ZAxisP, ExitsInRoomType.ZAxisN }; + if (widthLiftRoom > lengthLiftRoom) return new[] { ExitsInRoomType.XAxisP, ExitsInRoomType.XAxisN }; + return AllDirs; +} + +// Сдвиги лифта вдоль стены, чтобы коридор всё равно попал в стену +private IEnumerable LiftShifts(ExitsInRoomType dir) +{ + yield return 0; + if (!liftDoorAnyPosition) yield break; + + bool xDir = dir == ExitsInRoomType.XAxisP || dir == ExitsInRoomType.XAxisN; + int cross = xDir ? widthLiftRoom : lengthLiftRoom; + int max = (cross - corridorWigth) / 2; + for (int s = 1; s <= max; s++) { yield return s; yield return -s; } +} + +bool TryAttachLift(bool requireMinDepth, float margin) +{ + var start = levelManager.GetRooms().First(r => r.rt == RoomType.Start); + + var parents = _depth.Keys + .OrderByDescending(r => _depth[r]) + .ThenByDescending(r => (r.Position - start.Position).sqrMagnitude) + .ToList(); + + foreach (var parent in parents) + { + if (requireMinDepth && _depth[parent] + 1 < _minDepthLiftRoom) continue; + + foreach (var dir in LiftEntryDirs().OrderBy(_ => Random.value)) { - var start = levelManager.GetRooms().First(r => r.rt == RoomType.Start); + if (parent.exits.Contains(dir)) continue; - // максимум шагов, при равенстве выбираем самую дальнюю от старта по прямой - var best = _depth - .OrderByDescending(p => p.Value) - .ThenByDescending(p => (p.Key.Position - start.Position).sqrMagnitude) - .First(); - - if (best.Key == start || best.Value <= _minDepthLiftRoom) + foreach (int shift in LiftShifts(dir)) { - return false; - + Room lift = new Room { Length = lengthLiftRoom, Width = widthLiftRoom }; + PlaceByEntry(EntryPoint(parent, dir), dir, lift); + + bool xDir = dir == ExitsInRoomType.XAxisP || dir == ExitsInRoomType.XAxisN; + lift.Position += xDir ? new Vector2(0, shift) : new Vector2(shift, 0); + + if (!CanPlaceLift(parent, lift, dir, margin)) continue; + + lift.rt = RoomType.Lift; + lift.exits = new List(); + lift.entrance = ToEntrance(dir); + lift.isWallable = false; + + levelManager.AddRoom(lift); + _depth[lift] = _depth[parent] + 1; + _parent[lift] = (parent, dir); + _corridorOf[lift] = CreateCorridor(parent, dir); + parent.exits.Add(dir); + _curRooms++; + return true; } - best.Key.rt = RoomType.Lift; // добавь значение в enum RoomType - best.Key.Length = lengthLiftRoom; - best.Key.Width = widthLiftRoom; - best.Key.exits.Clear(); - return true; } + } + return false; +} + +bool CanPlaceLift(Room parent, Room lift, ExitsInRoomType dir, float margin) +{ + if (!InBounds(lift)) return false; + if (!IsAreaFree(CorridorRect(parent, dir))) return false; + + Rect area = RoomRect(lift); + float m = Mathf.Min(margin, corridorLength); + area.xMin -= m; area.yMin -= m; area.xMax += m; area.yMax += m; + return IsAreaFree(area, parent); +} + +// Удаляет случайную тупиковую комнату вместе с её коридором +bool PruneRandomLeaf() +{ + var parentsSet = new HashSet(_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]; + + LevelManager.Rooms.Remove(leaf); + if (_corridorOf.TryGetValue(leaf, out var cor)) + { + LevelManager.Rooms.Remove(cor); + _corridorOf.Remove(leaf); + } + p.exits.Remove(dir); + _depth.Remove(leaf); + _parent.Remove(leaf); + _curRooms--; + return true; +} + +// Позиция комнаты заданного размера относительно точки входа +private static void PlaceByEntry(Vector2 e, ExitsInRoomType dir, Room n) +{ + switch (dir) + { + case ExitsInRoomType.XAxisP: n.Position = new Vector2(e.x, e.y - n.Width / 2f); break; + case ExitsInRoomType.XAxisN: n.Position = new Vector2(e.x - n.Length, e.y - n.Width / 2f); break; + case ExitsInRoomType.ZAxisP: n.Position = new Vector2(e.x - n.Length / 2f, e.y); break; + default: n.Position = new Vector2(e.x - n.Length / 2f, e.y - n.Width); break; + } +} + + private bool InBounds(Room r) => + r.Position.x >= 0 && r.Position.y >= 0 && + r.Position.x + r.Length <= lengthFloor && + r.Position.y + r.Width <= lengthFloor; + +// Комнаты проверяются с зазором, коридоры без него (иначе зазор заденет собственный коридор лифта) + void GenerateFloor() { - LevelManager.Rooms.Clear(); - _depth.Clear();// статический список мог остаться с прошлого запуска - _curRooms = 0; + const int maxAttempts = 50; + bool ok = false; - CreateStartRoom(); - - if (!PlaceElevator()) + for (int i = 0; i < maxAttempts && !ok; i++) { - GenerateFloor(); + LevelManager.Rooms.Clear(); + _depth.Clear(); + _parent.Clear(); + _corridorOf.Clear(); + _curRooms = 0; + + CreateStartRoom(); + ok = PlaceElevator(); } + if (!ok) Debug.LogWarning("Лифт не удалось разместить, этаж собран без него"); + Instantiate(GameObjectA, new Vector3(0, -1, 0), Quaternion.identity); foreach (var room in levelManager.GetRooms()) @@ -243,7 +360,8 @@ namespace _Project.Scripts.LevelGenerating // Всё хорошо - фиксируем levelManager.AddRoom(newRoom); _depth[newRoom] = _depth[prevRoom] + 1; - CreateCorridor(prevRoom, dir); + _parent[newRoom] = (prevRoom, dir); + _corridorOf[newRoom] = CreateCorridor(prevRoom, dir); // вместо отдельного CreateCorridor(...) _curRooms++; if (!prevRoom.exits.Contains(dir)) prevRoom.exits.Add(dir);