2Brute2Best
πŸ”₯0
C4

Spawning Waves of Enemies From Tuned Templates

Pro OnlyPrototype virtual clone() with unique_ptr
⚑ 60-Second Architecture SummaryC4 · Prototype Pattern
Naive Bottleneck→Prototype Pattern Cure
The Naive Bottleneck: Severe frame hitches during horde spawns

The spawner code cannot call `new ConcreteMonster` when it only holds a base `Monster*` or `EnemyPrototype*` template configured from level data. Naively re-reading disk JSON configs and parsing stats on every spawn triggers severe frame hitches and prevents dynamic runtime mutation of enemy archetypes.

Parsing JSON, allocating lookup maps, and reading disk configurations dozens of times per second inside the game loop causes massive CPU spikes and frame drops.

The Pattern Cure: Prototype Pattern

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

We define an abstract EnemyPrototype interface with virtual std::unique_ptr<EnemyPrototype> clone() const = 0;. Concrete classes (GoblinScout, EliteOrc, IceMage) implement clone() via standard copy construction. MonsterSpawnerRegistry loads and tunes base prototypes once during game boot. When a wave triggers, the spawner simply clones the pre-tuned prototype in O(1) memory copy time, sets position coordinates, and dispatches the enemy immediately.

Sprint 4 of 8 Β· Spawning Monster Waves by Cloning Templates
Story C β€” 2D RPG Game Engine β€” Progressive System Build
1. Where our codebase left off
← C3

In Sprint 3 (C3 Β· Memento), we built an encapsulated checkpoint and rollback mechanism protecting hero state across dungeon boss wipes. With the player save loop robust, the dungeon system now needs to populate chambers and arenas with hordes of enemy encounters.

2. What we are building now

We need a monster spawning system (MonsterSpawnerRegistry) that can rapidly instantiate waves of pre-configured monster variants (GoblinScout, EliteOrc, IceMage) from tuned templates at runtime, allowing designers to spawn customized mini-bosses without hardcoding new classes.

3. The pain point we hit

The spawner code cannot call new ConcreteMonster when it only holds a base Monster* or EnemyPrototype* template configured from level data. Naively re-reading disk JSON configs and parsing stats on every spawn triggers severe frame hitches and prevents dynamic runtime mutation of enemy archetypes.

4. What this unlocks next
Next: C5 β†’

The Prototype Pattern (clone()) solves polymorphic copying in O(1) memory time, allowing the registry to stamp out hundreds of enemies from pre-tuned templates instantly. Next in Sprint 5 (C5 Β· Flyweight), where spawning 50,000 dungeon tiles, trees, and particle effects crashes RAM because each instance duplicates multi-megabyte mesh and texture data.

01

The product ticket

Ticket Β· C4RPG-104 Β· Spawning Waves of Enemies From Tuned Templates

During dungeon horde battles, waves of dozens or hundreds of monsters spawn rapidly: GoblinScout (fast, low HP, dagger), EliteOrc (high HP, armor, battle axe), and IceMage (frost spells, elemental resistances). Each monster archetype has complex pre-tuned statistics, including weapon tables, AI behavior weights, and loot drop distributions.

Requirements
  • β€’

    Support spawning hundreds of monsters in rapid succession with O(1) overhead without re-parsing configuration files.

  • β€’

    Implement a polymorphic cloning mechanism via virtual std::unique_ptr<EnemyPrototype> clone() const = 0;.

  • β€’

    Provide a registry (MonsterSpawnerRegistry) that holds pre-tuned archetype templates and clones them on demand.

  • β€’

    Allow post-clone customization: set coordinates (x, y) and adjust stats without mutating the prototype template.

The next requirement that breaks naive code

Designers want to create new mini-boss variants at runtime β€” such as cloning an EliteOrc, buffing its HP to 450, equipping a 'Doombringer Greatsword', and registering it as orc_warlord β€” without creating a new C++ class or restarting the engine.

02

Step 1 β€” The naive code & why it breaks

The naive spawner uses a factory function with a switch statement that re-reads disk JSON configs, parses strings, and reconstructs monster state from scratch on every single spawn.

Naive design (what not to ship) Β· naive.cpp
#include <chrono>
#include <print>
#include <string>
#include <thread>
#include <vector>

