Tutorial 47: GameComponent and DrawableGameComponent

CNA — C++ XNA 4.0 reimplementation

The component system lets you split cross-cutting concerns (input, audio, debug overlay, fps counter) into self-contained objects that the Game loop calls automatically each frame — without boilerplate in your main Update and Draw methods.

GameComponent base — Update only

#include "Microsoft/Xna/Framework/GameComponent.hpp"
using namespace Microsoft::Xna::Framework;

class InputManager final : public GameComponent {
public:
    explicit InputManager(Game* game) : GameComponent(game) {
        // Lower UpdateOrder runs first
        setUpdateOrder(0);
    }

    void Update(GameTime& gameTime) override {
        prevKeys_ = currKeys_;
        currKeys_ = Keyboard::GetState();
        prevPad_  = currPad_;
        currPad_  = GamePad::GetState(PlayerIndex::One);
    }

    bool WasPressed(Keys key) const {
        return currKeys_.IsKeyDown(key) && prevKeys_.IsKeyUp(key);
    }

    bool WasButtonPressed(Buttons btn) const {
        return currPad_.IsButtonDown(btn) && prevPad_.IsButtonUp(btn);
    }

private:
    KeyboardState currKeys_, prevKeys_;
    GamePadState  currPad_,  prevPad_;
};

DrawableGameComponent — Update + Draw

#include "Microsoft/Xna/Framework/DrawableGameComponent.hpp"

class FPSCounter final : public DrawableGameComponent {
public:
    explicit FPSCounter(Game* game) : DrawableGameComponent(game) {
        setUpdateOrder(100); // run after game logic
        setDrawOrder(1000);  // draw on top of everything
    }

    void LoadContent() override {
        font_ = getGame()->getContentProperty().Load<SpriteFont>("fonts/debug");
        sb_   = std::make_unique<SpriteBatch>(
                    getGame()->getGraphicsDeviceProperty());
    }

    void Update(GameTime& gameTime) override {
        elapsed_ += gameTime.ElapsedGameTime.TotalSeconds();
        ++frames_;
        if (elapsed_ >= 1.0) {
            fps_     = frames_;
            frames_  = 0;
            elapsed_ = 0.0;
        }
    }

    void Draw(const GameTime&) override {
        if (!getVisible()) return;
        sb_->Begin();
        sb_->DrawString(*font_,
            "FPS: " + std::to_string(fps_),
            Vector2(10, 10),
            Color::Yellow);
        sb_->End();
    }

private:
    SpriteFont*                    font_    = nullptr;
    std::unique_ptr<SpriteBatch>   sb_;
    int                            fps_     = 0;
    int                            frames_  = 0;
    double                         elapsed_ = 0.0;
};

Game::Components collection

class MyGame final : public Game {
public:
    MyGame() : graphics_(this) {
        // Add components in the constructor — before Initialize() is called
        input_   = std::make_unique<InputManager>(this);
        fpsHud_  = std::make_unique<FPSCounter>(this);

        Components().Add(input_.get());
        Components().Add(fpsHud_.get());
    }

    // No manual Update/Draw calls for components —
    // the Game loop iterates Components() automatically.
protected:
    void Update(GameTime& gt) override {
        // Components().Update() is called by Game base before this
        if (input_->WasPressed(Keys::F1))
            fpsHud_->setVisible(!fpsHud_->getVisible());

        if (input_->WasPressed(Keys::Escape))
            Exit();
    }

    void Draw(const GameTime& gt) override {
        getGraphicsDeviceProperty().Clear(Color::CornflowerBlue);
        // ... game drawing ...
        getGraphicsDeviceProperty().Present();
        // Components().Draw() is called by Game base after this
    }

private:
    GraphicsDeviceManager           graphics_;
    std::unique_ptr<InputManager>   input_;
    std::unique_ptr<FPSCounter>     fpsHud_;
};

Enabled and Visible

// Pause a component's Update
input_->setEnabled(false);

// Hide a drawable component (Update still runs)
fpsHud_->setVisible(false);

// Re-enable
input_->setEnabled(true);

UpdateOrder and DrawOrder

Components are sorted by their order values before each call. Lower values run first.

ComponentRecommended UpdateOrderDrawOrder
InputManager0 (first)n/a
AudioManager10n/a
Game logic50 (default)50
UI / HUD80800
Debug overlay1001000 (last)

Use cases

  • InputManager — centralise keyboard, mouse, and gamepad polling into one place; expose WasPressed helpers.
  • AudioManager — manage music transitions, sound pools, and volume settings independently of game logic.
  • FPSCounter / DebugOverlay — toggle with F1; zero cost when Visible = false.
  • ParticleSystem — self-contained drawable that manages its own vertex buffers.
  • NetworkManager — update-only component that ticks the network layer each frame.