Files
3DBlobici-WorkingTitle/3D blobici/Assets/Scripts/MapGen/MapGenManager.cs

303 lines
11 KiB
C#
Raw Normal View History

2025-06-30 18:31:40 +02:00
using System;
using System.Collections;
2025-06-30 18:31:40 +02:00
using System.Collections.Generic;
2025-06-23 16:30:18 +02:00
using System.Linq;
using Unity.AI.Navigation;
2025-06-30 16:28:30 +02:00
using UnityEngine;
using UnityEngine.AI;
2025-06-23 16:30:18 +02:00
public class MapGenManager : MonoBehaviour
{
2025-06-27 07:13:39 +02:00
/* ------------------ INSPECTOR FIELDS ------------------ */
2025-06-23 16:30:18 +02:00
[Header("Room Prefabs")]
2025-06-27 07:13:39 +02:00
[SerializeField] private List<GameObject> mapPrefab = new();
2025-06-24 16:59:41 +02:00
2025-06-23 16:30:18 +02:00
[Header("Player")]
[SerializeField] private GameObject Player;
2025-06-24 16:59:41 +02:00
2025-06-23 16:30:18 +02:00
[Header("Corridor Prefabs")]
[SerializeField] private GameObject CorridorStraight;
[SerializeField] private GameObject CorridorStraightUnlit;
[SerializeField] private GameObject DoorCorridor;
2025-06-25 23:54:00 +02:00
[Header("Layout")]
[SerializeField] private MapLayout layout;
2025-06-24 16:59:41 +02:00
[Header("NavMesh Settings")]
[SerializeField] private bool useGlobalNavMesh = true;
private NavMeshSurface globalNavMeshSurface;
2025-06-23 16:30:18 +02:00
[Header("Generation Settings")]
[SerializeField] private int RoomDistance = 3;
2025-06-23 16:30:18 +02:00
2025-06-27 07:13:39 +02:00
private readonly Dictionary<Vector2Int, GameObject> gridToRoom = new();
2025-06-30 18:31:40 +02:00
public event Action<MapGenManager> OnGenerationComplete;
public IReadOnlyDictionary<Vector2Int, GameObject> GridToRoom => gridToRoom;
2025-06-27 07:13:39 +02:00
void Start() => StartCoroutine(GenerateWithDelay());
private IEnumerator GenerateWithDelay()
{
yield return null; // Počkej jeden frame
GenerateFromLayout();
}
2025-06-25 23:54:00 +02:00
private void GenerateFromLayout()
2025-06-23 16:30:18 +02:00
{
2025-06-25 23:54:00 +02:00
if (layout == null || string.IsNullOrWhiteSpace(layout.grid))
{
Debug.LogError("Layout asset není přiřazený nebo je prázdný!");
return;
}
gridToRoom.Clear();
2025-06-24 16:59:41 +02:00
2025-06-27 07:13:39 +02:00
/* ----------- Create a spawn room ----------- */
2025-06-25 23:54:00 +02:00
GameObject spawnPrefab = mapPrefab[0];
2025-06-27 07:13:39 +02:00
Vector3 cellSize = GetPrefabXZ(spawnPrefab);
2025-06-24 16:59:41 +02:00
2025-06-30 16:28:30 +02:00
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;
2025-06-25 23:54:00 +02:00
2025-06-30 16:28:30 +02:00
Vector3 spawnPos = new Vector3(spawnX * (cellSize.x + RoomDistance), 0, 0);
2025-06-27 07:13:39 +02:00
GameObject spawnRoom = Instantiate(spawnPrefab, spawnPos, Quaternion.identity, transform);
2025-06-30 16:28:30 +02:00
gridToRoom[new Vector2Int(spawnX, 0)] = spawnRoom;
2025-06-25 23:54:00 +02:00
2025-06-30 16:28:30 +02:00
/* ----------- Spawn the player ----------- */
2025-06-27 07:13:39 +02:00
if (Player)
{
2025-06-30 16:28:30 +02:00
GameObject p = Instantiate(Player, spawnPos + new Vector3(0, 1, 3), Quaternion.identity, transform);
Transform inner = p.transform.Find("Player");
if (inner) inner.tag = "Player";
2025-06-27 07:13:39 +02:00
}
2025-06-24 16:59:41 +02:00
2025-06-30 16:28:30 +02:00
/* ----------- Build rooms ----------- */
BuildRooms(lines, bottomRow, cellSize);
2025-06-25 23:54:00 +02:00
// Použij coroutine pro dokončení generování
StartCoroutine(FinishGenerationAfterDelay());
2025-06-23 16:30:18 +02:00
}
2025-06-30 16:34:55 +02:00
/// <summary>
/// Builds corridors and doors between rooms.
/// </summary>
2025-06-25 23:54:00 +02:00
private void BuildCorridors()
2025-06-23 16:30:18 +02:00
{
2025-06-30 16:28:30 +02:00
// Jediné dva směry, které musíme zkontrolovat z každé místnosti (right/up)
Vector2Int[] directions = { Vector2Int.right, Vector2Int.up };
// 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;
2025-06-27 07:13:39 +02:00
foreach (var kv in gridToRoom)
2025-06-25 23:54:00 +02:00
{
2025-06-27 07:13:39 +02:00
Vector2Int cell = kv.Key;
GameObject roomA = kv.Value;
2025-06-30 16:28:30 +02:00
foreach (Vector2Int dir in directions)
{
2025-06-27 07:13:39 +02:00
Vector2Int nKey = cell + dir;
2025-06-30 16:34:55 +02:00
if (!gridToRoom.TryGetValue(nKey, out GameObject roomB)) continue;
2025-06-27 07:13:39 +02:00
2025-06-30 16:34:55 +02:00
// Handle geometry
2025-06-30 16:28:30 +02:00
Vector3 axis; float lenStraight, lenDoor; Quaternion rot;
2025-06-27 07:13:39 +02:00
if (dir == Vector2Int.right)
{
2025-06-30 16:28:30 +02:00
axis = Vector3.right;
lenStraight = straightLenX;
lenDoor = doorLenX;
2025-06-27 07:13:39 +02:00
rot = Quaternion.Euler(0, 90, 0);
}
2025-06-30 16:28:30 +02:00
else // Vector2Int.up
2025-06-27 07:13:39 +02:00
{
2025-06-30 16:28:30 +02:00
axis = Vector3.forward;
lenStraight = straightLenZ;
lenDoor = doorLenZ;
2025-06-27 07:13:39 +02:00
rot = Quaternion.identity;
}
2025-06-25 23:54:00 +02:00
2025-06-30 16:34:55 +02:00
// Wall calculation
2025-06-30 16:28:30 +02:00
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;
2025-06-27 07:13:39 +02:00
2025-06-30 16:34:55 +02:00
// Doors
2025-06-30 16:28:30 +02:00
Vector3 doorPos = (wallA + wallB) * 0.5f;
GameObject doorGO = Instantiate(DoorCorridor, doorPos, rot, transform);
DoorAnimation anim = doorGO.GetComponent<DoorAnimation>();
2025-06-27 07:13:39 +02:00
2025-06-30 16:34:55 +02:00
// Register the corridor to both rooms
2025-06-30 16:28:30 +02:00
RoomHandler rhA = roomA.GetComponent<RoomHandler>();
RoomHandler rhB = roomB.GetComponent<RoomHandler>();
if (dir == Vector2Int.right)
{
2025-06-30 16:28:30 +02:00
rhA.RegisterDoor(RoomHandler.Side.East, anim);
rhB.RegisterDoor(RoomHandler.Side.West, anim);
}
2025-06-30 16:28:30 +02:00
else
{
2025-06-30 16:28:30 +02:00
rhA.RegisterDoor(RoomHandler.Side.North, anim);
rhB.RegisterDoor(RoomHandler.Side.South, anim);
}
2025-06-25 23:54:00 +02:00
2025-06-30 16:28:30 +02:00
// ROVNÉ SEGMENTY z obou stran dveří
Vector3 doorEdgeA = doorPos - axis * (lenDoor * 0.5f);
Vector3 doorEdgeB = doorPos + axis * (lenDoor * 0.5f);
2025-06-27 07:13:39 +02:00
2025-06-30 16:28:30 +02:00
PlaceStraightSegments(doorEdgeA, wallA, -axis, rot, lenStraight);
PlaceStraightSegments(doorEdgeB, wallB, axis, rot, lenStraight);
2025-06-27 07:13:39 +02:00
}
}
}
/// <summary>
2025-06-30 16:28:30 +02:00
/// 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.
2025-06-27 07:13:39 +02:00
/// </summary>
2025-06-30 16:28:30 +02:00
private void PlaceStraightSegments(
Vector3 startEdge,
Vector3 wallEdge,
2025-06-30 16:34:55 +02:00
Vector3 direction,
2025-06-30 16:28:30 +02:00
Quaternion rot,
2025-06-30 16:34:55 +02:00
float len)
2025-06-27 07:13:39 +02:00
{
2025-06-30 16:34:55 +02:00
const float EPS = 0.01f;
2025-06-30 16:28:30 +02:00
float dist = Vector3.Distance(startEdge, wallEdge);
2025-06-30 16:34:55 +02:00
if (dist < EPS) return;
2025-06-27 07:13:39 +02:00
2025-06-30 16:28:30 +02:00
int fullCount = Mathf.FloorToInt(dist / len);
2025-06-30 16:34:55 +02:00
float remainder = dist - fullCount * len;
2025-06-25 23:54:00 +02:00
2025-06-30 16:34:55 +02:00
// Full segments
Vector3 firstPivot = startEdge + direction * (len * 0.5f);
2025-06-30 16:28:30 +02:00
for (int i = 0; i < fullCount; i++)
2025-06-27 07:13:39 +02:00
{
2025-06-30 16:28:30 +02:00
Vector3 pos = firstPivot + direction * (i * len);
GameObject prefab = (i % 2 == 0) ? CorridorStraight : CorridorStraightUnlit;
Instantiate(prefab, pos, rot, transform);
2025-06-27 07:13:39 +02:00
}
2025-06-30 16:28:30 +02:00
2025-06-30 16:34:55 +02:00
// Short segment to fill the gap
if (remainder > EPS)
2025-06-27 07:13:39 +02:00
{
2025-06-30 16:28:30 +02:00
Vector3 remPivot = wallEdge - direction * (remainder * 0.5f);
GameObject last = Instantiate(CorridorStraightUnlit, remPivot, rot, transform);
Vector3 sc = last.transform.localScale;
2025-06-30 16:34:55 +02:00
sc.z *= remainder / len;
2025-06-30 16:28:30 +02:00
last.transform.localScale = sc;
2025-06-25 23:54:00 +02:00
}
}
2025-06-30 16:28:30 +02:00
/* ============================================================ */
/* ROOMS */
/* ============================================================ */
private void BuildRooms(string[] lines, int bottomRowIdx, Vector3 cellSize)
2025-06-25 23:54:00 +02:00
{
2025-06-30 16:28:30 +02:00
for (int r = 0; r < lines.Length; r++)
2025-06-25 23:54:00 +02:00
{
2025-06-30 16:28:30 +02:00
string line = lines[r];
int gridZ = bottomRowIdx - r + 1;
for (int x = 0; x < line.Length; x++)
{
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;
}
2025-06-23 16:30:18 +02:00
}
}
2025-06-27 07:13:39 +02:00
private IEnumerator FinishGenerationAfterDelay()
{
yield return new WaitForSeconds(0.5f);
/* ----------- Set entrances ----------- */
foreach (var kv in gridToRoom)
{
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);
}
/* ----------- Build corridors ----------- */
BuildCorridors();
/* ----------- Toggle all doors ----------- */
foreach (var keyValuePair in gridToRoom)
{
RoomHandler rh = keyValuePair.Value.GetComponent<RoomHandler>();
rh.ToggleAllDoors();
}
// Vytvoř globální NavMesh
if (useGlobalNavMesh)
{
yield return new WaitForSeconds(0.2f);
CreateGlobalNavMesh();
}
OnGenerationComplete?.Invoke(this);
}
private void CreateGlobalNavMesh()
{
// Odstraň všechny existující NavMeshSurface z místností
foreach (var room in gridToRoom.Values)
{
NavMeshSurface roomSurface = room.GetComponent<NavMeshSurface>();
if (roomSurface != null)
{
Destroy(roomSurface);
}
}
// Vytvoř globální NavMeshSurface
globalNavMeshSurface = gameObject.AddComponent<NavMeshSurface>();
globalNavMeshSurface.collectObjects = CollectObjects.Children;
globalNavMeshSurface.useGeometry = NavMeshCollectGeometry.PhysicsColliders;
// Bake globální NavMesh
globalNavMeshSurface.BuildNavMesh();
Debug.Log("Global NavMesh baked");
}
2025-06-30 16:28:30 +02:00
/* ============================================================ */
/* HELPERS */
/* ============================================================ */
2025-06-27 07:13:39 +02:00
private static Vector3 GetPrefabXZ(GameObject prefab)
{
var ps = prefab.GetComponent<PrefabSize>();
return ps ? new Vector3(ps.prefabSize.x, 0, ps.prefabSize.y) : new Vector3(10, 0, 10);
}
2025-06-30 16:28:30 +02:00
private Vector2 GetFirstOrLastRoom(string[] lines)
{
for (int r = lines.Length - 1; r >= 0; r--)
{
string l = lines[r];
int c = l.ToCharArray().ToList().FindIndex(char.IsDigit);
if (c != -1) return new Vector2(c, r);
}
Debug.LogError("Layout neobsahuje žádnou číslici (místnost)!");
return Vector2.zero;
}
2025-06-27 07:13:39 +02:00
}