Build a Tower Defense Game using C++, Raylib & A* Pathfinding

Lesson 6 of 8

Towers: Targeting, Shooting & Blocking the Grid

Towers do two jobs: shoot enemies that wander into range, and, the mechanic that ties this whole course together, block the grid so enemies have to path around them.

The Tower struct

struct Tower {
    GridPos gridPos;
    Vector2 pixelPos;
    float range = 100.f;
    float damage = 10.f;
    float fireRate = 1.f;  // shots per second
    float cooldown = 0.f;
};

Targeting and shooting

void updateTower(Tower& tower, std::vector<Enemy>& enemies, float deltaTime) {
    tower.cooldown -= deltaTime;
    if (tower.cooldown > 0.f) return; // still reloading

    for (Enemy& enemy : enemies) {
        if (enemy.health <= 0.f || enemy.reachedGoal) continue;

        if (Vector2Distance(tower.pixelPos, enemy.position) <= tower.range) {
            enemy.health -= tower.damage;
            tower.cooldown = 1.f / tower.fireRate;
            break; // one shot per cooldown, stop after the first valid target
        }
    }
}

tower.cooldown counts down every frame by deltaTime, and resets to 1.f / tower.fireRate the instant a shot fires, a tower with fireRate = 2.f reloads in half a second, one with fireRate = 0.5f takes two full seconds. This is the same "accumulate/reset a timer" pattern as the horse race course's per-tick movement, just applied to shooting instead of distance.

Placing a tower: the maze-blocking rule

This is the payoff for everything built so far. A player should be able to block some of the path to slow enemies down, funneling them past more towers, but never seal off the goal entirely. Checking that is exactly one extra call to findPath:

bool tryPlaceTower(GridPos pos, GridPos startPos, GridPos goalPos, std::vector<Tower>& towers) {
    if (grid[pos.row][pos.col] != CellType::Empty) {
        return false; // already a wall, or the start/goal cell
    }

    grid[pos.row][pos.col] = CellType::Wall; // tentatively block it

    std::vector<GridPos> testPath = findPath(startPos, goalPos);
    if (testPath.empty()) {
        grid[pos.row][pos.col] = CellType::Empty; // undo, this would have sealed off the goal
        return false;
    }

    Tower tower;
    tower.gridPos = pos;
    tower.pixelPos = gridToPixel(pos);
    towers.push_back(tower);

    return true; // caller should recompute waypoints from testPath for every enemy in play
}

The block-then-test-then-maybe-undo sequence is the whole trick: mark the cell as a wall before checking, run the exact same findPath from the earlier lesson, and only keep the change if a path still exists. If it doesn't, revert the one cell that was just changed, no other state needs touching, since nothing else was modified yet.

WARNING

tryPlaceTower finds a valid path, but doesn't rebuild every enemy's waypoints itself. After a successful placement, the caller still needs to convert testPath to waypoints and reset each enemy's currentWaypoint to 0, exactly the reroute mechanism from the end of the last lesson, otherwise enemies keep walking a path that may now be blocked.

📝 Towers & Path Blocking Quiz

Passing score: 70%
  1. 1.How does tryPlaceTower decide whether a tower placement is legal?

  2. 2.A tower with a higher fireRate reloads faster (shoots more often).

  3. 3.After a legal tower placement, every enemy still needs its waypoints rebuilt and its current____ reset to 0 to actually reroute.