Feat: updated room generator

This commit is contained in:
Tomáš Pěnkava
2026-07-07 21:50:11 +02:00
parent e397d13f41
commit 9b7299304a
6 changed files with 1125 additions and 278 deletions
+24 -27
View File
@@ -128,8 +128,8 @@ GameObject:
serializedVersion: 6
m_Component:
- component: {fileID: 23489964}
- component: {fileID: 23489963}
- component: {fileID: 23489965}
- component: {fileID: 23489966}
m_Layer: 0
m_Name: Dungeon
m_TagString: Untagged
@@ -137,32 +137,6 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &23489963
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 23489962}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fba07e5a285f4d84f93955f44f02c76c, type: 3}
m_Name:
m_EditorClassIdentifier:
mapPrefab:
- {fileID: 819094401162878122, guid: 03f2147e5a186fc408b959faa2f97e86, type: 3}
- {fileID: 2783560673348224804, guid: b7111ccd107e56741a9790a50ab8e190, type: 3}
- {fileID: 7069902989848030098, guid: e8e0ba1faefe88f47b927a477b87ed22, type: 3}
- {fileID: 8524395191423973796, guid: 37b1623de3a4b8140bc4fd60f59c6329, type: 3}
- {fileID: 4362243528253867212, guid: 04cd18e800b8e854491a95529a91b3a1, type: 3}
- {fileID: 5061069654894918266, guid: 6ac586441811a834fbf148a96b779734, type: 3}
Player: {fileID: 6983871523237736218, guid: f0df263e5be65a041848d5a8bab85af1, type: 3}
CorridorStraight: {fileID: 8047827979703692770, guid: 1a5d554c0c76caf4195cae47e098b79d, type: 3}
CorridorStraightUnlit: {fileID: 2016417306107577256, guid: 92d9025262a022a499862d352c2724ee, type: 3}
DoorCorridor: {fileID: 5603417387143431118, guid: 9eccbf5a500a0d2429e6008a713a49fd, type: 3}
layout: {fileID: 11400000, guid: 8379cbdc96f21cc47a95cab23068d69a, type: 2}
useGlobalNavMesh: 1
RoomDistance: 40
--- !u!4 &23489964
Transform:
m_ObjectHideFlags: 0
@@ -210,6 +184,29 @@ MonoBehaviour:
m_MinRegionArea: 2
m_NavMeshData: {fileID: 0}
m_BuildHeightMesh: 0
--- !u!114 &23489966
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 23489962}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fba07e5a285f4d84f93955f44f02c76c, type: 3}
m_Name:
m_EditorClassIdentifier:
cellSize: 1
wallHeight: 5
roomCountMin: 12
roomCountMax: 16
maxRoomSizeMin: 28
maxRoomSizeMax: 40
wallMaterial: {fileID: 0}
floorMaterial: {fileID: 0}
doorMaterial: {fileID: 0}
playerPrefab: {fileID: 6983871523237736218, guid: f0df263e5be65a041848d5a8bab85af1, type: 3}
generateOnStart: 1
--- !u!1 &1862002347
GameObject:
m_ObjectHideFlags: 0
@@ -10,9 +10,9 @@ public class GameHandler : MonoBehaviour
void Awake()
{
mapGen = GetComponent<MapGenManager>();
//mapGen = GetComponent<MapGenManager>();
mapGen.OnGenerationComplete += HandleStart;
//mapGen.OnGenerationComplete += HandleStart;
}
void Start()
{
@@ -25,14 +25,14 @@ public class GameHandler : MonoBehaviour
}
private void HandleStart(MapGenManager map)
{
var Rooms = map.GridToRoom;
//private void HandleStart(MapGenManager map)
//{
// var Rooms = map.GridToRoom;
/*----- For now only open the doors -----*/
foreach (var room in mapGen.GridToRoom)
{
var rh = room.Value.GetComponent<RoomHandler>();
}
}
// /*----- For now only open the doors -----*/
// foreach (var room in mapGen.GridToRoom)
// {
// var rh = room.Value.GetComponent<RoomHandler>();
// }
//}
}
+453 -238
View File
@@ -1,301 +1,516 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.AI.Navigation;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class MapGenManager : MonoBehaviour
{
/* ------------------ INSPECTOR FIELDS ------------------ */
[Header("Room Prefabs")]
[SerializeField] private List<GameObject> mapPrefab = new();
[Header("Generátor")]
[Tooltip("Velikost jedné buňky matice v Unity units.")]
public float cellSize = 1f;
[Header("Player")]
[SerializeField] private GameObject Player;
[Tooltip("Výška zdi.")]
public float wallHeight = 1f;
[Header("Corridor Prefabs")]
[SerializeField] private GameObject CorridorStraight;
[SerializeField] private GameObject CorridorStraightUnlit;
[SerializeField] private GameObject DoorCorridor;
[Tooltip("Počet místností (rozsah).")]
public int roomCountMin = 10;
public int roomCountMax = 20;
[Header("Layout")]
[SerializeField] private MapLayout layout;
[Tooltip("Maximální velikost místnosti v buňkách (rozsah).")]
public int maxRoomSizeMin = 14;
public int maxRoomSizeMax = 20;
[Header("NavMesh Settings")]
[SerializeField] private bool useGlobalNavMesh = true;
private NavMeshSurface globalNavMeshSurface;
[Header("Materiály")]
public Material wallMaterial;
public Material floorMaterial;
public Material doorMaterial;
[Header("Generation Settings")]
[SerializeField] private int RoomDistance = 3;
[Header("Hráč")]
[Tooltip("Prefab hráče bude spawnut uprostřed první místnosti.")]
public GameObject playerPrefab;
private readonly Dictionary<Vector2Int, GameObject> gridToRoom = new();
public event Action<MapGenManager> OnGenerationComplete;
public IReadOnlyDictionary<Vector2Int, GameObject> GridToRoom => gridToRoom;
[Header("Chování")]
public bool generateOnStart = true;
void Start() => StartCoroutine(GenerateWithDelay());
private int[][] genMatrix;
private int roomCount;
private int maxRoomSize;
private IEnumerator GenerateWithDelay()
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()
{
yield return null; // Počkej jeden frame
GenerateFromLayout();
if (generateOnStart)
Generate();
}
private void GenerateFromLayout()
public void Generate()
{
if (layout == null || string.IsNullOrWhiteSpace(layout.grid))
{
Debug.LogError("Layout asset není přiřazený nebo je prázdný!");
return;
}
gridToRoom.Clear();
/* ----------- Create a spawn room ----------- */
GameObject spawnPrefab = mapPrefab[0];
Vector3 cellSize = GetPrefabXZ(spawnPrefab);
string[] lines = layout.grid.Split('\n')
.Select(l => l.TrimEnd('\r'))
.Where(l => !string.IsNullOrWhiteSpace(l))
.ToArray();
Vector2 first = GetFirstOrLastRoom(lines);
int bottomRow = (int)first.y;
int spawnX = (int)first.x;
Vector3 spawnPos = new Vector3(spawnX * (cellSize.x + RoomDistance), 0, 0);
GameObject spawnRoom = Instantiate(spawnPrefab, spawnPos, Quaternion.identity, transform);
gridToRoom[new Vector2Int(spawnX, 0)] = spawnRoom;
/* ----------- Spawn the player ----------- */
if (Player)
{
GameObject p = Instantiate(Player, spawnPos + new Vector3(0, 1, 3), Quaternion.identity, transform);
Transform inner = p.transform.Find("Player");
if (inner) inner.tag = "Player";
}
/* ----------- Build rooms ----------- */
BuildRooms(lines, bottomRow, cellSize);
// Použij coroutine pro dokončení generování
StartCoroutine(FinishGenerationAfterDelay());
ClearPrevious();
RunGenerator();
ConnectRooms();
BuildMeshes();
SpawnPlayer();
}
/// <summary>
/// Builds corridors and doors between rooms.
/// </summary>
private void BuildCorridors()
private void SpawnPlayer()
{
// Jediné dva směry, které musíme zkontrolovat z každé místnosti (right/up)
Vector2Int[] directions = { Vector2Int.right, Vector2Int.up };
if (playerPrefab == null || rooms.Count == 0) return;
// Délky prefebů podél X a Z osy
float straightLenX = CorridorStraight.GetComponent<PrefabSize>().prefabSize.x;
float straightLenZ = CorridorStraight.GetComponent<PrefabSize>().prefabSize.y;
float doorLenX = DoorCorridor.GetComponent<PrefabSize>().prefabSize.x;
float doorLenZ = DoorCorridor.GetComponent<PrefabSize>().prefabSize.y;
Room spawnRoom = rooms[0];
float centerCol = (spawnRoom.start_x + spawnRoom.end_x) / 2f;
float centerRow = (spawnRoom.start_y + spawnRoom.end_y) / 2f;
foreach (var kv in gridToRoom)
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)
{
Vector2Int cell = kv.Key;
GameObject roomA = kv.Value;
safety++;
Room current = expandQueue.Dequeue();
if (!current.canExpand()) continue;
foreach (Vector2Int dir in directions)
var dirKeys = new List<int>(current.getPossibleDirections().Keys);
foreach (int dirKey in dirKeys)
{
Vector2Int nKey = cell + dir;
if (!gridToRoom.TryGetValue(nKey, out GameObject roomB)) continue;
// Handle geometry
Vector3 axis; float lenStraight, lenDoor; Quaternion rot;
if (dir == Vector2Int.right)
if (rooms.Count >= roomCount) break;
Room newRoom = TryGrowNeighbour(current, dirKey);
if (newRoom != null)
{
axis = Vector3.right;
lenStraight = straightLenX;
lenDoor = doorLenX;
rot = Quaternion.Euler(0, 90, 0);
rooms.Add(newRoom);
current.addNeighbour(newRoom);
newRoom.addNeighbour(current);
expandQueue.Enqueue(newRoom);
}
else // Vector2Int.up
{
axis = Vector3.forward;
lenStraight = straightLenZ;
lenDoor = doorLenZ;
rot = Quaternion.identity;
}
// Wall calculation
float halfA = Vector3.Scale(GetPrefabXZ(roomA), axis).magnitude * 0.5f;
float halfB = Vector3.Scale(GetPrefabXZ(roomB), axis).magnitude * 0.5f;
Vector3 wallA = roomA.transform.position + axis * halfA;
Vector3 wallB = roomB.transform.position - axis * halfB;
// Doors
Vector3 doorPos = (wallA + wallB) * 0.5f;
GameObject doorGO = Instantiate(DoorCorridor, doorPos, rot, transform);
DoorAnimation anim = doorGO.GetComponent<DoorAnimation>();
// Register the corridor to both rooms
RoomHandler rhA = roomA.GetComponent<RoomHandler>();
RoomHandler rhB = roomB.GetComponent<RoomHandler>();
if (dir == Vector2Int.right)
{
rhA.RegisterDoor(RoomHandler.Side.East, anim);
rhB.RegisterDoor(RoomHandler.Side.West, anim);
}
else
{
rhA.RegisterDoor(RoomHandler.Side.North, anim);
rhB.RegisterDoor(RoomHandler.Side.South, anim);
}
// ROVNÉ SEGMENTY z obou stran dveří
Vector3 doorEdgeA = doorPos - axis * (lenDoor * 0.5f);
Vector3 doorEdgeB = doorPos + axis * (lenDoor * 0.5f);
PlaceStraightSegments(doorEdgeA, wallA, -axis, rot, lenStraight);
PlaceStraightSegments(doorEdgeB, wallB, axis, rot, lenStraight);
}
}
}
/// <summary>
/// Vyplní úsek mezi startEdge (hrana dveří nebo předchozího dílu)
/// a wallEdge (vnější hrana stěny místnosti) rovnými segmenty tak,
/// aby žádný segment nepřečníval do stěny.
/// </summary>
private void PlaceStraightSegments(
Vector3 startEdge,
Vector3 wallEdge,
Vector3 direction,
Quaternion rot,
float len)
private void ConnectRooms()
{
const float EPS = 0.01f;
float dist = Vector3.Distance(startEdge, wallEdge);
if (dist < EPS) return;
int fullCount = Mathf.FloorToInt(dist / len);
float remainder = dist - fullCount * len;
// Full segments
Vector3 firstPivot = startEdge + direction * (len * 0.5f);
for (int i = 0; i < fullCount; i++)
var connectedPairs = new HashSet<(int, int)>();
foreach (Room roomA in rooms)
{
Vector3 pos = firstPivot + direction * (i * len);
GameObject prefab = (i % 2 == 0) ? CorridorStraight : CorridorStraightUnlit;
Instantiate(prefab, pos, rot, transform);
}
// Short segment to fill the gap
if (remainder > EPS)
{
Vector3 remPivot = wallEdge - direction * (remainder * 0.5f);
GameObject last = Instantiate(CorridorStraightUnlit, remPivot, rot, transform);
Vector3 sc = last.transform.localScale;
sc.z *= remainder / len;
last.transform.localScale = sc;
}
}
/* ============================================================ */
/* ROOMS */
/* ============================================================ */
private void BuildRooms(string[] lines, int bottomRowIdx, Vector3 cellSize)
{
for (int r = 0; r < lines.Length; r++)
{
string line = lines[r];
int gridZ = bottomRowIdx - r + 1;
for (int x = 0; x < line.Length; x++)
foreach (Room roomB in roomA.getNeighbours())
{
char ch = line[x];
if (ch == '-' || !char.IsDigit(ch)) continue;
int idx = ch - '0';
if (idx >= mapPrefab.Count) continue;
Vector3 pos = new Vector3(x * (cellSize.x + RoomDistance), 0, gridZ * (cellSize.z + RoomDistance));
GameObject room = Instantiate(mapPrefab[idx], pos, Quaternion.identity, transform);
gridToRoom[new Vector2Int(x, gridZ)] = room;
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 IEnumerator FinishGenerationAfterDelay()
private void CarveCorridor(Room a, Room b)
{
yield return new WaitForSeconds(0.5f);
int half = CORRIDOR_WIDTH / 2; // pro width=3 → half=1
/* ----------- Set entrances ----------- */
foreach (var kv in gridToRoom)
if (a.end_x < b.start_x || b.end_x < a.start_x)
{
Vector2Int g = kv.Key;
RoomHandler rh = kv.Value.GetComponent<RoomHandler>();
bool north = gridToRoom.ContainsKey(g + Vector2Int.left);
bool south = gridToRoom.ContainsKey(g + Vector2Int.right);
bool east = gridToRoom.ContainsKey(g + Vector2Int.up);
bool west = gridToRoom.ContainsKey(g + Vector2Int.down);
rh.SetEntrances(north, south, east, west);
// 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);
}
}
/* ----------- Build corridors ----------- */
BuildCorridors();
/* ----------- Toggle all doors ----------- */
foreach (var keyValuePair in gridToRoom)
else if (a.end_y < b.start_y || b.end_y < a.start_y)
{
RoomHandler rh = keyValuePair.Value.GetComponent<RoomHandler>();
rh.ToggleAllDoors();
}
// 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;
// Vytvoř globální NavMesh
if (useGlobalNavMesh)
{
yield return new WaitForSeconds(0.2f);
CreateGlobalNavMesh();
}
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;
OnGenerationComplete?.Invoke(this);
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 CreateGlobalNavMesh()
private void SafeSetWall(int x, int y, Room r1, Room r2)
{
// Odstraň všechny existující NavMeshSurface z místností
foreach (var room in gridToRoom.Values)
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++)
{
NavMeshSurface roomSurface = room.GetComponent<NavMeshSurface>();
if (roomSurface != null)
for (int col = 0; col < genMatrix[row].Length; col++)
{
Destroy(roomSurface);
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;
}
}
}
// Vytvoř globální NavMeshSurface
globalNavMeshSurface = gameObject.AddComponent<NavMeshSurface>();
globalNavMeshSurface.collectObjects = CollectObjects.Children;
globalNavMeshSurface.useGeometry = NavMeshCollectGeometry.PhysicsColliders;
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);
// Bake globální NavMesh
globalNavMeshSurface.BuildNavMesh();
Destroy(cubeMesh);
Destroy(floorMesh);
}
/* ============================================================ */
/* HELPERS */
/* ============================================================ */
private static Vector3 GetPrefabXZ(GameObject prefab)
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)
{
var ps = prefab.GetComponent<PrefabSize>();
return ps ? new Vector3(ps.prefabSize.x, 0, ps.prefabSize.y) : new Vector3(10, 0, 10);
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 Vector2 GetFirstOrLastRoom(string[] lines)
private Mesh CreateCubeMesh(float w, float h, float d)
{
for (int r = lines.Length - 1; r >= 0; r--)
var mesh = new Mesh();
float hw = w / 2f, hh = h / 2f, hd = d / 2f;
mesh.vertices = new Vector3[]
{
string l = lines[r];
int c = l.ToCharArray().ToList().FindIndex(char.IsDigit);
if (c != -1) return new Vector2(c, r);
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;
}
Debug.LogError("Layout neobsahuje žádnou číslici (místnost)!");
return Vector2.zero;
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];
}
}
@@ -0,0 +1,536 @@
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];
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5d9ee91532380784aa5134fbb9be90cb
@@ -379,6 +379,103 @@ PlayerSettings:
m_Height: 36
m_Kind: 0
m_SubKind:
- m_BuildTarget: iPhone
m_Icons:
- m_Textures: []
m_Width: 180
m_Height: 180
m_Kind: 0
m_SubKind: iPhone
- m_Textures: []
m_Width: 120
m_Height: 120
m_Kind: 0
m_SubKind: iPhone
- m_Textures: []
m_Width: 167
m_Height: 167
m_Kind: 0
m_SubKind: iPad
- m_Textures: []
m_Width: 152
m_Height: 152
m_Kind: 0
m_SubKind: iPad
- m_Textures: []
m_Width: 76
m_Height: 76
m_Kind: 0
m_SubKind: iPad
- m_Textures: []
m_Width: 120
m_Height: 120
m_Kind: 3
m_SubKind: iPhone
- m_Textures: []
m_Width: 80
m_Height: 80
m_Kind: 3
m_SubKind: iPhone
- m_Textures: []
m_Width: 80
m_Height: 80
m_Kind: 3
m_SubKind: iPad
- m_Textures: []
m_Width: 40
m_Height: 40
m_Kind: 3
m_SubKind: iPad
- m_Textures: []
m_Width: 87
m_Height: 87
m_Kind: 1
m_SubKind: iPhone
- m_Textures: []
m_Width: 58
m_Height: 58
m_Kind: 1
m_SubKind: iPhone
- m_Textures: []
m_Width: 29
m_Height: 29
m_Kind: 1
m_SubKind: iPhone
- m_Textures: []
m_Width: 58
m_Height: 58
m_Kind: 1
m_SubKind: iPad
- m_Textures: []
m_Width: 29
m_Height: 29
m_Kind: 1
m_SubKind: iPad
- m_Textures: []
m_Width: 60
m_Height: 60
m_Kind: 2
m_SubKind: iPhone
- m_Textures: []
m_Width: 40
m_Height: 40
m_Kind: 2
m_SubKind: iPhone
- m_Textures: []
m_Width: 40
m_Height: 40
m_Kind: 2
m_SubKind: iPad
- m_Textures: []
m_Width: 20
m_Height: 20
m_Kind: 2
m_SubKind: iPad
- m_Textures: []
m_Width: 1024
m_Height: 1024
m_Kind: 4
m_SubKind: App Store
m_BuildTargetBatching:
- m_BuildTarget: Standalone
m_StaticBatching: 1