using System; using System.Collections.Generic; using GeometryTD.Pathfinding; using UnityEngine; using UnityEngine.Tilemaps; using UnityGameFramework.Runtime; namespace GeometryTD.Map { public sealed class MapTopologyService { private const string PathTileName = "Path"; private const string FoundationTileName = "Foundation"; private readonly List _pathCells = new(); private readonly List _foundationCells = new(); private readonly HashSet _pathCellSet = new(); private readonly HashSet _foundationCellSet = new(); private readonly IMapPathfinder _mapPathfinder = new GridMapPathfinder(); private readonly List _pathCellBuffer = new(); private readonly Dictionary _spawnerPathStartByRef = new(); private readonly Dictionary> _defaultPathCellsBySpawner = new(); private bool _hasHousePathCell; private Vector3Int _housePathCell; public IReadOnlyList PathCells => _pathCells; public IReadOnlyList FoundationCells => _foundationCells; public void Refresh(Tilemap tilemap, Spawner[] spawners, House house, string mapName, int levelId) { Clear(); if (tilemap == null) { return; } BoundsInt bounds = tilemap.cellBounds; foreach (Vector3Int cellPosition in bounds.allPositionsWithin) { TileBase tile = tilemap.GetTile(cellPosition); if (tile == null || string.IsNullOrEmpty(tile.name)) { continue; } if (string.Equals(tile.name, PathTileName, StringComparison.Ordinal)) { _pathCells.Add(cellPosition); _pathCellSet.Add(cellPosition); continue; } if (string.Equals(tile.name, FoundationTileName, StringComparison.Ordinal)) { _foundationCells.Add(cellPosition); _foundationCellSet.Add(cellPosition); } } RefreshPathCache(tilemap, spawners, house, mapName); Log.Info( "Map '{0}' initialized. LevelId={1}, PathCells={2}, FoundationCells={3}, Spawners={4}, House={5}, Routes={6}.", mapName, levelId, _pathCells.Count, _foundationCells.Count, spawners != null ? spawners.Length : 0, house != null ? house.name : "None", _defaultPathCellsBySpawner.Count); } public void Clear() { _pathCells.Clear(); _foundationCells.Clear(); _pathCellSet.Clear(); _foundationCellSet.Clear(); _pathCellBuffer.Clear(); _hasHousePathCell = false; _housePathCell = default; _spawnerPathStartByRef.Clear(); _defaultPathCellsBySpawner.Clear(); } public bool IsPathCell(Vector3Int cellPosition) { return _pathCellSet.Contains(cellPosition); } public bool IsFoundationCell(Vector3Int cellPosition) { return _foundationCellSet.Contains(cellPosition); } public bool TryGetNearestPathCell(Tilemap tilemap, Vector3 worldPosition, out Vector3Int pathCell) { pathCell = default; if (_pathCells.Count <= 0 || tilemap == null) { return false; } Vector3Int directCell = tilemap.WorldToCell(worldPosition); if (_pathCellSet.Contains(directCell)) { pathCell = directCell; return true; } float minDistance = float.MaxValue; foreach (var candidate in _pathCells) { float distance = (tilemap.GetCellCenterWorld(candidate) - worldPosition).sqrMagnitude; if (distance >= minDistance) { continue; } minDistance = distance; pathCell = candidate; } return minDistance < float.MaxValue; } public Vector3 GetPathCellCenterWorld(Tilemap tilemap, Vector3Int pathCell) { return tilemap != null ? tilemap.GetCellCenterWorld(pathCell) : Vector3.zero; } public bool TryGetDefaultPathCells(Spawner spawner, out IReadOnlyList pathCells) { pathCells = null; if (spawner == null) { return false; } if (!_defaultPathCellsBySpawner.TryGetValue(spawner, out List cachedPathCells)) { return false; } pathCells = cachedPathCells; return true; } public bool TryFindPathCells(Spawner spawner, IReadOnlyCollection blockedCells, List pathResult) { if (pathResult == null) { return false; } pathResult.Clear(); if (spawner == null || !_hasHousePathCell) { return false; } if (!_spawnerPathStartByRef.TryGetValue(spawner, out Vector3Int startCell)) { return false; } return _mapPathfinder.TryFindPath(_pathCells, startCell, _housePathCell, blockedCells, pathResult); } public bool TryFindPathWorldPoints(Tilemap tilemap, Spawner spawner, IReadOnlyCollection blockedCells, List worldPathResult) { if (worldPathResult == null) { return false; } worldPathResult.Clear(); if (tilemap == null) { return false; } if (!TryFindPathCells(spawner, blockedCells, _pathCellBuffer)) { return false; } foreach (Vector3Int pos in _pathCellBuffer) { worldPathResult.Add(GetPathCellCenterWorld(tilemap, pos)); } return true; } private void RefreshPathCache(Tilemap tilemap, Spawner[] spawners, House house, string mapName) { _hasHousePathCell = false; _housePathCell = default; _spawnerPathStartByRef.Clear(); _defaultPathCellsBySpawner.Clear(); if (house == null) { Log.Warning("Map '{0}' has no house reference, path cache skipped.", mapName); return; } if (!TryGetNearestPathCell(tilemap, house.Position, out _housePathCell)) { Log.Warning("Map '{0}' house position can not map to a valid path cell.", mapName); return; } _hasHousePathCell = true; if (spawners == null) { return; } foreach (Spawner spawner in spawners) { if (spawner == null) { continue; } if (!TryGetNearestPathCell(tilemap, spawner.Position, out Vector3Int startCell)) { Log.Warning("Map '{0}' spawner '{1}' can not map to a valid path cell.", mapName, spawner.name); continue; } _spawnerPathStartByRef[spawner] = startCell; List defaultPathCells = new(); if (!_mapPathfinder.TryFindPath(_pathCells, startCell, _housePathCell, null, defaultPathCells)) { Log.Warning( "Map '{0}' spawner '{1}' has no path to house cell {2}.", mapName, spawner.name, _housePathCell); continue; } _defaultPathCellsBySpawner[spawner] = defaultPathCells; } } } }