AlgoDaily

Lesson 2: Game Client - Basics

Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.

Client Begins

From toolchain to playable window

In Lesson 1: Setting up the Development Environment, you got the compiler, build flow, dependencies, and smoke test under control. Now we can stop asking, “Can C++ run?” and start asking, “What is a game client?”

For our MMORPG, the client is responsible for:

  • Opening a window and owning the local process lifecycle
  • Reading input from the player
  • Updating a local view of the world
  • Rendering frames smoothly
  • Managing assets like textures, maps, fonts, and sounds
  • Eventually talking to a server — but that is Lesson 3: Game Client - Networking

Today’s goal is not fancy graphics. It is the shape of the client: the loop, boundaries, timing, and small pieces of architecture that will survive as the project grows.

Client Begins

The Main Loop

The client heartbeat

Most game clients are built around a loop:

while game is running:
    read input
    update simulation
    render frame

Coming from Java web/server work, this is a shift. A Spring or Netty app usually waits for events. A game client actively produces time by advancing frame after frame.

A basic loop must answer:

  • Should the game keep running?
  • How much time passed since the previous frame?
  • What needs to update this frame?
  • What should be drawn now?

This example is console-only, but the structure is the same loop you would later wrap around SFML, SDL, OpenGL, or Vulkan.

```cpp
#include <chrono>
#include <iostream>
#include <thread>

int main() {
    using clock = std::chrono::steady_clock;

    bool running = true;
    int frame = 0;
    auto previous = clock::now();

    while (running) {
        auto now = clock::now();
        std::chrono::duration<double> elapsed = now - previous;
        previous = now;

        double dt = elapsed.count();

        // 1. Input phase: placeholder for keyboard/mouse/window events.
        std::cout << "[input] frame=" << frame << "\n";

        // 2. Update phase: advance the local simulation.
        std::cout << "[update] dt=" << dt << " seconds\n";

        // 3. Render phase: draw the current state.
        std::cout << "[render] drawing frame " << frame << "\n\n";

        ++frame;
        if (frame >= 5) {
            running = false;
        }

        // Pretend the display or OS pacing limits the loop.
        std::this_thread::sleep_for(std::chrono::milliseconds(16));
    }

    std::cout << "Client shut down cleanly.\n";
    return 0;
}
```

Loop Purpose

In a game client, why is the main loop usually organized into input, update, and render phases?

Subsystem Boundaries

Avoid the “everything object”

A beginner client often becomes one giant class:

Game.cpp knows about windows, input, textures, monsters,
network packets, UI buttons, file formats, and camera math.

That works for a weekend prototype. It collapses under an MMORPG.

Instead, think in boundaries:

Application
  ├─ Window / Platform Layer
  ├─ Input System
  ├─ Client World
  ├─ Renderer
  ├─ Asset Manager
  └─ UI Layer

For now, these can be small C++ classes. The important part is that each subsystem has a reason to change.

Examples:

  • Renderer changes when you move from simple sprites to OpenGL batching.
  • Asset manager changes when you add hot-reloading for god-mode tools.
  • Client world changes when entity state becomes more complex.
  • Input system changes when you add rebinding or controller support.

This keeps the client flexible without prematurely building an engine.

Subsystem Boundaries

Render Mutations

True or false: the render phase should generally mutate core gameplay state, such as moving the player or spawning monsters.

World State

A client needs a local world

Even before networking, the client needs a model of “what exists.”

For an MMORPG, that eventually includes:

  • The player character
  • Other characters
  • Monsters
  • Projectiles or abilities
  • Tiles, props, portals, and interactables

Do not overbuild an ECS today. But do start with a habit: keep world data separate from rendering code.

A renderer can draw a player at (x, y), but it should not be the owner of the player’s position. The world owns the state; the renderer observes it.

