Appendix B

The Demo Skeleton

Every demo in this book starts from the same small program, the skeleton. Chapter 1 builds it a piece at a time, and explains every line. This appendix prints it whole, exactly as it is in the Skeleton folder of the book's repository, so that any part of it is easy to find. It opens a window, runs a game loop timed by delta time, reads the keyboard and the mouse, and draws text. Then it gets out of the way.

The Whole Skeleton

Here it is, from its first comment to its last return:

// Skeleton - the starting point of every demo in
// Learning C++ by Building Games: Fundamentals
//
// It opens a window, runs a game loop timed by delta time, reads the
// keyboard and the mouse, and draws text. Chapter 1 explains it line by
// line, and Appendix B prints it whole.

#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <string>

constexpr int WINDOW_WIDTH  = 1000;
constexpr int WINDOW_HEIGHT = 700;

constexpr SDL_Color TEXT = { 235, 238, 242, 255 };
constexpr SDL_Color GREY = { 90, 100, 112, 255 };

// Draw a line of text at (x, y). SDL_ttf turns the string into a surface,
// the surface is uploaded as a texture, and the texture is drawn.
void drawText(SDL_Renderer* renderer, TTF_Font* font,
              const std::string& text, float x, float y, SDL_Color color)
{
    if (!font) return;   // No font loaded: skip the label rather than crash.

    SDL_Surface* surface = TTF_RenderText_Blended(font, text.c_str(), 0, color);
    if (!surface) return;

    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
    SDL_FRect dst = { x, y, (float)surface->w, (float)surface->h };
    SDL_RenderTexture(renderer, texture, nullptr, &dst);

    SDL_DestroySurface(surface);
    SDL_DestroyTexture(texture);
}

int main(int argc, char* argv[])
{
    // Start SDL and SDL_ttf. Each returns false if it fails.
    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        SDL_Log("SDL_Init failed: %s", SDL_GetError());
        return 1;
    }
    if (!TTF_Init())
    {
        SDL_Log("TTF_Init failed: %s", SDL_GetError());
        return 1;
    }

    // The window, and a renderer that waits for the screen to refresh
    // (vsync), so the loop runs once for every frame the screen shows.
    SDL_Window* window =
        SDL_CreateWindow("Skeleton", WINDOW_WIDTH, WINDOW_HEIGHT, 0);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, nullptr);
    SDL_SetRenderVSync(renderer, 1);

    // The font is copied beside the program when it's built. SDL_GetBasePath
    // gives the program's folder, wherever the program is started from.
    const char* base = SDL_GetBasePath();
    std::string folder = base ? base : "";
    std::string fontPath = folder + "RobotoMono-Light.ttf";
    TTF_Font* font = TTF_OpenFont(fontPath.c_str(), 16);
    if (!font) SDL_Log("Font not found, so no labels: %s", SDL_GetError());

    // Delta time: the seconds since the last frame, from SDL's counter.
    Uint64 previous = SDL_GetPerformanceCounter();
    const double frequency = (double)SDL_GetPerformanceFrequency();

    bool running = true;
    while (running)
    {
        Uint64 now = SDL_GetPerformanceCounter();
        float dt = (float)((now - previous) / frequency);
        previous = now;
        if (dt > 0.05f) dt = 0.05f;   // A long stall counts as 1/20 s.

        // Events: quitting, and the keys and clicks that happen once.
        SDL_Event event;
        while (SDL_PollEvent(&event))
        {
            if (event.type == SDL_EVENT_QUIT) running = false;
            else if (event.type == SDL_EVENT_KEY_DOWN)
            {
                if (event.key.key == SDLK_ESCAPE) running = false;
            }
        }

        // The mouse's position, read once a frame.
        float mx, my;
        SDL_GetMouseState(&mx, &my);

        // Update: a demo's own work goes here, scaled by dt.

        // Draw: clear to the background, draw the frame, then show it.
        SDL_SetRenderDrawColor(renderer, 24, 26, 30, 255);
        SDL_RenderClear(renderer);

        drawText(renderer, font, "Hello, Fundamentals", 12, 10, TEXT);
        drawText(renderer, font, "[Esc] quit", 12, WINDOW_HEIGHT - 26, GREY);

        SDL_RenderPresent(renderer);
    }

    // Clean up, in the reverse order of creation.
    if (font) TTF_CloseFont(font);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    TTF_Quit();
    SDL_Quit();
    return 0;
}

