using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Tilemaps;
/// <summary>
/// GAME 1303 Tier 5 — Builds a maze from a text layout. Weenie Maze; also handy for
/// Last Delivery's street grid, or any level you'd rather type than click.
///
/// WHY: drawing a maze tile by tile in the Scene view is slow to iterate on. With this,
/// a level is a block of text you can edit in seconds and even paste from a spreadsheet.
///
/// LAYOUT FORMAT (one character per cell, top row first)
/// ###########
/// #.........#
/// #.###.###.#
/// #....P....#
/// #.###.###.#
/// #.........#
/// ###########
/// # wall . or space empty
/// P player start T treat spawn point E exit
/// 1-9 extra markers you can map to any prefab (monsters, doors, pickups)
///
/// SETUP
/// 1. Empty GameObject + this script.
/// 2. Either paste the layout into Layout Text (multi-line box in the Inspector) or drag
/// in a TextAsset (.txt file in your project — easier for big mazes).
/// 3. Assign either a Wall Tilemap + Wall Tile (fast, tidy) OR a Wall Prefab (simple).
/// 4. Cell Size must match your GridMover's Cell Size.
/// 5. Marker Prefabs: add entries mapping a character to a prefab. 'P' moves the existing
/// player instead of spawning one, unless you assign a player prefab to it.
/// 6. Build On Start is on by default. Call Build() again from a button to rebuild.
///
/// The maze is centred on this object, so put it at the origin and the middle of your
/// layout lands there.
/// </summary>
public class MazeLoader : MonoBehaviour
{
[System.Serializable]
public class MarkerPrefab
{
[Tooltip("Single character in the layout, e.g. T")]
public string character = "T";
public GameObject prefab;
[Tooltip("Leave the object where the maze places it, or just record the position?")]
public bool spawnPrefab = true;
}
[Header("Layout (use one)")]
[TextArea(6, 20)] public string layoutText = "";
public TextAsset layoutFile;
[Header("Grid")]
public float cellSize = 1f;
public bool centreOnThisObject = true;
[Header("Walls (use one)")]
public Tilemap wallTilemap;
public TileBase wallTile;
public GameObject wallPrefab;
public char wallChar = '#';
[Header("Markers")]
public List<MarkerPrefab> markerPrefabs = new List<MarkerPrefab>();
public char playerChar = 'P';
[Tooltip("Move the existing tagged Player to the P cell instead of spawning one.")]
public bool movePlayerToStart = true;
[Header("Build")]
public bool buildOnStart = true;
public Transform wallParent;
[Header("Events")]
public UnityEvent OnBuilt;
public Vector3 PlayerStart { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
/// <summary>Every position found for a given marker character, after Build().</summary>
public Dictionary<char, List<Vector3>> MarkerPositions { get; private set; } = new Dictionary<char, List<Vector3>>();
private readonly List<GameObject> spawned = new List<GameObject>();
void Start()
{
if (buildOnStart) Build();
}
public void Build()
{
Clear();
string raw = layoutFile != null ? layoutFile.text : layoutText;
if (string.IsNullOrWhiteSpace(raw)) { Debug.LogWarning("MazeLoader: no layout given."); return; }
string[] rows = raw.Replace("\r", "").Split('\n');
// Drop trailing blank lines
int last = rows.Length - 1;
while (last > 0 && rows[last].Trim().Length == 0) last--;
Height = last + 1;
Width = 0;
for (int i = 0; i <= last; i++) Width = Mathf.Max(Width, rows[i].Length);
Vector3 origin = transform.position;
if (centreOnThisObject)
origin -= new Vector3((Width - 1) * cellSize * 0.5f, -(Height - 1) * cellSize * 0.5f, 0f);
for (int y = 0; y <= last; y++)
{
string row = rows[y];
for (int x = 0; x < row.Length; x++)
{
char c = row[x];
if (c == ' ' || c == '.') continue;
// Row 0 is the TOP, so y goes negative as we read down
Vector3 pos = origin + new Vector3(x * cellSize, -y * cellSize, 0f);
if (c == wallChar) { PlaceWall(pos); continue; }
if (!MarkerPositions.ContainsKey(c)) MarkerPositions[c] = new List<Vector3>();
MarkerPositions[c].Add(pos);
if (c == playerChar)
{
PlayerStart = pos;
if (movePlayerToStart)
{
var p = GameObject.FindGameObjectWithTag("Player");
if (p != null) p.transform.position = pos;
}
}
foreach (var m in markerPrefabs)
{
if (string.IsNullOrEmpty(m.character) || m.character[0] != c) continue;
if (m.spawnPrefab && m.prefab != null)
spawned.Add(Instantiate(m.prefab, pos, Quaternion.identity, transform));
}
}
}
OnBuilt?.Invoke();
}
void PlaceWall(Vector3 pos)
{
if (wallTilemap != null && wallTile != null)
{
Vector3Int cell = wallTilemap.WorldToCell(pos);
wallTilemap.SetTile(cell, wallTile);
}
else if (wallPrefab != null)
{
var parent = wallParent != null ? wallParent : transform;
spawned.Add(Instantiate(wallPrefab, pos, Quaternion.identity, parent));
}
}
public void Clear()
{
foreach (var o in spawned) if (o != null) DestroyImmediate(o);
spawned.Clear();
MarkerPositions.Clear();
if (wallTilemap != null) wallTilemap.ClearAllTiles();
}
/// <summary>All positions for a marker, e.g. GetPositions('T') for treat spawn points.</summary>
public List<Vector3> GetPositions(char marker) =>
MarkerPositions.TryGetValue(marker, out var list) ? list : new List<Vector3>();
}