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");
}
}