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

Lesson 7 of 8

Waves, Gold, and Game Over

The last pieces turn "towers that shoot" into an actual game: enemies arrive in waves, killing them earns gold to afford more towers, and letting too many through ends the game.

Spawning a wave over time

struct WaveState {
    int enemiesRemaining = 10;
    float spawnTimer = 0.f;
    float spawnInterval = 0.8f; // seconds between spawns
};

void updateWaveSpawning(WaveState& wave, std::vector<Enemy>& enemies, Vector2 startPixel, float deltaTime) {
    if (wave.enemiesRemaining <= 0) return;

    wave.spawnTimer -= deltaTime;
    if (wave.spawnTimer <= 0.f) {
        Enemy enemy;
        enemy.position = startPixel;
        enemies.push_back(enemy);

        wave.enemiesRemaining--;
        wave.spawnTimer = wave.spawnInterval;
    }
}

Spawning every enemy on frame one would make them all overlap and march in a single clump, staggering them with spawnInterval (the same countdown-timer pattern from the tower's cooldown) spreads them into a readable line instead.

Gold and lives

int gold = 100;
int lives = 20;
const int TOWER_COST = 25;
const int KILL_REWARD = 5;

// in updateTower, right after an enemy's health drops to 0 or below:
if (enemy.health <= 0.f) {
    gold += KILL_REWARD;
}

// in the main loop, checking every enemy each frame:
if (enemy.reachedGoal) {
    lives--;
}

tryPlaceTower from the last lesson should also check gold >= TOWER_COST before allowing a placement, and subtract TOWER_COST on success, a player with 0 gold shouldn't be able to place a free tower just because a cell happens to be empty and a path would still exist.

Removing enemies that die or finish

enemies.erase(
    std::remove_if(enemies.begin(), enemies.end(),
        [](const Enemy& e) { return e.health <= 0.f || e.reachedGoal; }),
    enemies.end()
);

The erase-remove idiom: std::remove_if doesn't actually shrink the vector, it shuffles the elements that should be kept to the front and returns an iterator marking where the "removed" ones now start, .erase(...) is what actually shrinks the vector down to just the kept elements. Doing this once per frame, after health and reachedGoal have been updated, keeps enemies from growing forever.

Game over

bool gameOver = lives <= 0;
bool waveCleared = wave.enemiesRemaining <= 0 && enemies.empty();

waveCleared needs both conditions, no more enemies left to spawn, and none currently alive on the board, checking enemiesRemaining alone would declare victory while enemies are still mid-path.

TIP

Once gameOver is true, stop calling updateEnemy/updateTower/updateWaveSpawning (freeze the board, same idea as the horse race course's finish-line freeze), but keep drawing and keep the window's own event loop running, so the final state and a "GAME OVER" message stay visible.

📝 Waves & Game Over Quiz

Passing score: 70%
  1. 1.What does std::remove_if actually do to a vector?

  2. 2.A wave should be considered cleared as soon as enemiesRemaining reaches 0, even if enemies are still alive on the board.

  3. 3.Staggering enemy spawns with a spawnInterval timer is the same countdown pattern used by a tower's ____.