using UnityEngine; using UnityEngine.Tilemaps; using System.Collections.Generic; /// /// Renders a maze to a tilemap for visualization /// This allows you to see the generated maze in the game scene /// [RequireComponent(typeof(Grid))] public class MazeRenderer : MonoBehaviour { [SerializeField] private MazeController mazeController; [SerializeField] private Tilemap combinedTilemap; [SerializeField] private Tilemap floorTilemap; [SerializeField] private Tilemap wallTilemap; [SerializeField] private Tilemap decorationTilemap; [Header("Tile Assets")] [SerializeField] private TileBase floorTile; [SerializeField] private TileBase floorCenterTile; [SerializeField] private TileBase floorEdgeTile; [SerializeField] private TileBase floorCornerTile; [SerializeField] private TileBase wallTile; [SerializeField] private TileBase swampTile; [SerializeField] private TileBase stoneTile; [SerializeField] private TileBase lavaTile; [Header("Wall Variant Tiles")] [SerializeField] private TileBase wallCenterTile; [SerializeField] private TileBase wallVerticalTile; [SerializeField] private TileBase wallHorizontalTile; [SerializeField] private TileBase wallCornerNW; [SerializeField] private TileBase wallCornerNE; [SerializeField] private TileBase wallCornerSE; [SerializeField] private TileBase wallCornerSW; [SerializeField] private TileBase wallCornerGenericTile; [SerializeField] private TileBase wallEndNorthTile; [SerializeField] private TileBase wallEndEastTile; [SerializeField] private TileBase wallEndSouthTile; [SerializeField] private TileBase wallEndWestTile; [SerializeField] private TileBase wallEndTile; [SerializeField] private TileBase wallTJNorthTile; [SerializeField] private TileBase wallTJEastTile; [SerializeField] private TileBase wallTJSouthTile; [SerializeField] private TileBase wallTJWestTile; [SerializeField] private TileBase wallTTile; [SerializeField] private TileBase wallCrossTile; [Header("Debug")] [SerializeField] private bool showNeighborMaskDebug = false; [SerializeField] private Sprite startMarkerSprite; [SerializeField] private Sprite exitMarkerSprite; private Dictionary terrainTiles; void OnEnable() { SetupTerrainDictionary(); } void SetupTerrainDictionary() { terrainTiles = new Dictionary { { MazeTile.TerrainType.Normal, floorTile ?? wallTile }, { MazeTile.TerrainType.Swamp, swampTile ?? floorTile ?? wallTile }, { MazeTile.TerrainType.Stone, stoneTile ?? floorTile ?? wallTile }, { MazeTile.TerrainType.Lava, lavaTile ?? floorTile ?? wallTile }, }; } /// /// Renders the maze to the tilemaps /// public void RenderMaze() { ClearTilemaps(); var maze = mazeController.GetCurrentMaze(); if (maze == null) { Debug.LogError("No maze to render"); return; } // For very large mazes, skip full rendering to prevent crashes if (maze.Width > 500 || maze.Height > 500) { Debug.LogWarning($"Maze too large ({maze.Width}x{maze.Height}) for tilemap rendering. Consider using chunked rendering or 3D mesh generation."); return; } // Auto-generate placeholder tiles if none are assigned if (floorTile == null) { GeneratePlaceholderTiles(); } // Render tiles for (int x = 0; x < maze.Width; x++) { for (int y = 0; y < maze.Height; y++) { var tile = maze.GetTile(x, y); var position = new Vector3Int(x, y, 0); if (combinedTilemap != null) { if (tile.Type == MazeTile.TileType.Wall) { var wallDefinition = GetWallTileDefinition(maze, x, y); SetTileWithRotation(combinedTilemap, position, wallDefinition.tile, wallDefinition.rotation); } else if (tile.Type == MazeTile.TileType.Floor || tile.Type == MazeTile.TileType.Terrain) { combinedTilemap.SetTile(position, GetFloorTile(maze, x, y, tile.Terrain)); } } else { if (tile.Type == MazeTile.TileType.Wall) { var wallDefinition = GetWallTileDefinition(maze, x, y); SetTileWithRotation(wallTilemap, position, wallDefinition.tile, wallDefinition.rotation); } else if (tile.Type == MazeTile.TileType.Floor || tile.Type == MazeTile.TileType.Terrain) { floorTilemap.SetTile(position, GetFloorTile(maze, x, y, tile.Terrain)); } } } } // Render room markers (optional - can cause performance issues with large mazes) // RenderRoomBoundaries(maze); // Render start and exit points RenderStartPoints(maze); RenderExitPoints(maze); if (showNeighborMaskDebug) { LogNeighborMaskStats(maze); } Debug.Log("Maze rendered"); } /// /// Gets the tilemap tile for a terrain type /// private TileBase GetTerrainTile(MazeTile.TerrainType terrain) { if (terrainTiles.TryGetValue(terrain, out var tile)) { return tile; } return floorTile; } private TileBase GetFloorTile(MazeData maze, int x, int y, MazeTile.TerrainType terrain) { int openMask = GetFloorOpenMask(maze, x, y); return openMask switch { 0 => floorCenterTile ?? GetTerrainTile(terrain), 1 or 2 or 4 or 8 => floorEdgeTile ?? GetTerrainTile(terrain), 3 or 6 or 12 or 9 => floorCornerTile ?? GetTerrainTile(terrain), 5 or 10 => GetTerrainTile(terrain), _ => GetTerrainTile(terrain), }; } private (TileBase tile, Quaternion rotation) GetWallTileDefinition(MazeData maze, int x, int y) { int openMask = GetWallOpenMask(maze, x, y); if (showNeighborMaskDebug) { Debug.Log($"Wall mask @({x},{y}) = {openMask}"); } return openMask switch { 0 => (wallCenterTile ?? wallTile, Quaternion.identity), 5 => (wallVerticalTile ?? wallTile, Quaternion.identity), 10 => (wallHorizontalTile ?? wallTile, Quaternion.identity), 3 => (wallCornerNE ?? wallCornerGenericTile ?? wallTile, Quaternion.identity), 6 => (wallCornerSE ?? wallCornerGenericTile ?? wallTile, Quaternion.identity), 12 => (wallCornerSW ?? wallCornerGenericTile ?? wallTile, Quaternion.identity), 9 => (wallCornerNW ?? wallCornerGenericTile ?? wallTile, Quaternion.identity), 1 => (wallEndNorthTile ?? wallEndTile ?? wallTile, Quaternion.identity), 2 => (wallEndEastTile ?? wallEndTile ?? wallTile, Quaternion.Euler(0, 0, 270)), 4 => (wallEndSouthTile ?? wallEndTile ?? wallTile, Quaternion.Euler(0, 0, 180)), 8 => (wallEndWestTile ?? wallEndTile ?? wallTile, Quaternion.Euler(0, 0, 90)), 7 => (wallTJNorthTile ?? wallTTile ?? wallTile, Quaternion.identity), 11 => (wallTJEastTile ?? wallTTile ?? wallTile, Quaternion.Euler(0, 0, 270)), 14 => (wallTJSouthTile ?? wallTTile ?? wallTile, Quaternion.Euler(0, 0, 180)), 13 => (wallTJWestTile ?? wallTTile ?? wallTile, Quaternion.Euler(0, 0, 90)), 15 => (wallCrossTile ?? wallTile, Quaternion.identity), _ => (wallTile, Quaternion.identity), }; } private void SetTileWithRotation(Tilemap tilemap, Vector3Int position, TileBase tile, Quaternion rotation) { tilemap.SetTile(position, tile); tilemap.SetTransformMatrix(position, Matrix4x4.TRS(Vector3.zero, rotation, Vector3.one)); } private bool IsWallNeighbor(MazeData maze, int x, int y) { if (!maze.IsInBounds(x, y)) { return true; } var tile = maze.GetTile(x, y); return tile != null && tile.Type == MazeTile.TileType.Wall; } private bool IsWalkableNeighbor(MazeData maze, int x, int y) { if (!maze.IsInBounds(x, y)) { return false; } var tile = maze.GetTile(x, y); return tile != null && (tile.Type == MazeTile.TileType.Floor || tile.Type == MazeTile.TileType.Terrain); } private int GetWallOpenMask(MazeData maze, int x, int y) { int mask = 0; if (!IsWallNeighbor(maze, x, y + 1)) mask |= 1; // north open if (!IsWallNeighbor(maze, x + 1, y)) mask |= 2; // east open if (!IsWallNeighbor(maze, x, y - 1)) mask |= 4; // south open if (!IsWallNeighbor(maze, x - 1, y)) mask |= 8; // west open return mask; } private int GetFloorOpenMask(MazeData maze, int x, int y) { int mask = 0; if (IsWalkableNeighbor(maze, x, y + 1)) mask |= 1; // north walkable if (IsWalkableNeighbor(maze, x + 1, y)) mask |= 2; // east walkable if (IsWalkableNeighbor(maze, x, y - 1)) mask |= 4; // south walkable if (IsWalkableNeighbor(maze, x - 1, y)) mask |= 8; // west walkable return mask; } private void LogNeighborMaskStats(MazeData maze) { int wallMaskCount = 0; int floorMaskCount = 0; for (int x = 0; x < maze.Width; x++) { for (int y = 0; y < maze.Height; y++) { var tile = maze.GetTile(x, y); if (tile.Type == MazeTile.TileType.Wall) { if (GetWallOpenMask(maze, x, y) != 0) wallMaskCount++; } else if (tile.Type == MazeTile.TileType.Floor || tile.Type == MazeTile.TileType.Terrain) { if (GetFloorOpenMask(maze, x, y) != 0) floorMaskCount++; } } } Debug.Log($"Neighbor mask debug: wall tiles with open sides={wallMaskCount}, floor tiles with adjacent walkable={floorMaskCount}"); } /// /// Renders start points as visual markers /// private void RenderStartPoints(MazeData maze) { foreach (var point in maze.StartPoints) { var go = new GameObject($"StartPoint_{point.x}_{point.y}"); go.transform.parent = transform; go.transform.position = new Vector3(point.x + 0.5f, point.y + 0.5f, -1); if (startMarkerSprite != null) { var spriteRenderer = go.AddComponent(); spriteRenderer.sprite = startMarkerSprite; spriteRenderer.color = new Color(0, 1, 0, 0.7f); } } } /// /// Renders exit points as visual markers /// private void RenderExitPoints(MazeData maze) { foreach (var point in maze.ExitPoints) { var go = new GameObject($"ExitPoint_{point.x}_{point.y}"); go.transform.parent = transform; go.transform.position = new Vector3(point.x + 0.5f, point.y + 0.5f, -1); if (exitMarkerSprite != null) { var spriteRenderer = go.AddComponent(); spriteRenderer.sprite = exitMarkerSprite; spriteRenderer.color = new Color(1, 0, 0, 0.7f); } } } /// /// Renders room boundaries (disabled by default for performance) /// private void RenderRoomBoundaries(MazeData maze) { foreach (var room in maze.Rooms) { // You could draw gizmo lines or use wireframe renderer Debug.DrawLine(new Vector3(room.MinX, room.MinY, 0), new Vector3(room.MaxX, room.MinY, 0), Color.cyan); Debug.DrawLine(new Vector3(room.MaxX, room.MinY, 0), new Vector3(room.MaxX, room.MaxY, 0), Color.cyan); Debug.DrawLine(new Vector3(room.MaxX, room.MaxY, 0), new Vector3(room.MinX, room.MaxY, 0), Color.cyan); Debug.DrawLine(new Vector3(room.MinX, room.MaxY, 0), new Vector3(room.MinX, room.MinY, 0), Color.cyan); } } /// /// Clears all tilemaps /// private void ClearTilemaps() { if (combinedTilemap != null) { combinedTilemap.ClearAllTiles(); } else { if (floorTilemap != null) floorTilemap.ClearAllTiles(); if (wallTilemap != null) wallTilemap.ClearAllTiles(); } if (decorationTilemap != null) decorationTilemap.ClearAllTiles(); // Clear markers foreach (Transform child in transform) { Destroy(child.gameObject); } } /// /// Generates placeholder tiles at runtime if none are assigned /// private void GeneratePlaceholderTiles() { floorTile = TileGenerator.CreateTile(TileGenerator.CreateColoredSprite(new Color(0.8f, 0.8f, 0.8f), "Floor")); floorCenterTile = floorTile; floorEdgeTile = floorTile; floorCornerTile = floorTile; wallTile = TileGenerator.CreateTile(TileGenerator.CreateColoredSprite(new Color(0.3f, 0.3f, 0.3f), "Wall")); wallCenterTile = wallTile; wallVerticalTile = wallTile; wallHorizontalTile = wallTile; wallCornerNW = wallTile; wallCornerNE = wallTile; wallCornerSE = wallTile; wallCornerSW = wallTile; wallCornerGenericTile = wallTile; wallEndNorthTile = wallTile; wallEndEastTile = wallTile; wallEndSouthTile = wallTile; wallEndWestTile = wallTile; wallEndTile = wallTile; wallTJNorthTile = wallTile; wallTJEastTile = wallTile; wallTJSouthTile = wallTile; wallTJWestTile = wallTile; wallTTile = wallTile; wallCrossTile = wallTile; swampTile = TileGenerator.CreateTile(TileGenerator.CreateCheckerSprite(new Color(0.5f, 0.8f, 0.3f), new Color(0.4f, 0.6f, 0.2f), "Swamp")); stoneTile = TileGenerator.CreateTile(TileGenerator.CreateCheckerSprite(new Color(0.7f, 0.7f, 0.7f), new Color(0.6f, 0.6f, 0.6f), "Stone")); lavaTile = TileGenerator.CreateTile(TileGenerator.CreateColoredSprite(new Color(1.0f, 0.4f, 0.0f), "Lava")); // Rebuild the terrain dictionary SetupTerrainDictionary(); Debug.Log("Generated placeholder tiles for maze rendering"); } }