using System.Collections.Generic; using UnityEngine; using System.Linq; public class MapGenManager : MonoBehaviour { [Header("Room Prefabs")] [SerializeField] private List mapPrefab = new List(); [SerializeField] private GameObject StartPoint; [SerializeField] private GameObject EndPoint; [Header("Player")] [SerializeField] private GameObject Player; [Header("Corridor Prefabs")] [SerializeField] private GameObject CorridorStraight; [SerializeField] private GameObject CorridorL; [SerializeField] private GameObject CorridorT; [SerializeField] private GameObject CorridorCross; [SerializeField] private GameObject CorridorEnd; [Header("Generation Settings")] [SerializeField] private float minRoomDistance = 30f; [SerializeField] private float maxRoomDistance = 50f; [SerializeField] private float corridorWidth = 5f; private List roomPositions = new List(); private List placedRooms = new List(); void Start() { MapGen(); } private void MapGen() { roomPositions.Clear(); placedRooms.Clear(); // Start position should be aligned to grid Vector3 startPos = new Vector3(0, 0, 0); // Grid aligned at origin GameObject startPoint = Instantiate(StartPoint, startPos, Quaternion.identity, transform); roomPositions.Add(startPos); placedRooms.Add(startPoint); GameObject player = Instantiate(Player, new Vector3(startPos.x, 1, startPos.z), Quaternion.identity, transform); int roomCount = Random.Range(3, 7); for (int i = 0; i < roomCount; i++) { Vector3 roomPos = GetRandomGridPosition(); GameObject roomPrefab = mapPrefab[Random.Range(0, mapPrefab.Count)]; GameObject room = Instantiate(roomPrefab, roomPos, Quaternion.identity, transform); placedRooms.Add(room); roomPositions.Add(roomPos); } GameObject endPoint = Instantiate(EndPoint, GetRandomGridPosition(), Quaternion.identity, transform); roomPositions.Add(endPoint.transform.position); placedRooms.Add(endPoint); // Generate corridors to connect rooms GenerateCorridors(); // Add some dead ends for more dynamic layouts AddDeadEndCorridors(); } private void GenerateCorridors() { // Create a minimum spanning tree to ensure all rooms are connected List<(int, int)> edges = CreateMinimumSpanningTree(); // Place corridors between connected rooms foreach (var edge in edges) { ConnectRoomsWithCorridor(roomPositions[edge.Item1], roomPositions[edge.Item2]); } } private List<(int, int)> CreateMinimumSpanningTree() { // Using Prim's algorithm to generate a minimum spanning tree List<(int, int)> mstEdges = new List<(int, int)>(); List connectedNodes = new List(); List unconnectedNodes = new List(); // Start with node 0 (start room) for (int i = 0; i < roomPositions.Count; i++) { unconnectedNodes.Add(i); } // Start with first node connectedNodes.Add(unconnectedNodes[0]); unconnectedNodes.RemoveAt(0); // Continue until all nodes are connected while (unconnectedNodes.Count > 0) { float minDistance = float.MaxValue; int closestConnected = -1; int closestUnconnected = -1; // Find shortest edge between a connected and unconnected node foreach (int connected in connectedNodes) { foreach (int unconnected in unconnectedNodes) { float distance = Vector3.Distance(roomPositions[connected], roomPositions[unconnected]); if (distance < minDistance) { minDistance = distance; closestConnected = connected; closestUnconnected = unconnected; } } } // Add the edge to our MST mstEdges.Add((closestConnected, closestUnconnected)); // Move the node from unconnected to connected connectedNodes.Add(closestUnconnected); unconnectedNodes.Remove(closestUnconnected); } return mstEdges; } private void ConnectRoomsWithCorridor(Vector3 startRoom, Vector3 endRoom) { // Calculate the grid-based path between rooms List path = CalculateGridPath(startRoom, endRoom); // Place corridor pieces along the path for (int i = 0; i < path.Count - 1; i++) { PlaceCorridorSegment(path[i], path[i + 1]); } } private List CalculateGridPath(Vector3 start, Vector3 end) { List path = new List(); path.Add(start); // Determine if we go horizontal first or vertical first (50/50 chance) bool horizontalFirst = Random.value < 0.5f; Vector3 current = start; if (horizontalFirst) { // Move horizontally first, then vertically while (Mathf.Abs(current.x - end.x) >= 5) { float step = current.x < end.x ? 5 : -5; current = new Vector3(current.x + step, 0, current.z); path.Add(current); } while (Mathf.Abs(current.z - end.z) >= 5) { float step = current.z < end.z ? 5 : -5; current = new Vector3(current.x, 0, current.z + step); path.Add(current); } } else { // Move vertically first, then horizontally while (Mathf.Abs(current.z - end.z) >= 5) { float step = current.z < end.z ? 5 : -5; current = new Vector3(current.x, 0, current.z + step); path.Add(current); } while (Mathf.Abs(current.x - end.x) >= 5) { float step = current.x < end.x ? 5 : -5; current = new Vector3(current.x + step, 0, current.z); path.Add(current); } } // Add the end position if it's not already there if (Vector3.Distance(current, end) >= 5) { path.Add(end); } return path; } private void PlaceCorridorSegment(Vector3 start, Vector3 end) { // Determine corridor type based on connecting rooms Vector3 direction = end - start; GameObject corridorPrefab = CorridorStraight; // Default to straight Quaternion rotation = Quaternion.identity; Vector3 position = (start + end) / 2; // Calculate direction for rotation if (direction.x > 0) // East { rotation = Quaternion.Euler(0, 90, 0); } else if (direction.x < 0) // West { rotation = Quaternion.Euler(0, 90, 0); } else if (direction.z > 0) // North { rotation = Quaternion.Euler(0, 0, 0); } else if (direction.z < 0) // South { rotation = Quaternion.Euler(0, 0, 0); } // Check if this segment is part of a turn, T-junction, or crossing List connectingDirections = GetConnectingDirections(end); // Determine corridor type and rotation based on connections if (connectingDirections.Count == 1) // Straight corridor or dead end { corridorPrefab = CorridorStraight; } else if (connectingDirections.Count == 2) { // Check if it's an L-turn if (IsLTurn(direction, connectingDirections)) { corridorPrefab = CorridorL; // Adjust rotation for L-turn rotation = GetLTurnRotation(direction, connectingDirections); } } else if (connectingDirections.Count == 3) // T-junction { corridorPrefab = CorridorT; // Adjust rotation for T-junction rotation = GetTJunctionRotation(direction, connectingDirections); } else if (connectingDirections.Count >= 4) // Cross junction { corridorPrefab = CorridorCross; } Instantiate(corridorPrefab, position, rotation, transform); } private enum Direction { North, East, South, West } private List GetConnectingDirections(Vector3 position) { // Check which directions have corridors or rooms from this position List connections = new List(); // Check in each cardinal direction Vector3[] offsets = new Vector3[] { new Vector3(0, 0, 5), // North new Vector3(5, 0, 0), // East new Vector3(0, 0, -5), // South new Vector3(-5, 0, 0) // West }; Direction[] directions = new Direction[] { Direction.North, Direction.East, Direction.South, Direction.West }; for (int i = 0; i < offsets.Length; i++) { Vector3 checkPos = position + offsets[i]; // Check if there's a room at this position bool hasConnection = roomPositions.Any(rp => Vector3.Distance(rp, checkPos) < 2.5f); // If no room, check for corridor (simplified - in a full implementation, // you'd track placed corridors separately) if (!hasConnection) { // For simplicity, assume there's a corridor if it's part of a path we've calculated // In a complete implementation, you'd track corridor positions } if (hasConnection) { connections.Add(directions[i]); } } return connections; } private bool IsLTurn(Vector3 incomingDirection, List connections) { // Check if connections form an L shape (90-degree turn) if (connections.Count != 2) return false; Direction incoming = VectorToDirection(incomingDirection); Direction opposite = GetOppositeDirection(incoming); // If one of the connections is opposite to the incoming direction, // then it's a straight corridor, not an L-turn return !connections.Contains(opposite); } private Direction VectorToDirection(Vector3 vector) { if (vector.x > 0) return Direction.East; if (vector.x < 0) return Direction.West; if (vector.z > 0) return Direction.North; return Direction.South; } private Direction GetOppositeDirection(Direction dir) { switch (dir) { case Direction.North: return Direction.South; case Direction.East: return Direction.West; case Direction.South: return Direction.North; case Direction.West: return Direction.East; default: return Direction.North; } } private Quaternion GetLTurnRotation(Vector3 incomingDirection, List connections) { // Calculate rotation for L-turns based on the directions it connects Direction incoming = VectorToDirection(incomingDirection); // Find the other direction (not the incoming and not the opposite of incoming) Direction other = connections.Find(d => d != incoming && d != GetOppositeDirection(incoming)); switch (incoming) { case Direction.North: return other == Direction.East ? Quaternion.Euler(0, 0, 0) : Quaternion.Euler(0, 270, 0); case Direction.East: return other == Direction.North ? Quaternion.Euler(0, 90, 0) : Quaternion.Euler(0, 0, 0); case Direction.South: return other == Direction.East ? Quaternion.Euler(0, 270, 0) : Quaternion.Euler(0, 180, 0); case Direction.West: return other == Direction.North ? Quaternion.Euler(0, 180, 0) : Quaternion.Euler(0, 90, 0); default: return Quaternion.identity; } } private Quaternion GetTJunctionRotation(Vector3 incomingDirection, List connections) { // Calculate rotation for T-junctions Direction incoming = VectorToDirection(incomingDirection); Direction opposite = GetOppositeDirection(incoming); // If the connection doesn't include the opposite direction, the "T" points in that direction if (!connections.Contains(opposite)) { switch (opposite) { case Direction.North: return Quaternion.Euler(0, 0, 0); case Direction.East: return Quaternion.Euler(0, 90, 0); case Direction.South: return Quaternion.Euler(0, 180, 0); case Direction.West: return Quaternion.Euler(0, 270, 0); } } // If it does contain the opposite, find the missing direction Direction[] allDirections = new Direction[] { Direction.North, Direction.East, Direction.South, Direction.West }; Direction missing = allDirections.First(d => !connections.Contains(d)); switch (missing) { case Direction.North: return Quaternion.Euler(0, 180, 0); case Direction.East: return Quaternion.Euler(0, 270, 0); case Direction.South: return Quaternion.Euler(0, 0, 0); case Direction.West: return Quaternion.Euler(0, 90, 0); default: return Quaternion.identity; } } private void AddDeadEndCorridors() { // Add some random dead ends for more interesting level design int deadEndCount = Random.Range(1, 4); // 1-3 dead ends for (int i = 0; i < deadEndCount; i++) { // Pick a random room to extend from int roomIndex = Random.Range(0, roomPositions.Count); Vector3 roomPos = roomPositions[roomIndex]; // Pick a random direction Vector3[] directions = new Vector3[] { new Vector3(5, 0, 0), // East new Vector3(-5, 0, 0), // West new Vector3(0, 0, 5), // North new Vector3(0, 0, -5) // South }; Vector3 direction = directions[Random.Range(0, directions.Length)]; // Create a dead end corridor (1-3 segments long) int segmentCount = Random.Range(1, 4); Vector3 currentPos = roomPos; for (int j = 0; j < segmentCount; j++) { Vector3 nextPos = currentPos + direction; // Make sure we're not placing corridors where rooms exist bool canPlace = true; foreach (Vector3 roomPosition in roomPositions) { if (Vector3.Distance(nextPos, roomPosition) < 5) { canPlace = false; break; } } if (canPlace) { PlaceCorridorSegment(currentPos, nextPos); currentPos = nextPos; } else { break; } } // Place an end cap at the last position if it's not overlapping with a room bool endCapCanBePlaced = true; foreach (Vector3 roomPosition in roomPositions) { if (Vector3.Distance(currentPos, roomPosition) < 5) { endCapCanBePlaced = false; break; } } if (endCapCanBePlaced && segmentCount > 0) { // Calculate rotation based on direction Quaternion rotation = Quaternion.identity; if (direction.x > 0) rotation = Quaternion.Euler(0, 90, 0); else if (direction.x < 0) rotation = Quaternion.Euler(0, 270, 0); else if (direction.z > 0) rotation = Quaternion.Euler(0, 0, 0); else if (direction.z < 0) rotation = Quaternion.Euler(0, 180, 0); Instantiate(CorridorEnd, currentPos, rotation, transform); } } } private Vector3 GetRandomGridPosition() { Vector3 lastRoomPos = roomPositions[roomPositions.Count - 1]; // Calculate min and max distances in grid units (multiples of 5) int minGridDistance = Mathf.CeilToInt(minRoomDistance / 5); int maxGridDistance = Mathf.FloorToInt(maxRoomDistance / 5); // Get random grid cell offset int xGridOffset = 0; int zGridOffset = 0; Vector3 roomPos = Vector3.zero; // Make sure the position is valid int attempts = 0; int maxAttempts = 100; // Prevent infinite loops do { // Generate random offsets directly as grid units xGridOffset = Random.Range(-maxGridDistance, maxGridDistance + 1); zGridOffset = Random.Range(-maxGridDistance, maxGridDistance + 1); // Ensure we respect minimum distance if (Mathf.Abs(xGridOffset) < minGridDistance && Mathf.Abs(zGridOffset) < minGridDistance) { // Force minimum distance by picking a direction if (Random.value < 0.5f) xGridOffset = Random.value < 0.5f ? minGridDistance : -minGridDistance; else zGridOffset = Random.value < 0.5f ? minGridDistance : -minGridDistance; } // Convert grid units to world position (multiply by 5) roomPos = new Vector3( lastRoomPos.x + (xGridOffset * 5), 0, lastRoomPos.z + (zGridOffset * 5) ); attempts++; } while (!IsValidPos(roomPos) && attempts < maxAttempts); // If we couldn't find a valid position, use fallback if (attempts >= maxAttempts) { Debug.LogWarning("Couldn't find valid room position after " + maxAttempts + " attempts. Using best approximation."); roomPos = FindNearestValidGridPosition(lastRoomPos); } return roomPos; } private Vector3 FindNearestValidGridPosition(Vector3 startPos) { // Define min and max grid distances in grid units (not world units) int minGridDistance = Mathf.CeilToInt(minRoomDistance / 5); int maxGridDistance = Mathf.FloorToInt(maxRoomDistance / 5); // Check each grid distance in increasing order for (int distance = minGridDistance; distance <= maxGridDistance; distance++) { // Try cardinal directions first (more likely to have space) int[] directions = { distance, -distance }; // Try horizontal directions foreach (int x in directions) { Vector3 testPos = new Vector3(startPos.x + (x * 5), 0, startPos.z); if (IsValidPos(testPos)) return testPos; } // Try vertical directions foreach (int z in directions) { Vector3 testPos = new Vector3(startPos.x, 0, startPos.z + (z * 5)); if (IsValidPos(testPos)) return testPos; } // Try diagonals foreach (int x in directions) { foreach (int z in directions) { Vector3 testPos = new Vector3(startPos.x + (x * 5), 0, startPos.z + (z * 5)); if (IsValidPos(testPos)) return testPos; } } } // If all else fails, return a position at minimum distance return new Vector3(startPos.x + (minGridDistance * 5), 0, startPos.z); } private bool IsValidPos(Vector3 pos) { foreach (Vector3 roomPos in roomPositions) { if (Vector3.Distance(pos, roomPos) < minRoomDistance) { return false; } } return true; } }