// Naive: Every spawn parses config strings and rebuilds complex structures from scratch!
struct MonsterConfig {
    std::string type;
    int hp;
    int attack;
    std::string weapon;
    std::vector<std::string> lootTable;
};

// Simulates expensive parsing/loading from disk or network
MonsterConfig loadConfigFromDisk(const std::string& type) {
    // In production game engines, disk I/O and JSON parsing take substantial CPU cycles
    if (type == "goblin") {
        return {"Goblin Scout", 45, 12, "Rusty Dagger", {"10 Gold", "Cloth Scrap"}};
    } else if (type == "orc") {
        return {"Elite Orc", 180, 28, "Battle Axe", {"Heavy Belt", "Ruby"}};
    }
    return {"Unknown", 10, 5, "Fist", {}};
}

class NaiveMonster {
    MonsterConfig cfg_;
    int x_{0};
    int y_{0};
public:
    explicit NaiveMonster(MonsterConfig cfg) : cfg_(std::move(cfg)) {}

    void setPosition(int x, int y) { x_ = x; y_ = y; }
    void print() const {
        std::println("Spawned {} at ({}, {}) [HP: {}, Atk: {}, Weapon: {}]",
                     cfg_.type, x_, y_, cfg_.hp, cfg_.attack, cfg_.weapon);
    }
};

class NaiveSpawner {
public:
    NaiveMonster spawn(const std::string& type, int x, int y) {
        // Flaw: Disk/JSON config parsing repeated for every single spawn!
        MonsterConfig cfg = loadConfigFromDisk(type);
        NaiveMonster m(std::move(cfg));
        m.setPosition(x, y);
        return m;
    }
};

int main() {
    NaiveSpawner spawner;

    std::println("--- Naive Horde Spawning (Re-parsing config on every spawn) ---");
    for (int i = 0; i < 3; ++i) {
        auto goblin = spawner.spawn("goblin", 10 * i, 20 * i);
        goblin.print();
    }
    auto orc = spawner.spawn("orc", 100, 100);
    orc.print();
}
Severe frame hitches during horde spawns

Parsing JSON, allocating lookup maps, and reading disk configurations dozens of times per second inside the game loop causes massive CPU spikes and frame drops.

Rigid coupling to hardcoded constructors

Adding a new enemy type forces developers to add a new case branch and pass 6+ constructor arguments, violating the Open-Closed Principle (OCP).

Cannot create dynamic runtime variants

If a level designer wants an 'Orc Warlord' with tweaked stats, they cannot simply clone and modify an existing template; they must create a new config file and add new factory code.

03

Step 2 β€” The fix: Prototype Pattern

Core idea in one sentence

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

We define an abstract EnemyPrototype interface with virtual std::unique_ptr<EnemyPrototype> clone() const = 0;. Concrete classes (GoblinScout, EliteOrc, IceMage) implement clone() via standard copy construction. MonsterSpawnerRegistry loads and tunes base prototypes once during game boot. When a wave triggers, the spawner simply clones the pre-tuned prototype in O(1) memory copy time, sets position coordinates, and dispatches the enemy immediately.

Pattern roles in our story
Prototype (Interface)

EnemyPrototype β€” declares clone(), spawnAt(x, y), takeDamage(dmg), and inspection methods.

Concrete Prototypes

GoblinScout, EliteOrc, IceMage β€” implement clone() via std::make_unique<Derived>(*this) and manage archetype stats.

Client / Registry

MonsterSpawnerRegistry β€” maintains a dictionary of named prototypes (prototypes_) and spawns clones on demand.

Brute2Best Pro Content

Unlock Full UML Diagram & C++23 Solution

The interactive UML architecture diagrams, refactored C++23 code, real-world extensions, and interview Q&A are part of Brute2Best Pro. All 7 foundation chapters are 100% free.

All 440 visual dry runs & algorithms
Interactive step-by-step playback with pointer & array state transitions
52 comprehensive LLD chapters
Interactive UML diagrams, real-world stories (Food Delivery, Trading, RPG), and practice solutions
40 C++ basics labs & hardware memory models
Vtables, byte layouts, assembly insights, and multi-threading race conditions
Production-ready C++23 source code
Modern idioms, clean contracts, time/space complexity derivations, and interview takeaways

First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.

Systems Integration

Related C++ Systems & Algorithmic Foundations

Deep dive into underlying layers