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

384 lines
16 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 System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;
namespace _Project.Scripts.LevelGenerating
{
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 _minDepthLiftRoom;
[SerializeField] private GameObject GameObjectA;
[SerializeField] private GameObject GameObjectB;
[Header("Генерация")] [SerializeField] private int maxRooms = 15;
[SerializeField, Range(0f, 1f)] private float branchChance = 0.7f;
[SerializeField] private float roomMargin = 2f; // зазор между комнатами
private int _curRooms;
private readonly Dictionary<Room, int> _depth = new Dictionary<Room, int>();
private LevelManager levelManager;
private static readonly ExitsInRoomType[] AllDirs =
{
ExitsInRoomType.XAxisP, ExitsInRoomType.XAxisN,
ExitsInRoomType.ZAxisP, ExitsInRoomType.ZAxisN
};
void Start()
{
levelManager = LevelManager.Instance;
GenerateFloor();
}
bool PlaceElevator()
{
var start = levelManager.GetRooms().First(r => r.rt == RoomType.Start);
// максимум шагов, при равенстве выбираем самую дальнюю от старта по прямой
var best = _depth
.OrderByDescending(p => p.Value)
.ThenByDescending(p => (p.Key.Position - start.Position).sqrMagnitude)
.First();
if (best.Key == start || best.Value <= _minDepthLiftRoom)
{
return false;
}
best.Key.rt = RoomType.Lift; // добавь значение в enum RoomType
best.Key.Length = lengthLiftRoom;
best.Key.Width = widthLiftRoom;
best.Key.exits.Clear();
return true;
}
void GenerateFloor()
{
LevelManager.Rooms.Clear();
_depth.Clear();// статический список мог остаться с прошлого запуска
_curRooms = 0;
CreateStartRoom();
if (!PlaceElevator())
{
GenerateFloor();
}
Instantiate(GameObjectA, new Vector3(0, -1, 0), Quaternion.identity);
foreach (var room in levelManager.GetRooms())
{
float x = room.Position.x;
float z = room.Position.y; // Vector2.y -> Z в 3D
float length = room.Length;
float width = room.Width;
Instantiate(GameObjectB, new Vector3(x, 0, z), Quaternion.identity);
Instantiate(GameObjectB, new Vector3(x + length, 0, z), Quaternion.identity);
Instantiate(GameObjectB, new Vector3(x + length, 0, z + width), Quaternion.identity);
Instantiate(GameObjectB, new Vector3(x, 0, z + width), Quaternion.identity);
}
}
// ---------- Вспомогательные ----------
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 Rect RoomRect(Room r) => new Rect(r.Position.x, r.Position.y, r.Length, r.Width);
// Прямоугольник коридора, ведущего из prev в направлении dir
private Rect CorridorRect(Room prev, ExitsInRoomType dir)
{
switch (dir)
{
case ExitsInRoomType.XAxisP:
return new Rect(prev.Position.x + prev.Length,
prev.Position.y + (prev.Width - corridorWigth) / 2f,
corridorLength, corridorWigth);
case ExitsInRoomType.XAxisN:
return new Rect(prev.Position.x - corridorLength,
prev.Position.y + (prev.Width - corridorWigth) / 2f,
corridorLength, corridorWigth);
case ExitsInRoomType.ZAxisP:
return new Rect(prev.Position.x + (prev.Length - corridorWigth) / 2f,
prev.Position.y + prev.Width,
corridorWigth, corridorLength);
default: // ZAxisN
return new Rect(prev.Position.x + (prev.Length - corridorWigth) / 2f,
prev.Position.y - corridorLength,
corridorWigth, corridorLength);
}
}
// Центр стены новой комнаты, куда упирается коридор
private Vector2 EntryPoint(Room prev, ExitsInRoomType dir)
{
switch (dir)
{
case ExitsInRoomType.XAxisP:
return new Vector2(prev.Position.x + prev.Length + corridorLength,
prev.Position.y + prev.Width / 2f);
case ExitsInRoomType.XAxisN:
return new Vector2(prev.Position.x - corridorLength,
prev.Position.y + prev.Width / 2f);
case ExitsInRoomType.ZAxisP:
return new Vector2(prev.Position.x + prev.Length / 2f,
prev.Position.y + prev.Width + corridorLength);
default:
return new Vector2(prev.Position.x + prev.Length / 2f,
prev.Position.y - corridorLength);
}
}
private bool IsAreaFree(Rect rect, Room ignore = null)
{
foreach (var other in levelManager.GetRooms())
{
if (other == ignore) continue;
if (rect.Overlaps(RoomRect(other))) return false;
}
return true;
}
// ---------- Коридор ----------
Room CreateCorridor(Room prev, ExitsInRoomType dir)
{
Rect r = CorridorRect(prev, dir);
Room corridor = new Room(r.position,
Mathf.RoundToInt(r.width), Mathf.RoundToInt(r.height),
RoomType.Corridor, false,
ToEntrance(dir), new List<ExitsInRoomType> { dir });
LevelManager.Rooms.Add(corridor);
return corridor;
}
private bool FitRoom(Room prev, Room n, ExitsInRoomType dir, out bool shrunk)
{
Vector2 e = EntryPoint(prev, dir);
int origLen = n.Length, origWid = n.Width;
switch (dir)
{
case ExitsInRoomType.XAxisP:
case ExitsInRoomType.XAxisN:
{
float maxAlong = dir == ExitsInRoomType.XAxisP ? lengthFloor - e.x : e.x;
float maxCross = 2f * Mathf.Min(e.y, lengthFloor - e.y);
n.Length = Mathf.Min(n.Length, Mathf.FloorToInt(maxAlong));
n.Width = Mathf.Min(n.Width, Mathf.FloorToInt(maxCross));
float x = dir == ExitsInRoomType.XAxisP ? e.x : e.x - n.Length;
n.Position = new Vector2(x, e.y - n.Width / 2f);
break;
}
default: // Z
{
float maxAlong = dir == ExitsInRoomType.ZAxisP ? lengthFloor - e.y : e.y;
float maxCross = 2f * Mathf.Min(e.x, lengthFloor - e.x);
n.Width = Mathf.Min(n.Width, Mathf.FloorToInt(maxAlong));
n.Length = Mathf.Min(n.Length, Mathf.FloorToInt(maxCross));
float y = dir == ExitsInRoomType.ZAxisP ? e.y : e.y - n.Width;
n.Position = new Vector2(e.x - n.Length / 2f, y);
break;
}
}
shrunk = n.Length != origLen || n.Width != origWid;
return n.Length >= lengthWallMin && n.Width >= lengthWallMin;
}
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.isWallable = shrunk;
// Пересечения: комната (с зазором, без prevRoom) и коридор к ней
Rect roomArea = RoomRect(newRoom);
float m = Mathf.Min(roomMargin, corridorLength);
roomArea.xMin -= m; roomArea.yMin -= m;
roomArea.xMax += m; roomArea.yMax += m;
if (!IsAreaFree(roomArea, prevRoom)) return false;
if (!IsAreaFree(CorridorRect(prevRoom, dir))) return false;
// Всё хорошо - фиксируем
levelManager.AddRoom(newRoom);
_depth[newRoom] = _depth[prevRoom] + 1;
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);
Room room = new Room();
room.Length = a;
room.Width = b;
return room;
}
// ---------- Стартовая комната ----------
void CreateStartRoom()
{
float randValue = Random.value;
int xR, zR;
ExitsInRoomType firstDir;
Room room = new Room(lengthStartRoom, lengthStartRoom, RoomType.Start);
room.exits = new List<ExitsInRoomType>();
room.entrance = EntranceToRoomType.Null;
if (randValue < 0.25f) // 1 квадрант
{
xR = Random.Range(0, lengthFloor / 2 - lengthStartRoom);
if (xR + lengthStartRoom <= lengthFloor / 4)
{
firstDir = ExitsInRoomType.XAxisP;
zR = Random.Range(0, lengthFloor / 2 - lengthStartRoom);
}
else
{
firstDir = 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)
{
firstDir = ExitsInRoomType.ZAxisP;
zR = Random.Range(0, lengthFloor / 4 - lengthStartRoom);
}
else
{
firstDir = ExitsInRoomType.XAxisN;
zR = Random.Range(0, lengthFloor / 2 - lengthStartRoom);
}
}
else if (randValue < 0.75f) // 3 квадрант
{
xR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom);
if (xR + lengthStartRoom <= lengthFloor * 3 / 4)
{
firstDir = ExitsInRoomType.ZAxisN;
zR = Random.Range(lengthFloor * 3 / 4, lengthFloor - lengthStartRoom);
}
else
{
firstDir = ExitsInRoomType.XAxisN;
zR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom);
}
}
else // 4 квадрант
{
xR = Random.Range(0, lengthFloor / 2 - lengthStartRoom);
if (xR + lengthStartRoom <= lengthFloor / 4)
{
firstDir = ExitsInRoomType.XAxisP;
zR = Random.Range(lengthFloor / 2, lengthFloor - lengthStartRoom);
}
else
{
firstDir = ExitsInRoomType.ZAxisN;
zR = Random.Range(lengthFloor * 3 / 4, lengthFloor - lengthStartRoom);
}
}
room.Position = new Vector2(xR, zR);
LevelManager.Rooms.Add(room);
_depth[room] = 0;
_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()
{
var rooms = LevelManager.Rooms;
if (rooms == null) return;
foreach (var r in 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;
default:
Gizmos.color = Color.cyan;
break;
}
// Gizmos.color = r.rt == RoomType.Start ? Color.green
// : r.rt == RoomType.Corridor ? Color.yellow : Color.cyan;
Vector3 c = new Vector3(r.Position.x + r.Length / 2f, 0, r.Position.y + r.Width / 2f);
Gizmos.DrawWireCube(c, new Vector3(r.Length, 0.1f, r.Width));
}
}
}
}