Adding flying functionality for enemies
This commit is contained in:
parent
54f11d91ff
commit
d685894878
6 changed files with 83 additions and 26 deletions
|
|
@ -125,7 +125,12 @@ namespace TD.Gameplay
|
|||
/// is unavailable (should not occur — TowerPlacementManager guarantees a path
|
||||
/// always exists after any placement).
|
||||
/// </summary>
|
||||
public List<Vector2Int> ComputePath(Vector2Int startTile)
|
||||
/// <param name="ignoreTowers">
|
||||
/// When true, pathing uses the baked terrain grid
|
||||
/// (<see cref="LevelLoader.IsBaseWalkable"/>) instead of the runtime
|
||||
/// tower-stamped grid, so the path soars over towers. Used by flying enemies.
|
||||
/// </param>
|
||||
public List<Vector2Int> ComputePath(Vector2Int startTile, bool ignoreTowers = false)
|
||||
{
|
||||
var loader = LevelLoader.Instance;
|
||||
if (loader == null || !loader.IsLoaded || goalTiles == null || goalTiles.Count == 0)
|
||||
|
|
@ -140,23 +145,31 @@ namespace TD.Gameplay
|
|||
// that just became blocked). Nudge to the nearest walkable tile so
|
||||
// the enemy can resume routing instead of repeatedly failing.
|
||||
// Search outward in Chebyshev rings — closest 8-neighbor wins.
|
||||
if (!loader.IsWalkable(startTile))
|
||||
// (For flyers this nudge is effectively never needed — the baked grid
|
||||
// doesn't change — but it's cheap and keeps the two paths uniform.)
|
||||
if (!IsTileWalkable(loader, startTile, ignoreTowers))
|
||||
{
|
||||
if (TryFindNearestWalkable(startTile, loader, maxRadius: 4, out var nudged))
|
||||
if (TryFindNearestWalkable(startTile, loader, maxRadius: 4, ignoreTowers, out var nudged))
|
||||
startTile = nudged;
|
||||
else
|
||||
return new List<Vector2Int>();
|
||||
}
|
||||
|
||||
return RunAStar(startTile, loader);
|
||||
return RunAStar(startTile, loader, ignoreTowers);
|
||||
}
|
||||
|
||||
// Single seam for "is this tile traversable for this enemy kind": grounded
|
||||
// enemies see the runtime grid (towers block them); flyers see only the baked
|
||||
// terrain grid (towers are invisible to them).
|
||||
private static bool IsTileWalkable(LevelLoader loader, Vector2Int tile, bool ignoreTowers)
|
||||
=> ignoreTowers ? loader.IsBaseWalkable(tile) : loader.IsWalkable(tile);
|
||||
|
||||
// Expanding ring search (Chebyshev / 8-connected) for the nearest
|
||||
// walkable tile around <paramref name="origin"/>. Caps at maxRadius to
|
||||
// keep this O(maxRadius²) in the worst case. Used by ComputePath when
|
||||
// an enemy's start tile becomes non-walkable mid-flight.
|
||||
private static bool TryFindNearestWalkable(Vector2Int origin, LevelLoader loader,
|
||||
int maxRadius, out Vector2Int found)
|
||||
int maxRadius, bool ignoreTowers, out Vector2Int found)
|
||||
{
|
||||
for (int r = 1; r <= maxRadius; r++)
|
||||
{
|
||||
|
|
@ -167,7 +180,7 @@ namespace TD.Gameplay
|
|||
// Only walk the OUTER ring at distance r (skip interior — already checked at smaller r).
|
||||
if (Mathf.Abs(dx) != r && Mathf.Abs(dy) != r) continue;
|
||||
var tile = new Vector2Int(origin.x + dx, origin.y + dy);
|
||||
if (loader.IsWalkable(tile))
|
||||
if (IsTileWalkable(loader, tile, ignoreTowers))
|
||||
{
|
||||
found = tile;
|
||||
return true;
|
||||
|
|
@ -181,7 +194,7 @@ namespace TD.Gameplay
|
|||
|
||||
// ----- A* implementation ------------------------------------------
|
||||
|
||||
private List<Vector2Int> RunAStar(Vector2Int start, LevelLoader loader)
|
||||
private List<Vector2Int> RunAStar(Vector2Int start, LevelLoader loader, bool ignoreTowers)
|
||||
{
|
||||
cameFrom.Clear();
|
||||
gScore.Clear();
|
||||
|
|
@ -197,14 +210,14 @@ namespace TD.Gameplay
|
|||
if (goalTiles.Contains(current))
|
||||
{
|
||||
var tilePath = ReconstructPath(start, current);
|
||||
return SmoothPath(start, tilePath, loader);
|
||||
return SmoothPath(start, tilePath, loader, ignoreTowers);
|
||||
}
|
||||
|
||||
float currentG = gScore.TryGetValue(current, out float g) ? g : float.MaxValue;
|
||||
|
||||
foreach (var neighbor in GridCoordinates.GetNeighbors8(current))
|
||||
{
|
||||
if (!loader.IsWalkable(neighbor)) continue;
|
||||
if (!IsTileWalkable(loader, neighbor, ignoreTowers)) continue;
|
||||
|
||||
// Corner-cut prevention: for a diagonal step, both cardinal
|
||||
// shoulder tiles must also be walkable. Otherwise enemies could
|
||||
|
|
@ -214,7 +227,8 @@ namespace TD.Gameplay
|
|||
{
|
||||
GridCoordinates.GetCornerShoulders(current, neighbor,
|
||||
out var shoulderA, out var shoulderB);
|
||||
if (!loader.IsWalkable(shoulderA) || !loader.IsWalkable(shoulderB))
|
||||
if (!IsTileWalkable(loader, shoulderA, ignoreTowers)
|
||||
|| !IsTileWalkable(loader, shoulderB, ignoreTowers))
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -305,7 +319,7 @@ namespace TD.Gameplay
|
|||
/// squeezes through a diagonal corner.
|
||||
/// </remarks>
|
||||
private List<Vector2Int> SmoothPath(Vector2Int start, List<Vector2Int> path,
|
||||
LevelLoader loader)
|
||||
LevelLoader loader, bool ignoreTowers)
|
||||
{
|
||||
if (path.Count <= 1) return path;
|
||||
|
||||
|
|
@ -319,7 +333,7 @@ namespace TD.Gameplay
|
|||
int farthest = i;
|
||||
for (int j = path.Count - 1; j > i; j--)
|
||||
{
|
||||
if (HasLineOfSight(anchor, path[j], loader))
|
||||
if (HasLineOfSight(anchor, path[j], loader, ignoreTowers))
|
||||
{
|
||||
farthest = j;
|
||||
break;
|
||||
|
|
@ -340,7 +354,8 @@ namespace TD.Gameplay
|
|||
/// crosses walkable tiles, with no diagonal corner-cuts. Used by
|
||||
/// <see cref="SmoothPath"/> to decide whether two waypoints can be collapsed.
|
||||
/// </summary>
|
||||
private static bool HasLineOfSight(Vector2Int a, Vector2Int b, LevelLoader loader)
|
||||
private static bool HasLineOfSight(Vector2Int a, Vector2Int b, LevelLoader loader,
|
||||
bool ignoreTowers)
|
||||
{
|
||||
int x0 = a.x, y0 = a.y;
|
||||
int x1 = b.x, y1 = b.y;
|
||||
|
|
@ -374,11 +389,11 @@ namespace TD.Gameplay
|
|||
// shoulder tiles (same rule A* uses).
|
||||
if (steppedX && steppedY)
|
||||
{
|
||||
if (!loader.IsWalkable(new Vector2Int(x - sx, y))) return false;
|
||||
if (!loader.IsWalkable(new Vector2Int(x, y - sy))) return false;
|
||||
if (!IsTileWalkable(loader, new Vector2Int(x - sx, y), ignoreTowers)) return false;
|
||||
if (!IsTileWalkable(loader, new Vector2Int(x, y - sy), ignoreTowers)) return false;
|
||||
}
|
||||
|
||||
if (!loader.IsWalkable(new Vector2Int(x, y))) return false;
|
||||
if (!IsTileWalkable(loader, new Vector2Int(x, y), ignoreTowers)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue