using System.Collections.Generic; using UnityEngine; /// /// Dungeon generátor pro Unity 3D. /// Připojte tento skript na prázdný GameObject ve scéně. /// /// NASTAVENÍ V INSPEKTORU: /// Cell Size – velikost jedné buňky matice v Unity units (default 1) /// Wall Height – výška zdi (default 1) /// Wall Material – material pro zdi (může být null → default/pink) /// Floor Material– material pro podlahu (může být null) /// Generate On Start – zavolá Generate() automaticky při startu /// public class MapManager : 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("Chování")] public bool generateOnStart = true; // ── Interní stav ────────────────────────────────────────────────────────── 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; private System.Random rng = new System.Random(); private List rooms = new List(); // Rodičovské objekty pro přehlednost v hierarchii private GameObject wallsParent; private GameObject floorParent; private GameObject doorsParent; // TileType musí odpovídat hodnotám v matici private const int TILE_EMPTY = 0; private const int TILE_WALL = 1; private const int TILE_DOOR = 2; // ── Unity lifecycle ─────────────────────────────────────────────────────── private void Start() { if (generateOnStart) Generate(); } // ── Veřejné API ────────────────────────────────────────────────────────── /// Vygeneruje nový dungeon (odstraní předchozí). public void Generate() { ClearPrevious(); RunGenerator(); ConnectRooms(); BuildMeshes(); } // ── Čištění ────────────────────────────────────────────────────────────── 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(); } // ══════════════════════════════════════════════════════════════════════════ // LOGIKA GENERÁTORU (portováno z původního C# kódu) // ══════════════════════════════════════════════════════════════════════════ 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]; Room firstRoom = CreateAndCarveRoom(0, 0); firstRoom.possibleNeighbourDir[0] = false; firstRoom.possibleNeighbourDir[1] = false; rooms.Add(firstRoom); Queue expandQueue = new Queue(); expandQueue.Enqueue(firstRoom); int safety = 0; int maxSafety = roomCount * 50; while (rooms.Count < roomCount && expandQueue.Count > 0 && safety < maxSafety) { safety++; Room current = expandQueue.Dequeue(); if (!current.canExpand()) continue; foreach (var dir in current.getPossibleDirections()) { if (rooms.Count >= roomCount) break; Room newRoom = TryGrowNeighbour(current, dir.Key); 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(); int 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) { // Horizontální sousedství if (a.end_x < b.start_x || b.end_x < a.start_x) { Room left = a.end_x < b.start_x ? a : b; Room right = left == a ? b : a; int overlapStartY = Mathf.Max(left.start_y, right.start_y); int overlapEndY = Mathf.Min(left.end_y, right.end_y); if (overlapStartY > overlapEndY) return; int targetY = (overlapStartY == overlapEndY) ? overlapStartY : rng.Next(overlapStartY + 1, overlapEndY); for (int x = left.end_x; x <= right.start_x; x++) SetTile(x, targetY, TILE_EMPTY); SetTile(left.end_x, targetY, TILE_DOOR); SetTile(right.start_x, targetY, TILE_DOOR); for (int x = left.end_x + 1; x < right.start_x; x++) { SafeSetWall(x, targetY - 1, left, right); SafeSetWall(x, targetY + 1, left, right); } } // Vertikální sousedství else if (a.end_y < b.start_y || b.end_y < a.start_y) { Room top = a.end_y < b.start_y ? a : b; Room bottom = top == a ? b : a; int overlapStartX = Mathf.Max(top.start_x, bottom.start_x); int overlapEndX = Mathf.Min(top.end_x, bottom.end_x); if (overlapStartX > overlapEndX) return; int targetX = (overlapStartX == overlapEndX) ? overlapStartX : rng.Next(overlapStartX + 1, overlapEndX); for (int y = top.end_y; y <= bottom.start_y; y++) SetTile(targetX, y, TILE_EMPTY); SetTile(targetX, top.end_y, TILE_DOOR); SetTile(targetX, bottom.start_y, TILE_DOOR); for (int y = top.end_y + 1; y < bottom.start_y; y++) { SafeSetWall(targetX - 1, y, top, bottom); SafeSetWall(targetX + 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 // Klíčová optimalizace: místo N GameObject cubů se vše sloučí do 3 meshů // (zdi, podlaha, dveře) → 3 draw cally celkem bez ohledu na velikost dungeonu. // ══════════════════════════════════════════════════════════════════════════ private void BuildMeshes() { var wallInstances = new List(); var floorInstances = new List(); var doorInstances = new List(); // Sdílené primitivní meshe (vytvoříme je jednou a použijeme jako šablonu) Mesh cubeMesh = CreateCubeMesh(cellSize, wallHeight, cellSize); Mesh floorMesh = CreateCubeMesh(cellSize, cellSize * 0.1f, cellSize); for (int row = 0; row < genMatrix.Length; row++) { for (int col = 0; col < genMatrix[row].Length; col++) { int tile = genMatrix[row][col]; if (tile == TILE_EMPTY) continue; // Pozice v Unity: X = sloupec, Y = výška, Z = řádek Vector3 pos = new Vector3(col * cellSize, 0f, row * cellSize); if (tile == TILE_WALL) { wallInstances.Add(MakeCombineInstance(cubeMesh, pos)); } else if (tile == TILE_DOOR) { doorInstances.Add(MakeCombineInstance(floorMesh, pos + new Vector3(0f, wallHeight * 0.05f, 0f))); } } } // Podlaha: jedna plocha pod celou mapou pro každou neprázdnou buňku for (int row = 0; row < genMatrix.Length; row++) { for (int col = 0; col < genMatrix[row].Length; col++) { if (genMatrix[row][col] == TILE_EMPTY || genMatrix[row][col] == TILE_DOOR) { Vector3 pos = new Vector3(col * cellSize, -cellSize * 0.05f, row * cellSize); floorInstances.Add(MakeCombineInstance(floorMesh, pos)); } } } // Sestavení finálních objektů if (wallInstances.Count > 0) BuildCombinedObject(wallInstances, cubeMesh, wallsParent, wallMaterial, "WallMesh", true); if (floorInstances.Count > 0) BuildCombinedObject(floorInstances, floorMesh, floorParent, floorMaterial, "FloorMesh", false); if (doorInstances.Count > 0) BuildCombinedObject(doorInstances, floorMesh, doorsParent, doorMaterial, "DoorMesh", false); // Uvolníme dočasné meshe (nepotřebujeme je po sloučení) Destroy(cubeMesh); Destroy(floorMesh); } private CombineInstance MakeCombineInstance(Mesh mesh, Vector3 position) { return new CombineInstance { mesh = mesh, transform = Matrix4x4.Translate(position) }; } private void BuildCombinedObject(List instances, Mesh templateMesh, GameObject parent, Material mat, string meshName, bool addCollider) { Mesh combined = new Mesh(); combined.name = meshName; // IndexFormat.UInt32 umožní meshe s více než 65k vrcholy (velké dungeony) 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); var mf = go.AddComponent(); mf.sharedMesh = combined; var mr = go.AddComponent(); mr.sharedMaterial = mat != null ? mat : new Material(Shader.Find("Standard")); if (addCollider) { // MeshCollider na sloučeném zdi-meshi → fyzika funguje bez extra objektů var mc = go.AddComponent(); mc.sharedMesh = combined; } } // ── Pomocné: vytvoří jednoduchý kvádrový mesh o daných rozměrech ───────── 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[] { // Přední stěna new(-hw,-hh, hd), new( hw,-hh, hd), new( hw, hh, hd), new(-hw, hh, hd), // Zadní stěna new( hw,-hh,-hd), new(-hw,-hh,-hd), new(-hw, hh,-hd), new( hw, hh,-hd), // Levá stěna new(-hw,-hh,-hd), new(-hw,-hh, hd), new(-hw, hh, hd), new(-hw, hh,-hd), // Pravá stěna new( hw,-hh, hd), new( hw,-hh,-hd), new( hw, hh,-hd), new( hw, hh, hd), // Horní stěna new(-hw, hh, hd), new( hw, hh, hd), new( hw, hh,-hd), new(-hw, hh,-hd), // Spodní stěna new(-hw,-hh,-hd), new( hw,-hh,-hd), new( hw,-hh, hd), new(-hw,-hh, hd), }; mesh.triangles = new int[] { 0, 2, 1, 0, 3, 2, // přední 4, 6, 5, 4, 7, 6, // zadní 8,10, 9, 8,11,10, // levá 12,14,13, 12,15,14, // pravá 16,18,17, 16,19,18, // horní 20,22,21, 20,23,22, // spodní }; 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 – INTERNÍ TŘÍDY A METODY (beze změny z původního kódu) // ══════════════════════════════════════════════════════════════════════════ 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)) >= 3; } return (Mathf.Min(parent.end_x, child.end_x) - Mathf.Max(parent.start_x, child.start_x)) >= 3; } private Room TryGrowNeighbour(Room from, int direction) { int est = maxRoomSize, minOverlap = 3; 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); } } // ── Třída Room (vnořená, aby byl soubor self-contained) ────────────────── private class Room { public int start_x, start_y, end_x, end_y; public List neighbours = new List(); public Dictionary 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 { {0,true},{1,true},{2,true},{3,true} }; } public List getNeighbours() => neighbours; public void addNeighbour(Room r) => neighbours.Add(r); public void blockDirection(int d) => possibleNeighbourDir[d] = false; // addNeighbour(int) v původním kódu blokovalo směr – zachováváme jako alias: public Dictionary getPossibleDirections() => possibleNeighbourDir; public bool canExpand() => possibleNeighbourDir[0] || possibleNeighbourDir[1] || possibleNeighbourDir[2] || possibleNeighbourDir[3]; } }