| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- using UnityEngine;
- using UnityEngine.Tilemaps;
- using System.Collections.Generic;
- /// <summary>
- /// Renders large mazes using chunked tilemaps to prevent performance issues
- /// Only renders chunks that are visible or near the camera
- /// </summary>
- [RequireComponent(typeof(Grid))]
- public class ChunkedMazeRenderer : MonoBehaviour
- {
- [Header("Maze Setup")]
- [SerializeField] private MazeController mazeController;
- [Header("Chunk Settings")]
- [SerializeField] private int chunkSize = 32;
- [SerializeField] private int renderDistance = 2; // chunks around camera
- [SerializeField] private Transform cameraTransform;
- [SerializeField] private bool useXZPlane = false;
- [Header("Tile Assets")]
- [SerializeField] private TileBase floorTile;
- [SerializeField] private TileBase wallTile;
- [SerializeField] private TileBase swampTile;
- [SerializeField] private TileBase stoneTile;
- [Header("Markers")]
- [SerializeField] private Sprite startMarkerSprite;
- [SerializeField] private Sprite exitMarkerSprite;
- [SerializeField] private bool showStartEndMarkers = true;
- [Header("Debug")]
- [SerializeField] private bool showChunkBounds = false;
- private Dictionary<Vector2Int, Tilemap> activeChunks = new();
- private Dictionary<MazeTile.TerrainType, TileBase> terrainTiles;
- private Vector2Int lastCameraChunk;
- private GameObject markerContainer;
- void Start()
- {
- SetupTerrainDictionary();
- if (cameraTransform == null)
- {
- cameraTransform = Camera.main?.transform;
- }
- EnsurePlaceholderTiles();
- }
- void Update()
- {
- if (cameraTransform != null)
- {
- UpdateVisibleChunks();
- }
- }
- void SetupTerrainDictionary()
- {
- terrainTiles = new Dictionary<MazeTile.TerrainType, TileBase>
- {
- { MazeTile.TerrainType.Normal, floorTile },
- { MazeTile.TerrainType.Swamp, swampTile ?? floorTile },
- { MazeTile.TerrainType.Stone, stoneTile ?? floorTile },
- };
- }
- /// <summary>
- /// Updates which chunks are visible and renders them
- /// </summary>
- private void UpdateVisibleChunks()
- {
- var maze = mazeController.GetCurrentMaze();
- if (maze == null) return;
- Vector2Int cameraChunk = WorldToChunk(cameraTransform.position);
- // Only update if camera moved to a different chunk
- if (cameraChunk != lastCameraChunk)
- {
- lastCameraChunk = cameraChunk;
- RenderVisibleChunks(maze, cameraChunk);
- }
- }
- /// <summary>
- /// Converts world position to chunk coordinates
- /// </summary>
- private Vector2Int WorldToChunk(Vector3 worldPos)
- {
- if (useXZPlane)
- {
- return new Vector2Int(
- Mathf.FloorToInt(worldPos.x / chunkSize),
- Mathf.FloorToInt(worldPos.z / chunkSize)
- );
- }
- return new Vector2Int(
- Mathf.FloorToInt(worldPos.x / chunkSize),
- Mathf.FloorToInt(worldPos.y / chunkSize)
- );
- }
- /// <summary>
- /// Renders chunks within render distance of camera
- /// </summary>
- private void RenderVisibleChunks(MazeData maze, Vector2Int centerChunk)
- {
- HashSet<Vector2Int> chunksToKeep = new();
- // Determine which chunks should be visible
- for (int x = centerChunk.x - renderDistance; x <= centerChunk.x + renderDistance; x++)
- {
- for (int y = centerChunk.y - renderDistance; y <= centerChunk.y + renderDistance; y++)
- {
- Vector2Int chunkPos = new Vector2Int(x, y);
- chunksToKeep.Add(chunkPos);
- if (!activeChunks.ContainsKey(chunkPos))
- {
- CreateAndRenderChunk(maze, chunkPos);
- }
- }
- }
- // Remove chunks that are no longer visible
- List<Vector2Int> chunksToRemove = new();
- foreach (var chunk in activeChunks.Keys)
- {
- if (!chunksToKeep.Contains(chunk))
- {
- chunksToRemove.Add(chunk);
- }
- }
- foreach (var chunk in chunksToRemove)
- {
- Destroy(activeChunks[chunk].gameObject);
- activeChunks.Remove(chunk);
- }
- UpdateMarkers();
- }
- /// <summary>
- /// Creates and renders a single chunk
- /// </summary>
- private void CreateAndRenderChunk(MazeData maze, Vector2Int chunkPos)
- {
- GameObject chunkObj = new GameObject($"Chunk_{chunkPos.x}_{chunkPos.y}");
- chunkObj.transform.parent = transform;
- chunkObj.transform.localPosition = GetChunkWorldPosition(chunkPos);
- chunkObj.transform.localRotation = useXZPlane ? Quaternion.Euler(90, 0, 0) : Quaternion.identity;
- Tilemap tilemap = chunkObj.AddComponent<Tilemap>();
- TilemapRenderer renderer = chunkObj.AddComponent<TilemapRenderer>();
- renderer.sortingOrder = 0;
- if (showChunkBounds)
- {
- AddChunkBounds(chunkObj, chunkSize);
- }
- // Render tiles in this chunk
- int startX = chunkPos.x * chunkSize;
- int startY = chunkPos.y * chunkSize;
- int endX = Mathf.Min(startX + chunkSize, maze.Width);
- int endY = Mathf.Min(startY + chunkSize, maze.Height);
- for (int x = startX; x < endX; x++)
- {
- for (int y = startY; y < endY; y++)
- {
- var tile = maze.GetTile(x, y);
- if (tile != null)
- {
- Vector3Int localPos = new Vector3Int(x - startX, y - startY, 0);
- TileBase tileToSet = GetTileForMazeTile(tile);
- if (tileToSet != null)
- {
- tilemap.SetTile(localPos, tileToSet);
- }
- }
- }
- }
- activeChunks[chunkPos] = tilemap;
- }
- private void UpdateMarkers()
- {
- if (!showStartEndMarkers) return;
- if (markerContainer != null)
- {
- Destroy(markerContainer);
- }
- markerContainer = new GameObject("MazeMarkers");
- markerContainer.transform.parent = transform;
- markerContainer.transform.localPosition = Vector3.zero;
- var maze = mazeController?.GetCurrentMaze();
- if (maze == null) return;
- foreach (var start in maze.StartPoints)
- {
- CreateMarker(start, startMarkerSprite, Color.green, "StartPoint_");
- }
- foreach (var exit in maze.ExitPoints)
- {
- CreateMarker(exit, exitMarkerSprite, Color.red, "ExitPoint_");
- }
- }
- private void CreateMarker(Vector2Int position, Sprite markerSprite, Color color, string prefix)
- {
- var go = new GameObject(prefix + position.x + "_" + position.y);
- go.transform.parent = markerContainer.transform;
- go.transform.position = GetMarkerWorldPosition(position);
- go.transform.localScale = Vector3.one * 1.5f;
- var spriteRenderer = go.AddComponent<SpriteRenderer>();
- spriteRenderer.sprite = markerSprite ?? TileGenerator.CreateColoredSprite(color, prefix.TrimEnd('_'));
- spriteRenderer.color = new Color(color.r, color.g, color.b, 0.9f);
- spriteRenderer.sortingOrder = 100;
- spriteRenderer.material = new Material(Shader.Find("Sprites/Default"));
- }
- /// <summary>
- /// Gets the appropriate tile for a maze tile
- /// </summary>
- private TileBase GetTileForMazeTile(MazeTile mazeTile)
- {
- if (mazeTile.Type == MazeTile.TileType.Wall)
- {
- return wallTile;
- }
- else if (mazeTile.Type == MazeTile.TileType.Floor || mazeTile.Type == MazeTile.TileType.Terrain)
- {
- if (terrainTiles.TryGetValue(mazeTile.Terrain, out var terrainTile))
- {
- return terrainTile;
- }
- return floorTile;
- }
- return null;
- }
- private void EnsurePlaceholderTiles()
- {
- if (floorTile == null)
- {
- floorTile = TileGenerator.CreateTile(TileGenerator.CreateColoredSprite(new Color(0.8f, 0.8f, 0.8f), "Floor"));
- }
- if (wallTile == null)
- {
- wallTile = TileGenerator.CreateTile(TileGenerator.CreateColoredSprite(new Color(0.3f, 0.3f, 0.3f), "Wall"));
- }
- if (swampTile == null)
- {
- swampTile = TileGenerator.CreateTile(TileGenerator.CreateCheckerSprite(new Color(0.5f, 0.8f, 0.3f), new Color(0.4f, 0.6f, 0.2f), "Swamp"));
- }
- if (stoneTile == null)
- {
- stoneTile = TileGenerator.CreateTile(TileGenerator.CreateCheckerSprite(new Color(0.7f, 0.7f, 0.7f), new Color(0.6f, 0.6f, 0.6f), "Stone"));
- }
- SetupTerrainDictionary();
- }
- private Vector3 GetChunkWorldPosition(Vector2Int chunkPos)
- {
- if (useXZPlane)
- {
- return new Vector3(chunkPos.x * chunkSize, 0, chunkPos.y * chunkSize);
- }
- return new Vector3(chunkPos.x * chunkSize, chunkPos.y * chunkSize, 0);
- }
- private Vector3 GetMarkerWorldPosition(Vector2Int position)
- {
- if (useXZPlane)
- {
- return new Vector3(position.x + 0.5f, 0.5f, position.y + 0.5f);
- }
- return new Vector3(position.x + 0.5f, position.y + 0.5f, -0.5f);
- }
- /// <summary>
- /// Adds visual bounds for debugging chunks
- /// </summary>
- private void AddChunkBounds(GameObject chunkObj, int size)
- {
- GameObject bounds = new GameObject("Bounds");
- bounds.transform.parent = chunkObj.transform;
- bounds.transform.localPosition = Vector3.zero;
- LineRenderer lineRenderer = bounds.AddComponent<LineRenderer>();
- lineRenderer.positionCount = 5;
- lineRenderer.SetPositions(new Vector3[]
- {
- new Vector3(0, 0, 0),
- new Vector3(size, 0, 0),
- new Vector3(size, size, 0),
- new Vector3(0, size, 0),
- new Vector3(0, 0, 0)
- });
- lineRenderer.startWidth = 0.1f;
- lineRenderer.endWidth = 0.1f;
- lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
- lineRenderer.startColor = Color.red;
- lineRenderer.endColor = Color.red;
- }
- /// <summary>
- /// Forces a re-render of all visible chunks
- /// </summary>
- public void RefreshVisibleChunks()
- {
- foreach (var chunk in activeChunks.Values)
- {
- Destroy(chunk.gameObject);
- }
- activeChunks.Clear();
- lastCameraChunk = new Vector2Int(int.MinValue, int.MinValue); // Force update
- }
- }
|