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

516 lines
20 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;
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];
}
}