Files
2026-07-07 21:50:11 +02:00

536 lines
21 KiB
C#
Raw Permalink 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 UnityEngine;
/// <summary>
/// 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
/// </summary>
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<Room> rooms = new List<Room>();
// 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 ──────────────────────────────────────────────────────────
/// <summary>Vygeneruje nový dungeon (odstraní předchozí).</summary>
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<Room> expandQueue = new Queue<Room>();
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<CombineInstance>();
var floorInstances = new List<CombineInstance>();
var doorInstances = new List<CombineInstance>();
// 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<CombineInstance> 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<MeshFilter>();
mf.sharedMesh = combined;
var mr = go.AddComponent<MeshRenderer>();
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<MeshCollider>();
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<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;
// addNeighbour(int) v původním kódu blokovalo směr zachováváme jako alias:
public Dictionary<int, bool> getPossibleDirections() => possibleNeighbourDir;
public bool canExpand() =>
possibleNeighbourDir[0] || possibleNeighbourDir[1] ||
possibleNeighbourDir[2] || possibleNeighbourDir[3];
}
}