using System; using System.Collections.Generic; using GeometryTD.Entity.EntityData; using GeometryTD.Pathfinding; using UnityEngine; using UnityEngine.Tilemaps; using UnityGameFramework.Runtime; namespace GeometryTD.Entity { public class MapEntity : EntityBase { private const string PathTileName = "Path"; private const string FoundationTileName = "Foundation"; private static readonly Spawner[] EmptySpawners = Array.Empty(); 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 MapDataRefs _mapDataRefs; private MapData _mapData; private bool _hasHousePathCell; private Vector3Int _housePathCell; public IReadOnlyList PathCells => _pathCells; public IReadOnlyList FoundationCells => _foundationCells; public Tilemap Tilemap => _mapDataRefs != null ? _mapDataRefs.Tilemap : null; public Spawner[] Spawners => _mapDataRefs?.Spawners ?? EmptySpawners; public House House => _mapDataRefs?.House; public bool IsPathCell(Vector3Int cellPosition) { return _pathCellSet.Contains(cellPosition); } public bool IsFoundationCell(Vector3Int cellPosition) { return _foundationCellSet.Contains(cellPosition); } public bool TryGetNearestPathCell(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; for (int i = 0; i < _pathCells.Count; i++) { Vector3Int candidate = _pathCells[i]; float distance = (Tilemap.GetCellCenterWorld(candidate) - worldPosition).sqrMagnitude; if (distance >= minDistance) { continue; } minDistance = distance; pathCell = candidate; } return minDistance < float.MaxValue; } public Vector3 GetPathCellCenterWorld(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(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 (var pos in _pathCellBuffer) { worldPathResult.Add(GetPathCellCenterWorld(pos)); } return true; } protected override void OnInit(object userData) { base.OnInit(userData); _mapDataRefs = GetComponent(); if (_mapDataRefs == null) { Log.Error("MapDataRefs is missing on map entity '{0}'.", name); } } protected override void OnShow(object userData) { base.OnShow(userData); _mapData = userData as MapData; if (_mapData == null) { Log.Warning("MapData is invalid for map entity '{0}'.", Id); } RefreshTiles(); } protected override void OnHide(bool isShutdown, object userData) { ClearRuntimeData(); base.OnHide(isShutdown, userData); } private void RefreshTiles() { ClearRuntimeData(); if (_mapDataRefs == null) { _mapDataRefs = GetComponent(); if (_mapDataRefs == null) { Log.Error("MapDataRefs is missing on map entity '{0}'.", name); return; } } Tilemap tilemap = _mapDataRefs.Tilemap; if (tilemap == null) { Log.Error("Tilemap reference is missing in MapDataRefs on '{0}'.", name); 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(); Log.Info( "Map '{0}' initialized. LevelId={1}, PathCells={2}, FoundationCells={3}, Spawners={4}, House={5}, Routes={6}.", name, _mapData != null ? _mapData.LevelId : 0, _pathCells.Count, _foundationCells.Count, Spawners.Length, House != null ? House.name : "None", _defaultPathCellsBySpawner.Count); } private void RefreshPathCache() { _hasHousePathCell = false; _housePathCell = default; _spawnerPathStartByRef.Clear(); _defaultPathCellsBySpawner.Clear(); if (House == null) { Log.Warning("Map '{0}' has no house reference, path cache skipped.", name); return; } if (!TryGetNearestPathCell(House.Position, out _housePathCell)) { Log.Warning("Map '{0}' house position can not map to a valid path cell.", name); return; } _hasHousePathCell = true; Spawner[] spawners = Spawners; foreach (var spawner in spawners) { if (spawner == null) { continue; } if (!TryGetNearestPathCell(spawner.Position, out Vector3Int startCell)) { Log.Warning("Map '{0}' spawner '{1}' can not map to a valid path cell.", name, spawner.name); continue; } _spawnerPathStartByRef[spawner] = startCell; List defaultPathCells = new List(); if (!_mapPathfinder.TryFindPath(_pathCells, startCell, _housePathCell, null, defaultPathCells)) { Log.Warning( "Map '{0}' spawner '{1}' has no path to house cell {2}.", name, spawner.name, _housePathCell); continue; } _defaultPathCellsBySpawner[spawner] = defaultPathCells; } } private void ClearRuntimeData() { _pathCells.Clear(); _foundationCells.Clear(); _pathCellSet.Clear(); _foundationCellSet.Clear(); _pathCellBuffer.Clear(); _hasHousePathCell = false; _housePathCell = default; _spawnerPathStartByRef.Clear(); _defaultPathCellsBySpawner.Clear(); } } }