Updating HUD, Gold Config, and finishing off Play flow for 9player map.

This commit is contained in:
Matt F 2026-05-22 12:18:23 -07:00
parent a7be12fa9b
commit 3dcc0e7edd
28 changed files with 2272 additions and 9601 deletions

View file

@ -61,6 +61,17 @@ namespace TD.Gameplay
// Built once on Start from LevelData.Goals[].TileArea.
private HashSet<Vector2Int> goalTiles;
// Precomputed octile-distance-to-nearest-goal, indexed by [y * gridWidth + x] in
// grid-local space (subtract GridOriginTile to convert world-tile → array index).
// Computed ONCE on Start. Without this, the A* heuristic scanned every goal tile
// (~48 tiles for the 9-player map) on every node visit — making the heuristic
// O(node visits * goal count) per A* run. With this table, it's O(1) per node.
// Tiles outside the grid use Heuristic's fallback path (per-goal scan); they are
// rare so the table isn't extended to cover them.
private float[] heuristicTable;
private Vector2Int heuristicOrigin;
private Vector2Int heuristicSize;
// A* scratch collections — allocated once and cleared per run to avoid GC.
// PathfindingService is a singleton, so single-instance scratch is safe.
// gScore is float to support diagonal cost √2; the priority queue matches.
@ -85,6 +96,7 @@ namespace TD.Gameplay
private void Start()
{
BuildGoalTileSet();
BuildHeuristicTable();
var loader = LevelLoader.Instance;
if (loader != null)
@ -230,9 +242,24 @@ namespace TD.Gameplay
}
// Octile distance to the nearest goal tile. Admissible heuristic for an
// 8-connected uniform-cost grid (cardinal 1, diagonal √2).
// 8-connected uniform-cost grid (cardinal 1, diagonal √2). Hot path: served
// from heuristicTable (O(1)) when the tile is in the grid bounds, otherwise
// falls back to scanning every goal (O(goalCount)).
private float Heuristic(Vector2Int tile)
{
if (heuristicTable != null)
{
int x = tile.x - heuristicOrigin.x;
int y = tile.y - heuristicOrigin.y;
if (x >= 0 && x < heuristicSize.x && y >= 0 && y < heuristicSize.y)
{
return heuristicTable[y * heuristicSize.x + x];
}
}
// Out-of-grid fallback. A* shouldn't usually visit these (it expands only
// from walkable tiles, which are in-bounds), but we keep correctness for
// any callers that hand us a stray tile.
float best = float.MaxValue;
foreach (var goal in goalTiles)
{
@ -380,6 +407,53 @@ namespace TD.Gameplay
Debug.Log($"[PathfindingService] Goal tile set built: {goalTiles.Count} tiles.");
}
// Precomputes the octile-distance-to-nearest-goal for every tile in the grid.
// Runs once on Start (cheap: ~3000 tiles × ~50 goals = 150K octile evaluations on
// the 9-player map, well under a millisecond). The output is a flat float[] keyed
// by grid-local index, hot for the A* heuristic.
private void BuildHeuristicTable()
{
var loader = LevelLoader.Instance;
if (loader == null || loader.LevelData == null)
{
heuristicTable = null;
return;
}
var levelData = loader.LevelData;
heuristicOrigin = levelData.GridOriginTile;
heuristicSize = levelData.GridSize;
int total = heuristicSize.x * heuristicSize.y;
if (total <= 0 || goalTiles == null || goalTiles.Count == 0)
{
heuristicTable = null;
return;
}
heuristicTable = new float[total];
for (int y = 0; y < heuristicSize.y; y++)
{
for (int x = 0; x < heuristicSize.x; x++)
{
Vector2Int worldTile = new Vector2Int(
heuristicOrigin.x + x,
heuristicOrigin.y + y);
float best = float.MaxValue;
foreach (var goal in goalTiles)
{
float d = GridCoordinates.OctileDistance(worldTile, goal);
if (d < best) best = d;
}
heuristicTable[y * heuristicSize.x + x] = best;
}
}
Debug.Log($"[PathfindingService] Heuristic table built: {total} tiles, " +
$"origin={heuristicOrigin}, size={heuristicSize}.");
}
private void HandleWalkabilityChanged()
{
OnPathsInvalidated?.Invoke();