```cpp
#include <iostream>
#include <string>
#include <vector>

struct Vec2 {
    double x = 0.0;
    double y = 0.0;
};

struct Entity {
    int id = 0;
    std::string name;
    Vec2 position;
    Vec2 velocity;
};

class ClientWorld {
public:
    void add(Entity entity) {
        entities.push_back(entity);
    }

    void update(double dt) {
        for (auto& entity : entities) {
            entity.position.x += entity.velocity.x * dt;
            entity.position.y += entity.velocity.y * dt;
        }
    }

    void debugPrint() const {
        for (const auto& entity : entities) {
            std::cout << entity.name
                      << " at (" << entity.position.x
                      << ", " << entity.position.y << ")\n";
        }
    }

private:
    std::vector<Entity> entities;
};

int main() {
    ClientWorld world;

    world.add(Entity{1, "Player", {10.0, 5.0}, {2.0, 0.0}});
    world.add(Entity{2, "Training Slime", {3.0, 7.0}, {0.0, -1.0}});

    std::cout << "Before update:\n";
    world.debugPrint();

    world.update(0.5); // Half a second passes.

    std::cout << "\nAfter update:\n";
    world.debugPrint();

    return 0;
}
```

Delta Blank

Fill in the conventional two-letter variable name often used for “time elapsed since the previous frame”:

update(___);

Fill in the blank: _____________

Input As Intent

Input should become intent

Raw input is hardware-specific:

W key pressed
Left mouse button down
Gamepad axis moved

Game logic wants intent:

Move north
Select target
Open inventory
Confirm placement

That translation layer is small but powerful. It prevents the world update from caring whether movement came from WASD, arrow keys, a controller, or an editor tool.

A useful mental model:

Hardware Events → Input Mapping → Commands/Intent → World Update

This will matter later when the same client grows a god-mode editor. A map editor click and a gameplay click are both input, but they mean different things depending on client mode.

Input As Intent

Client States

The client is not always “in game”

Even a simple MMORPG client has modes:

  • Booting
  • Main menu
  • Character select
  • Loading world
  • In game
  • Exiting

Later, god-mode editing adds more modes, but the basic idea starts now: state controls what input means and what gets updated/rendered.

For example, pressing Escape in the main menu may exit the client. Pressing Escape in game may open a pause/options overlay.

Here is a tiny state-machine-style example. It is not a full engine; it is a habit of making transitions explicit.

```cpp
#include <iostream>
#include <string>

class ClientApp {
public:
    enum class State {
        Booting,
        MainMenu,
        LoadingWorld,
        InGame,
        Exiting
    };

    void transitionTo(State next) {
        std::cout << "Transition: " << name(state)
                  << " -> " << name(next) << "\n";
        state = next;
    }

    void tick() {
        switch (state) {
            case State::Booting:
                std::cout << "Checking local config...\n";
                transitionTo(State::MainMenu);
                break;
            case State::MainMenu:
                std::cout << "Showing menu buttons...\n";
                transitionTo(State::LoadingWorld);
                break;
            case State::LoadingWorld:
                std::cout << "Preparing assets and world data...\n";
                transitionTo(State::InGame);
                break;
            case State::InGame:
                std::cout << "Updating player, camera, and world...\n";
                transitionTo(State::Exiting);
                break;
            case State::Exiting:
                std::cout << "Saving settings and closing window...\n";
                break;
        }
    }

    bool isRunning() const {
        return state != State::Exiting;
    }

private:
    State state = State::Booting;

    static std::string name(State s) {
        switch (s) {
            case State::Booting: return "Booting";
            case State::MainMenu: return "MainMenu";
            case State::LoadingWorld: return "LoadingWorld";
            case State::InGame: return "InGame";
            case State::Exiting: return "Exiting";
        }
        return "Unknown";
    }
};

int main() {
    ClientApp app;

    while (app.isRunning()) {
        app.tick();
    }

    app.tick(); // Let Exiting state log its cleanup message.
    return 0;
}
```

Architecture Check

Which responsibility best belongs to the client world rather than the renderer?

Open interactive view →