The Skeleton folder holds it as a Visual Studio project, already set up, so you can copy the folder and start a program of your own from it. Chapter 1's A Project of Your Own gives the five settings that a new, empty project needs instead.

What Each Part Does

The skeleton runs from top to bottom in six parts, and Chapter 1 explains each one in a section of its own.

Starting up. The program starts SDL and SDL_ttf, and stops with a message if either fails. Both functions return false when they fail, which is why each test starts with !.

The window, the renderer and the font. The renderer draws into the window, and vsync makes it wait for the screen, so the loop runs once for each frame the screen shows. The font is found beside the program, in the folder that SDL_GetBasePath gives, wherever the program is started from. Without the font, the demo still runs, with no labels.

Delta time. Each pass of the loop measures the seconds since the last one, as dt, from SDL's high-resolution counter. Anything that moves by itself is scaled by dt, so it moves at the same speed at any frame rate. A long stall, such as dragging the window, counts as only a twentieth of a second, so a pause never makes a demo jump.

Events. Things that happen once, such as a click, a key going down or the window closing, arrive as events. The inner loop takes every waiting event in turn. Keys that are held, rather than pressed, are read in a demo's update with SDL_GetKeyboardState.

The update and the drawing. The mouse's position is read once a frame, and then the demo does its own work. Drawing starts by clearing the whole window to the background, since nothing is kept from one frame to the next. The frame is drawn, the readout last, on top of everything, and SDL_RenderPresent shows it.

Cleaning up. When the loop ends, everything made at the start is destroyed, in the reverse order of its making, and SDL_ttf and SDL shut down.

Where a Demo's Code Goes

Each chapter shows only the code that makes its demo what it is, and says which part of the skeleton it goes in. Figure 1.4 shows the places, and they're always the same. A demo's helpers go above main, after drawText. Its own types go near the top after the colors, or just above the helpers that use them. Its state, the things it keeps track of, goes after the font is loaded, before the loop. Its own keys and clicks go in the event loop, and its update goes after the mouse is read. Its drawing goes between the clear and the present, with the readout last, and anything it makes at the start, it destroys in the cleanup.

A demo leaves out the parts it doesn't need. One in which nothing moves by itself has no delta time, and one that never uses the mouse doesn't read it. The demo's title, in SDL_CreateWindow, is its own, and its readout and controls replace the skeleton's two lines of text.

The Math Headers

Beside main.cpp, each demo's folder holds the math headers it uses, as they stand at its chapter. Each header grows a few functions at a time, in the chapters that teach them, and never changes what an earlier chapter wrote:

  • The Vec2.h header is made in Chapter 2, with the arithmetic of two numbers. Chapter 3 adds length and distance, Chapter 4 the dot and cross products, and Chapter 5 angles. Chapter 6 adds rotation, Chapter 8 lerp and its relatives, Chapter 9 truncated and Chapter 10 reflect.
  • The Mat3.h header is made in Chapter 26, with the 2D transforms, their product, and determinant and inverse.
  • The Vec3.h header is made in Chapter 28, with Vec2's arithmetic in three numbers, and the cross product that gives a vector.
  • The Mat4.h header is made in Chapter 29, with the 3D transforms and column. Chapter 30 adds Vec4 and the projections, and Chapter 31 adds lookAt.
  • The Mesh.h header is made in Chapter 32, with meshes, their normals and the .obj loader.
  • The Quat.h header is made in Chapter 35, with quaternions and slerp.

None of them uses SDL. The math headers need only <cmath> and the headers they build on, and Mesh.h needs the standard library's strings, vectors and files too. So you can lift them into a program of your own, whatever it draws with. Appendix A lists what they compute.