using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace OctoCore.LevelBuilder { [ExecuteAlways] public class OctoLevelBuilderMap : MonoBehaviour { [System.Serializable] private class MapRow { public OctoLevelBuilderTile[] Rows; } [System.Serializable] private class MapColumn { public MapRow[] Columns; } [System.Serializable] private class Map { private bool _isMapUpToDate; [SerializeField] private Vector3Int _minBounds; [SerializeField] private Vector3Int _maxBounds; [SerializeField] private Transform _tilesRoot; [SerializeField] private List _tiles = new List(); private List _tilesToRefresh = new List(); [SerializeField] private MapColumn[] _floors = new MapColumn[0]; public Transform TilesRoot => _tilesRoot; public OctoLevelBuilderTile[] Tiles => _tiles.ToArray(); public void SetTilesRoot(Transform tilesRoot) { _tilesRoot = tilesRoot; } public void RequireMapRefresh() { _isMapUpToDate = false; } public void RequireTileRefresh(OctoLevelBuilderTile tile) { if (tile && !_tilesToRefresh.Contains(tile)) _tilesToRefresh.Add(tile); } public void AddTile(OctoLevelBuilderTile tile) { if (!_tiles.Contains(tile)) { _tiles.Add(tile); RequireMapRefresh(); } } public void RemoveTile(OctoLevelBuilderTile tile) { if (_tiles.Contains(tile)) { _tiles.Remove(tile); RequireMapRefresh(); } } public OctoLevelBuilderTile GetTile(Vector3Int position) { Vector3Int p = position - _minBounds; if (p.y < 0 || p.y >= _floors.Length) return null; if (p.z < 0 || p.z >= _floors[p.y].Columns.Length) return null; if (p.x < 0 || p.x >= _floors[p.y].Columns[p.z].Rows.Length) return null; return _floors[p.y].Columns[p.z].Rows[p.x]; } public void RefreshMap(string name) { if (_isMapUpToDate) return; _minBounds = new Vector3Int(int.MaxValue, int.MaxValue, int.MaxValue); _maxBounds = new Vector3Int(int.MinValue, int.MinValue, int.MinValue); // Get map bounds foreach (OctoLevelBuilderTile tile in _tiles) { Vector3Int p = tile.GetCoordinates(true); if (p.x < _minBounds.x) _minBounds.x = p.x; if (p.x > _maxBounds.x) _maxBounds.x = p.x; if (p.y < _minBounds.y) _minBounds.y = p.y; if (p.y > _maxBounds.y) _maxBounds.y = p.y; if (p.z < _minBounds.z) _minBounds.z = p.z; if (p.z > _maxBounds.z) _maxBounds.z = p.z; } // Copy previous map MapColumn[] previousFloors = new MapColumn[_floors.Length]; for (int y = 0; y < previousFloors.Length; y++) { previousFloors[y] = new MapColumn(); previousFloors[y].Columns = new MapRow[_floors[y].Columns.Length]; for (int z = 0; z < previousFloors[y].Columns.Length; z++) { previousFloors[y].Columns[z] = new MapRow(); previousFloors[y].Columns[z].Rows = new OctoLevelBuilderTile[_floors[y].Columns[z].Rows.Length]; for (int x = 0; x < previousFloors[y].Columns[z].Rows.Length; x++) previousFloors[y].Columns[z].Rows[x] = _floors[y].Columns[z].Rows[x]; } } // Init map Vector3Int size = _maxBounds - _minBounds + Vector3Int.one; _floors = new MapColumn[size.y]; for (int y = 0; y < size.y; y++) { _floors[y] = new MapColumn(); _floors[y].Columns = new MapRow[size.z]; for (int z = 0; z < size.z; z++) { _floors[y].Columns[z] = new MapRow(); _floors[y].Columns[z].Rows = new OctoLevelBuilderTile[size.x]; } } // Assing tiles to the map foreach (OctoLevelBuilderTile tile in _tiles) { Vector3Int p = tile.GetCoordinates(true) - _minBounds; _floors[p.y].Columns[p.z].Rows[p.x] = tile; } // Refresh tiles for (int y = 0; y < size.y; y++) { for (int z = 0; z < size.z; z++) { for (int x = 0; x < size.x; x++) { OctoLevelBuilderTile tile = _floors[y].Columns[z].Rows[x]; if (tile) { bool refreshTile = !tile.IsUpToDate; if (!refreshTile) { OctoLevelBuilderTile previousTile = null; if (previousFloors.Length > y && previousFloors[y].Columns.Length > z && previousFloors[y].Columns[z].Rows.Length > x) previousTile = previousFloors[y].Columns[z].Rows[x]; refreshTile = tile != previousTile; } if (refreshTile) { RequireTileRefresh(tile); } } } } } _isMapUpToDate = true; Debug.Log($"[OctoLevelGrid] {name}: Updated"); } public void RefreshTiles() { if (_tilesToRefresh.Count == 0) return; OctoLevelBuilderTile[] _tmpTilesToRefresh = _tilesToRefresh.ToArray(); _tilesToRefresh.Clear(); foreach (OctoLevelBuilderTile tile in _tmpTilesToRefresh) if (tile) tile.Refresh(); } } public static float DEFAULT_TILE_SIZE = 2f; public static float DEFAULT_FLOOR_HEIGHT = 3.5f; [SerializeField] private OctoLevelBuilderStyleData _defaultStyle; [SerializeField, OctoReadOnly] private Map _map; public OctoLevelBuilderStyleData DefaultStyle => _defaultStyle; public OctoLevelBuilderGridData Grid => DefaultStyle ? DefaultStyle.Grid : null; public float TileSize => Grid ? Grid.TileSize : DEFAULT_TILE_SIZE; public float FloorHeight => Grid ? Grid.FloorHeight : DEFAULT_FLOOR_HEIGHT; void LateUpdate() { if (Application.isPlaying) return; _map.RefreshTiles(); _map.RefreshMap(name); } public void SetLayer(string layer) { LayerMask layerMask = LayerMask.NameToLayer(layer); SetLayerRecursively(gameObject, layerMask); } public void RegisterTile(OctoLevelBuilderTile tile) { if (tile == null) return; _map.AddTile(tile); } public void UnregisterTile(OctoLevelBuilderTile tile) { if (tile == null) return; _map.RemoveTile(tile); } public void RequireMapRefresh() { _map.RequireMapRefresh(); } public void RequireTileRefresh(OctoLevelBuilderTile tile) { if (tile) _map.RequireTileRefresh(tile); } public bool GetTile(Vector3Int gridPosition, out OctoLevelBuilderTile tile) { tile = _map.GetTile(gridPosition); return tile; } public Vector3 GridToWorld(Vector3Int gridPosition) { return new Vector3( gridPosition.x * TileSize, gridPosition.y * FloorHeight, gridPosition.z * TileSize ); } public Vector3Int WorldToGrid(Vector3 worldPosition, int floorLevel) { return new Vector3Int( Mathf.RoundToInt(worldPosition.x / TileSize), floorLevel, Mathf.RoundToInt(worldPosition.z / TileSize) ); } [ContextMenu("GenerateMap (5x5)")] public void GenerateMap5x5() { GenerateMap(new Vector3Int(5, 1, 5)); } [ContextMenu("GenerateMap (7x7)")] public void GenerateMap7x7() { GenerateMap(new Vector3Int(7, 1, 7)); } [ContextMenu("GenerateMap (9x9)")] public void GenerateMap9x9() { GenerateMap(new Vector3Int(9, 1, 9)); } public void GenerateMap(Vector3Int size) { if (!_map.TilesRoot) { GameObject tilesRoot = new GameObject("Tiles"); tilesRoot.transform.parent = transform; _map.SetTilesRoot(tilesRoot.transform); } int xMin = size.x / -2; int yMin = size.y / -2; int zMin = size.z / -2; int xMax = xMin + size.x; int yMax = yMin + size.y; int zMax = zMin + size.z; for (int y = yMin; y < yMax; y++) { for (int z = zMin; z < zMax; z++) { for (int x = xMin; x < xMax; x++) { GameObject tileObj = new GameObject(); tileObj.transform.parent = _map.TilesRoot; tileObj.transform.localPosition = new Vector3(x * TileSize, y * FloorHeight, z * TileSize); OctoLevelBuilderTile tile = tileObj.AddComponent(); tile.Init(x == xMin || x == xMax - 1 || z == zMin || z == zMax - 1); } } } Debug.Log("[OctoLevelBluiderMap] Map generated"); } private void SetLayerRecursively(GameObject obj, int newLayer) { obj.layer = newLayer; foreach (Transform child in obj.transform) { SetLayerRecursively(child.gameObject, newLayer); } } } }