Updating HUD, Gold Config, and finishing off Play flow for 9player map.
This commit is contained in:
parent
a7be12fa9b
commit
3dcc0e7edd
28 changed files with 2272 additions and 9601 deletions
|
|
@ -93,6 +93,12 @@ namespace TD.Gameplay
|
|||
private readonly Queue<Vector2Int> bfsQueue = new Queue<Vector2Int>();
|
||||
private readonly HashSet<Vector2Int> bfsVisited = new HashSet<Vector2Int>();
|
||||
|
||||
// Scratch set for "tiles that should be treated as blocked for this BFS run only"
|
||||
// — populated by queue-time path-validity checks with the candidate tower's footprint
|
||||
// tiles. Avoids the stamp-then-restore pattern (which fired walkability-change events
|
||||
// on tiles whose net state didn't change, causing a cascade of enemy re-paths).
|
||||
private readonly HashSet<Vector2Int> virtualBlockedScratch = new HashSet<Vector2Int>();
|
||||
|
||||
// ----- Lifecycle --------------------------------------------------
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
|
|
@ -278,23 +284,26 @@ namespace TD.Gameplay
|
|||
|
||||
// ------------------------------------------------------------------
|
||||
// Check 6: Path validity (queue-time)
|
||||
// Temporarily stamp the footprint non-walkable, run BFS per spawner
|
||||
// in the placing player's zone, then un-stamp if any spawner loses
|
||||
// its exit route. Importantly we do NOT stamp other queued (but not
|
||||
// yet constructing) jobs as non-walkable — queued ghosts represent
|
||||
// intent only and don't block enemies. The check is "could THIS
|
||||
// tower be built right now if it were instantly complete?" — a
|
||||
// coarse test that catches obvious blockers at queue-time. The
|
||||
// construction-start re-check (in Builder.DriveHead_Queued) catches
|
||||
// cases where the maze changed since queue-time.
|
||||
// Virtually treat the footprint as non-walkable and run BFS per spawner
|
||||
// in the placing player's zone. We do NOT modify the grid here — the
|
||||
// BFS just consults a "virtually blocked" tile set in addition to
|
||||
// IsWalkable. Importantly we do NOT block other queued (but not yet
|
||||
// constructing) jobs — queued ghosts represent intent only and don't
|
||||
// block enemies. The check is "could THIS tower be built right now if
|
||||
// it were instantly complete?" — a coarse test that catches obvious
|
||||
// blockers at queue-time. The construction-start re-check (in
|
||||
// Builder.DriveHead_Queued) catches cases where the maze changed since
|
||||
// queue-time.
|
||||
//
|
||||
// Why virtual instead of stamp-and-restore: every real walkability
|
||||
// flip fires OnWalkabilityChanged which triggers all enemies to A*.
|
||||
// Stamp-and-restore (no net change) would fire those events twice for
|
||||
// no reason. The virtual approach has zero side-effects.
|
||||
// ------------------------------------------------------------------
|
||||
StampWalkable(loader, footprint, walkable: false);
|
||||
virtualBlockedScratch.Clear();
|
||||
foreach (var tile in footprint) virtualBlockedScratch.Add(tile);
|
||||
|
||||
bool pathValid = CheckPathValidity(loader, placingSlot);
|
||||
|
||||
// Restore walkability — the queue stage leaves tiles walkable.
|
||||
// Occupancy is stamped below as part of the commit.
|
||||
StampWalkable(loader, footprint, walkable: true);
|
||||
bool pathValid = CheckPathValidity(loader, placingSlot, virtualBlockedScratch);
|
||||
|
||||
if (!pathValid)
|
||||
{
|
||||
|
|
@ -348,18 +357,22 @@ namespace TD.Gameplay
|
|||
foreach (var tile in GridCoordinates.GetFootprintTiles(anchor, footprintSize))
|
||||
footprint.Add(tile);
|
||||
|
||||
StampWalkable(loader, footprint, walkable: false);
|
||||
// Virtual check first — no grid mutation, no walkability events fire while
|
||||
// we're just asking "would this break the maze?". Only if the check passes
|
||||
// do we stamp the footprint for real, which fires exactly one batched event.
|
||||
virtualBlockedScratch.Clear();
|
||||
foreach (var tile in footprint) virtualBlockedScratch.Add(tile);
|
||||
|
||||
bool ok = CheckPathValidity(loader, placingSlot);
|
||||
|
||||
if (!ok)
|
||||
if (!CheckPathValidity(loader, placingSlot, virtualBlockedScratch))
|
||||
{
|
||||
// Roll back — the maze would break. Caller refunds and drops the job.
|
||||
StampWalkable(loader, footprint, walkable: true);
|
||||
// Maze would break. Caller refunds and drops the job. Grid untouched,
|
||||
// no events fired.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Footprint is now occupied (still) and non-walkable. Construction proceeds.
|
||||
// Commit: stamp the footprint non-walkable. Single batched event fires
|
||||
// OnWalkabilityChanged once for the whole footprint, regardless of size.
|
||||
StampWalkable(loader, footprint, walkable: false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -407,7 +420,8 @@ namespace TD.Gameplay
|
|||
/// grid. Reuses <see cref="bfsQueue"/> and <see cref="bfsVisited"/> scratch
|
||||
/// collections (cleared between BFS runs) to avoid GC allocation per call.
|
||||
/// </remarks>
|
||||
private bool CheckPathValidity(LevelLoader loader, PlayerSlot slot)
|
||||
private bool CheckPathValidity(LevelLoader loader, PlayerSlot slot,
|
||||
HashSet<Vector2Int> virtualBlocked = null)
|
||||
{
|
||||
var levelData = loader.LevelData;
|
||||
|
||||
|
|
@ -432,10 +446,12 @@ namespace TD.Gameplay
|
|||
return true;
|
||||
}
|
||||
|
||||
// BFS per spawner: each spawner's tile area is the BFS seed set.
|
||||
// BFS per spawner: each spawner's tile area is the BFS seed set. The optional
|
||||
// virtualBlocked set lets queue-time checks treat the candidate footprint as
|
||||
// non-walkable WITHOUT modifying the grid (avoiding spurious walkability events).
|
||||
foreach (var spawner in zoneData.Spawners)
|
||||
{
|
||||
if (!SpawnerCanReachExit(loader, spawner, exitTiles))
|
||||
if (!SpawnerCanReachExit(loader, spawner, exitTiles, virtualBlocked))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -474,11 +490,20 @@ namespace TD.Gameplay
|
|||
/// is reachable via walkable tiles. Uses the shared scratch queue and visited set.
|
||||
/// </summary>
|
||||
private bool SpawnerCanReachExit(LevelLoader loader, SpawnerData spawner,
|
||||
HashSet<Vector2Int> exitTiles)
|
||||
HashSet<Vector2Int> exitTiles,
|
||||
HashSet<Vector2Int> virtualBlocked = null)
|
||||
{
|
||||
bfsQueue.Clear();
|
||||
bfsVisited.Clear();
|
||||
|
||||
// Local walkability check that honors the virtual-blocked override. Hot-path
|
||||
// helper so we don't duplicate the conditional inside every neighbor test.
|
||||
bool IsTileOpen(Vector2Int t)
|
||||
{
|
||||
if (virtualBlocked != null && virtualBlocked.Contains(t)) return false;
|
||||
return loader.IsWalkable(t);
|
||||
}
|
||||
|
||||
// Seed the BFS with the spawner's full tile area (not just its center tile),
|
||||
// matching bake-time P5-4 exactly.
|
||||
foreach (var tile in spawner.TileArea)
|
||||
|
|
@ -503,13 +528,13 @@ namespace TD.Gameplay
|
|||
foreach (var neighbor in GridCoordinates.GetNeighbors8(current))
|
||||
{
|
||||
if (bfsVisited.Contains(neighbor)) continue;
|
||||
if (!loader.IsWalkable(neighbor)) continue;
|
||||
if (!IsTileOpen(neighbor)) continue;
|
||||
|
||||
if (GridCoordinates.IsDiagonal(current, neighbor))
|
||||
{
|
||||
GridCoordinates.GetCornerShoulders(current, neighbor,
|
||||
out var shoulderA, out var shoulderB);
|
||||
if (!loader.IsWalkable(shoulderA) || !loader.IsWalkable(shoulderB))
|
||||
if (!IsTileOpen(shoulderA) || !IsTileOpen(shoulderB))
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -531,8 +556,11 @@ namespace TD.Gameplay
|
|||
private static void StampWalkable(LevelLoader loader, List<Vector2Int> footprint,
|
||||
bool walkable)
|
||||
{
|
||||
foreach (var tile in footprint)
|
||||
loader.SetWalkable(tile, walkable);
|
||||
// Batched: fires OnWalkabilityChanged at most once for the whole footprint,
|
||||
// instead of once per tile. Without this, a 2×2 placement fires 4 enemy
|
||||
// re-paths instead of 1; a 3×3 fires 9. The cascade was the dominant
|
||||
// contributor to placement stutter on larger maps.
|
||||
loader.SetWalkableBatch(footprint, walkable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue