516 lines
20 KiB
C#
516 lines
20 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
public class MapGenManager : MonoBehaviour
|
||
{
|
||
[Header("Generátor")]
|
||
[Tooltip("Velikost jedné buňky matice v Unity units.")]
|
||
public float cellSize = 1f;
|
||
|
||
[Tooltip("Výška zdi.")]
|
||
public float wallHeight = 1f;
|
||
|
||
[Tooltip("Počet místností (rozsah).")]
|
||
public int roomCountMin = 10;
|
||
public int roomCountMax = 20;
|
||
|
||
[Tooltip("Maximální velikost místnosti v buňkách (rozsah).")]
|
||
public int maxRoomSizeMin = 14;
|
||
public int maxRoomSizeMax = 20;
|
||
|
||
[Header("Materiály")]
|
||
public Material wallMaterial;
|
||
public Material floorMaterial;
|
||
public Material doorMaterial;
|
||
|
||
[Header("Hráč")]
|
||
[Tooltip("Prefab hráče – bude spawnut uprostřed první místnosti.")]
|
||
public GameObject playerPrefab;
|
||
|
||
[Header("Chování")]
|
||
public bool generateOnStart = true;
|
||
|
||
private int[][] genMatrix;
|
||
private int roomCount;
|
||
private int maxRoomSize;
|
||
|
||
private int MATRIX_SIZE_X = 10;
|
||
private int MATRIX_SIZE_Y = 10;
|
||
private const int ROOM_PADDING = 1;
|
||
|
||
// Šířka chodby v buňkách (1 = původní, 3 = o jeden blok víc na každou stranu)
|
||
private const int CORRIDOR_WIDTH = 3;
|
||
|
||
private System.Random rng = new System.Random();
|
||
private List<Room> rooms = new List<Room>();
|
||
|
||
private GameObject wallsParent;
|
||
private GameObject floorParent;
|
||
private GameObject doorsParent;
|
||
|
||
private const int TILE_EMPTY = 0;
|
||
private const int TILE_WALL = 1;
|
||
private const int TILE_DOOR = 2;
|
||
|
||
private void Start()
|
||
{
|
||
if (generateOnStart)
|
||
Generate();
|
||
}
|
||
|
||
public void Generate()
|
||
{
|
||
ClearPrevious();
|
||
RunGenerator();
|
||
ConnectRooms();
|
||
BuildMeshes();
|
||
SpawnPlayer();
|
||
}
|
||
|
||
private void SpawnPlayer()
|
||
{
|
||
if (playerPrefab == null || rooms.Count == 0) return;
|
||
|
||
Room spawnRoom = rooms[0];
|
||
float centerCol = (spawnRoom.start_x + spawnRoom.end_x) / 2f;
|
||
float centerRow = (spawnRoom.start_y + spawnRoom.end_y) / 2f;
|
||
|
||
Vector3 spawnPos = new Vector3(centerCol * cellSize, 1f, centerRow * cellSize);
|
||
GameObject player = Instantiate(playerPrefab, spawnPos, Quaternion.identity);
|
||
|
||
Transform inner = player.transform.Find("Player");
|
||
if (inner != null) inner.tag = "Player";
|
||
}
|
||
|
||
private void ClearPrevious()
|
||
{
|
||
if (wallsParent != null) Destroy(wallsParent);
|
||
if (floorParent != null) Destroy(floorParent);
|
||
if (doorsParent != null) Destroy(doorsParent);
|
||
|
||
wallsParent = new GameObject("Walls");
|
||
floorParent = new GameObject("Floor");
|
||
doorsParent = new GameObject("Doors");
|
||
|
||
wallsParent.transform.SetParent(transform);
|
||
floorParent.transform.SetParent(transform);
|
||
doorsParent.transform.SetParent(transform);
|
||
|
||
MATRIX_SIZE_X = 10;
|
||
MATRIX_SIZE_Y = 10;
|
||
rooms.Clear();
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════════════════
|
||
// GENERÁTOR
|
||
// ══════════════════════════════════════════════════════════════════════════
|
||
|
||
private void RunGenerator()
|
||
{
|
||
roomCount = rng.Next(roomCountMin, roomCountMax);
|
||
maxRoomSize = rng.Next(maxRoomSizeMin, maxRoomSizeMax);
|
||
|
||
genMatrix = new int[MATRIX_SIZE_Y][];
|
||
for (int i = 0; i < MATRIX_SIZE_Y; i++)
|
||
genMatrix[i] = new int[MATRIX_SIZE_X];
|
||
|
||
// FIX: zajistíme dostatek místa pro první místnost PŘED jejím vykreslením,
|
||
// jinak by GenerateRoomMatrix ořezal její rozměry na počáteční 10×10.
|
||
EnsureMatrixSize(maxRoomSize + ROOM_PADDING + 1, maxRoomSize + ROOM_PADDING + 1);
|
||
|
||
Room firstRoom = CreateAndCarveRoom(0, 0);
|
||
firstRoom.possibleNeighbourDir[2] = false; // doleva ven z mapy
|
||
firstRoom.possibleNeighbourDir[3] = false; // nahoru ven z mapy
|
||
rooms.Add(firstRoom);
|
||
|
||
Queue<Room> expandQueue = new Queue<Room>();
|
||
expandQueue.Enqueue(firstRoom);
|
||
|
||
int safety = 0, maxSafety = roomCount * 50;
|
||
|
||
while (rooms.Count < roomCount && expandQueue.Count > 0 && safety < maxSafety)
|
||
{
|
||
safety++;
|
||
Room current = expandQueue.Dequeue();
|
||
if (!current.canExpand()) continue;
|
||
|
||
var dirKeys = new List<int>(current.getPossibleDirections().Keys);
|
||
foreach (int dirKey in dirKeys)
|
||
{
|
||
if (rooms.Count >= roomCount) break;
|
||
Room newRoom = TryGrowNeighbour(current, dirKey);
|
||
if (newRoom != null)
|
||
{
|
||
rooms.Add(newRoom);
|
||
current.addNeighbour(newRoom);
|
||
newRoom.addNeighbour(current);
|
||
expandQueue.Enqueue(newRoom);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void ConnectRooms()
|
||
{
|
||
var connectedPairs = new HashSet<(int, int)>();
|
||
foreach (Room roomA in rooms)
|
||
{
|
||
foreach (Room roomB in roomA.getNeighbours())
|
||
{
|
||
int ha = roomA.GetHashCode(), hb = roomB.GetHashCode();
|
||
var pair = ha < hb ? (ha, hb) : (hb, ha);
|
||
if (connectedPairs.Contains(pair)) continue;
|
||
connectedPairs.Add(pair);
|
||
CarveCorridor(roomA, roomB);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void CarveCorridor(Room a, Room b)
|
||
{
|
||
int half = CORRIDOR_WIDTH / 2; // pro width=3 → half=1
|
||
|
||
if (a.end_x < b.start_x || b.end_x < a.start_x)
|
||
{
|
||
// Horizontální chodba (místnosti vedle sebe v ose X)
|
||
Room left = a.end_x < b.start_x ? a : b;
|
||
Room right = left == a ? b : a;
|
||
|
||
// Vnitřní překryv (bez krajních zdí místností)
|
||
int overlapStart = Mathf.Max(left.start_y + 1, right.start_y + 1);
|
||
int overlapEnd = Mathf.Min(left.end_y - 1, right.end_y - 1);
|
||
if (overlapEnd - overlapStart < CORRIDOR_WIDTH - 1) return;
|
||
|
||
// Střed chodby – náhodně v bezpečné zóně tak, aby se chodba vešla
|
||
int centerMin = overlapStart + half;
|
||
int centerMax = overlapEnd - half;
|
||
if (centerMin > centerMax) return;
|
||
int centerY = rng.Next(centerMin, centerMax + 1);
|
||
|
||
for (int dy = -half; dy <= half; dy++)
|
||
{
|
||
int y = centerY + dy;
|
||
// Vykopaný průchod od zdi levé místnosti po zeď pravé
|
||
for (int x = left.end_x; x <= right.start_x; x++)
|
||
SetTile(x, y, TILE_EMPTY);
|
||
|
||
// Dveře na hranici místností
|
||
SetTile(left.end_x, y, TILE_DOOR);
|
||
SetTile(right.start_x, y, TILE_DOOR);
|
||
}
|
||
|
||
// Boční zdi chodby (nad a pod ní), jen v mezeře mezi místnostmi
|
||
for (int x = left.end_x + 1; x < right.start_x; x++)
|
||
{
|
||
SafeSetWall(x, centerY - half - 1, left, right);
|
||
SafeSetWall(x, centerY + half + 1, left, right);
|
||
}
|
||
}
|
||
else if (a.end_y < b.start_y || b.end_y < a.start_y)
|
||
{
|
||
// Vertikální chodba (místnosti nad sebou v ose Y)
|
||
Room top = a.end_y < b.start_y ? a : b;
|
||
Room bottom = top == a ? b : a;
|
||
|
||
int overlapStart = Mathf.Max(top.start_x + 1, bottom.start_x + 1);
|
||
int overlapEnd = Mathf.Min(top.end_x - 1, bottom.end_x - 1);
|
||
if (overlapEnd - overlapStart < CORRIDOR_WIDTH - 1) return;
|
||
|
||
int centerMin = overlapStart + half;
|
||
int centerMax = overlapEnd - half;
|
||
if (centerMin > centerMax) return;
|
||
int centerX = rng.Next(centerMin, centerMax + 1);
|
||
|
||
for (int dx = -half; dx <= half; dx++)
|
||
{
|
||
int x = centerX + dx;
|
||
for (int y = top.end_y; y <= bottom.start_y; y++)
|
||
SetTile(x, y, TILE_EMPTY);
|
||
|
||
SetTile(x, top.end_y, TILE_DOOR);
|
||
SetTile(x, bottom.start_y, TILE_DOOR);
|
||
}
|
||
|
||
for (int y = top.end_y + 1; y < bottom.start_y; y++)
|
||
{
|
||
SafeSetWall(centerX - half - 1, y, top, bottom);
|
||
SafeSetWall(centerX + half + 1, y, top, bottom);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void SafeSetWall(int x, int y, Room r1, Room r2)
|
||
{
|
||
if (x < 0 || y < 0 || y >= genMatrix.Length || x >= genMatrix[y].Length) return;
|
||
bool inside1 = x > r1.start_x && x < r1.end_x && y > r1.start_y && y < r1.end_y;
|
||
bool inside2 = x > r2.start_x && x < r2.end_x && y > r2.start_y && y < r2.end_y;
|
||
if (!inside1 && !inside2) genMatrix[y][x] = TILE_WALL;
|
||
}
|
||
|
||
private void SetTile(int x, int y, int value)
|
||
{
|
||
if (y < 0 || y >= genMatrix.Length || x < 0 || x >= genMatrix[y].Length) return;
|
||
genMatrix[y][x] = value;
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════════════════
|
||
// MESH BUILDING
|
||
// ══════════════════════════════════════════════════════════════════════════
|
||
|
||
private void BuildMeshes()
|
||
{
|
||
var wallInstances = new List<CombineInstance>();
|
||
var floorInstances = new List<CombineInstance>();
|
||
var doorInstances = new List<CombineInstance>();
|
||
|
||
Mesh cubeMesh = CreateCubeMesh(cellSize, wallHeight, cellSize);
|
||
Mesh floorMesh = CreateCubeMesh(cellSize, cellSize * 0.1f, cellSize);
|
||
|
||
float wallY = wallHeight / 2f;
|
||
float floorY = -(cellSize * 0.1f) / 2f;
|
||
float doorY = cellSize * 0.05f;
|
||
|
||
for (int row = 0; row < genMatrix.Length; row++)
|
||
{
|
||
for (int col = 0; col < genMatrix[row].Length; col++)
|
||
{
|
||
int tile = genMatrix[row][col];
|
||
float worldX = col * cellSize;
|
||
float worldZ = row * cellSize;
|
||
|
||
switch (tile)
|
||
{
|
||
case TILE_WALL:
|
||
wallInstances.Add(MakeCombineInstance(cubeMesh,
|
||
new Vector3(worldX, wallY, worldZ)));
|
||
break;
|
||
case TILE_DOOR:
|
||
floorInstances.Add(MakeCombineInstance(floorMesh,
|
||
new Vector3(worldX, floorY, worldZ)));
|
||
doorInstances.Add(MakeCombineInstance(floorMesh,
|
||
new Vector3(worldX, doorY, worldZ)));
|
||
break;
|
||
case TILE_EMPTY:
|
||
floorInstances.Add(MakeCombineInstance(floorMesh,
|
||
new Vector3(worldX, floorY, worldZ)));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (wallInstances.Count > 0) BuildCombinedObject(wallInstances, cubeMesh, wallsParent, wallMaterial, "WallMesh", addCollider: true);
|
||
if (floorInstances.Count > 0) BuildCombinedObject(floorInstances, floorMesh, floorParent, floorMaterial, "FloorMesh", addCollider: true);
|
||
if (doorInstances.Count > 0) BuildCombinedObject(doorInstances, floorMesh, doorsParent, doorMaterial, "DoorMesh", addCollider: false);
|
||
|
||
Destroy(cubeMesh);
|
||
Destroy(floorMesh);
|
||
}
|
||
|
||
private CombineInstance MakeCombineInstance(Mesh mesh, Vector3 position)
|
||
=> new CombineInstance { mesh = mesh, transform = Matrix4x4.Translate(position) };
|
||
|
||
private void BuildCombinedObject(List<CombineInstance> instances, Mesh templateMesh,
|
||
GameObject parent, Material mat, string meshName, bool addCollider)
|
||
{
|
||
Mesh combined = new Mesh();
|
||
combined.name = meshName;
|
||
combined.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
|
||
combined.CombineMeshes(instances.ToArray(), true, true);
|
||
combined.RecalculateNormals();
|
||
combined.RecalculateBounds();
|
||
|
||
var go = new GameObject(meshName);
|
||
go.transform.SetParent(parent.transform);
|
||
go.AddComponent<MeshFilter>().sharedMesh = combined;
|
||
go.AddComponent<MeshRenderer>().sharedMaterial =
|
||
mat != null ? mat : new Material(Shader.Find("Standard"));
|
||
|
||
if (addCollider)
|
||
go.AddComponent<MeshCollider>().sharedMesh = combined;
|
||
}
|
||
|
||
private Mesh CreateCubeMesh(float w, float h, float d)
|
||
{
|
||
var mesh = new Mesh();
|
||
float hw = w / 2f, hh = h / 2f, hd = d / 2f;
|
||
|
||
mesh.vertices = new Vector3[]
|
||
{
|
||
new(-hw,-hh, hd), new( hw,-hh, hd), new( hw, hh, hd), new(-hw, hh, hd),
|
||
new( hw,-hh,-hd), new(-hw,-hh,-hd), new(-hw, hh,-hd), new( hw, hh,-hd),
|
||
new(-hw,-hh,-hd), new(-hw,-hh, hd), new(-hw, hh, hd), new(-hw, hh,-hd),
|
||
new( hw,-hh, hd), new( hw,-hh,-hd), new( hw, hh,-hd), new( hw, hh, hd),
|
||
new(-hw, hh, hd), new( hw, hh, hd), new( hw, hh,-hd), new(-hw, hh,-hd),
|
||
new(-hw,-hh,-hd), new( hw,-hh,-hd), new( hw,-hh, hd), new(-hw,-hh, hd),
|
||
};
|
||
// Winding order → normály dovnitř (hráč vidí vnitřní povrch zdí)
|
||
mesh.triangles = new int[]
|
||
{
|
||
0, 1, 2, 0, 2, 3,
|
||
4, 5, 6, 4, 6, 7,
|
||
8, 9,10, 8,10,11,
|
||
12,13,14, 12,14,15,
|
||
16,17,18, 16,18,19,
|
||
20,21,22, 20,22,23,
|
||
};
|
||
mesh.uv = new Vector2[]
|
||
{
|
||
new(0,0),new(1,0),new(1,1),new(0,1),
|
||
new(0,0),new(1,0),new(1,1),new(0,1),
|
||
new(0,0),new(1,0),new(1,1),new(0,1),
|
||
new(0,0),new(1,0),new(1,1),new(0,1),
|
||
new(0,0),new(1,0),new(1,1),new(0,1),
|
||
new(0,0),new(1,0),new(1,1),new(0,1),
|
||
};
|
||
mesh.RecalculateNormals();
|
||
return mesh;
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════════════════
|
||
// GENERÁTOR – pomocné metody
|
||
// ══════════════════════════════════════════════════════════════════════════
|
||
|
||
private int[] GenerateRoomMatrix(int start_x, int start_y)
|
||
{
|
||
int x_stop = start_x + rng.Next(maxRoomSize / 2, maxRoomSize + 1);
|
||
int y_stop = start_y + rng.Next(maxRoomSize / 2, maxRoomSize + 1);
|
||
|
||
if (x_stop >= MATRIX_SIZE_X) x_stop = MATRIX_SIZE_X - 1;
|
||
if (y_stop >= MATRIX_SIZE_Y) y_stop = MATRIX_SIZE_Y - 1;
|
||
|
||
for (int y = start_y; y <= y_stop; y++)
|
||
for (int x = start_x; x <= x_stop; x++)
|
||
genMatrix[y][x] = (x == start_x || x == x_stop || y == start_y || y == y_stop)
|
||
? TILE_WALL : TILE_EMPTY;
|
||
|
||
return new int[] { x_stop, y_stop };
|
||
}
|
||
|
||
private Room CreateAndCarveRoom(int sx, int sy)
|
||
{
|
||
int[] end = GenerateRoomMatrix(sx, sy);
|
||
return new Room(sx, sy, end[0], end[1]);
|
||
}
|
||
|
||
private bool HasValidOverlapWithParent(Room parent, Room child, int direction)
|
||
{
|
||
if (direction == 0 || direction == 2)
|
||
return (Mathf.Min(parent.end_y, child.end_y) - Mathf.Max(parent.start_y, child.start_y)) >= CORRIDOR_WIDTH + 2;
|
||
return (Mathf.Min(parent.end_x, child.end_x) - Mathf.Max(parent.start_x, child.start_x)) >= CORRIDOR_WIDTH + 2;
|
||
}
|
||
|
||
private Room TryGrowNeighbour(Room from, int direction)
|
||
{
|
||
int est = maxRoomSize, minOverlap = CORRIDOR_WIDTH + 2;
|
||
int nsx, nsy;
|
||
|
||
switch (direction)
|
||
{
|
||
case 0:
|
||
nsx = from.end_x + 1 + ROOM_PADDING;
|
||
nsy = rng.Next(from.start_y - est + minOverlap, from.end_y - minOverlap + 2); break;
|
||
case 1:
|
||
nsx = rng.Next(from.start_x - est + minOverlap, from.end_x - minOverlap + 2);
|
||
nsy = from.end_y + 1 + ROOM_PADDING; break;
|
||
case 2:
|
||
nsx = from.start_x - 1 - ROOM_PADDING - est;
|
||
nsy = rng.Next(from.start_y - est + minOverlap, from.end_y - minOverlap + 2); break;
|
||
case 3:
|
||
nsx = rng.Next(from.start_x - est + minOverlap, from.end_x - minOverlap + 2);
|
||
nsy = from.start_y - 1 - ROOM_PADDING - est; break;
|
||
default: return null;
|
||
}
|
||
|
||
if (nsx < 0) nsx = 0;
|
||
if (nsy < 0) nsy = 0;
|
||
|
||
EnsureMatrixSize(nsx + maxRoomSize + ROOM_PADDING + 1,
|
||
nsy + maxRoomSize + ROOM_PADDING + 1);
|
||
|
||
if (WouldOverlapAny(nsx, nsy, nsx + maxRoomSize, nsy + maxRoomSize))
|
||
return null;
|
||
|
||
Room newRoom = CreateAndCarveRoom(nsx, nsy);
|
||
|
||
if (OverlapsExistingRoom(newRoom) || !HasValidOverlapWithParent(from, newRoom, direction))
|
||
{
|
||
EraseRoom(newRoom);
|
||
return null;
|
||
}
|
||
|
||
from.blockDirection(direction);
|
||
return newRoom;
|
||
}
|
||
|
||
private void EraseRoom(Room room)
|
||
{
|
||
for (int y = room.start_y; y <= room.end_y; y++)
|
||
for (int x = room.start_x; x <= room.end_x; x++)
|
||
if (y >= 0 && y < genMatrix.Length && x >= 0 && x < genMatrix[y].Length)
|
||
genMatrix[y][x] = TILE_EMPTY;
|
||
}
|
||
|
||
private bool WouldOverlapAny(int sx, int sy, int ex, int ey)
|
||
{
|
||
foreach (Room r in rooms)
|
||
if (RectsOverlap(sx - ROOM_PADDING, sy - ROOM_PADDING, ex + ROOM_PADDING, ey + ROOM_PADDING,
|
||
r.start_x, r.start_y, r.end_x, r.end_y)) return true;
|
||
return false;
|
||
}
|
||
|
||
private bool OverlapsExistingRoom(Room candidate)
|
||
{
|
||
foreach (Room r in rooms)
|
||
{
|
||
if (r == candidate) continue;
|
||
if (RectsOverlap(candidate.start_x - ROOM_PADDING, candidate.start_y - ROOM_PADDING,
|
||
candidate.end_x + ROOM_PADDING, candidate.end_y + ROOM_PADDING,
|
||
r.start_x, r.start_y, r.end_x, r.end_y)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
private static bool RectsOverlap(int ax1, int ay1, int ax2, int ay2,
|
||
int bx1, int by1, int bx2, int by2)
|
||
=> ax1 <= bx2 && ax2 >= bx1 && ay1 <= by2 && ay2 >= by1;
|
||
|
||
private void EnsureMatrixSize(int reqW, int reqH)
|
||
{
|
||
bool resized = false;
|
||
while (reqW >= MATRIX_SIZE_X) { MATRIX_SIZE_X *= 2; resized = true; }
|
||
while (reqH >= MATRIX_SIZE_Y) { MATRIX_SIZE_Y *= 2; resized = true; }
|
||
if (resized) ResizeArray(ref genMatrix, MATRIX_SIZE_Y, MATRIX_SIZE_X);
|
||
}
|
||
|
||
private void ResizeArray(ref int[][] original, int newH, int newW)
|
||
{
|
||
System.Array.Resize(ref original, newH);
|
||
for (int i = 0; i < newH; i++)
|
||
{
|
||
if (original[i] == null) original[i] = new int[newW];
|
||
else System.Array.Resize(ref original[i], newW);
|
||
}
|
||
}
|
||
|
||
private class Room
|
||
{
|
||
public int start_x, start_y, end_x, end_y;
|
||
public List<Room> neighbours = new List<Room>();
|
||
public Dictionary<int, bool> possibleNeighbourDir;
|
||
|
||
public Room(int sx, int sy, int ex, int ey)
|
||
{
|
||
start_x = sx; start_y = sy; end_x = ex; end_y = ey;
|
||
possibleNeighbourDir = new Dictionary<int, bool>
|
||
{ {0,true},{1,true},{2,true},{3,true} };
|
||
}
|
||
|
||
public List<Room> getNeighbours() => neighbours;
|
||
public void addNeighbour(Room r) => neighbours.Add(r);
|
||
public void blockDirection(int d) => possibleNeighbourDir[d] = false;
|
||
public Dictionary<int, bool> getPossibleDirections() => possibleNeighbourDir;
|
||
public bool canExpand() =>
|
||
possibleNeighbourDir[0] || possibleNeighbourDir[1] ||
|
||
possibleNeighbourDir[2] || possibleNeighbourDir[3];
|
||
}
|
||
} |