Back to Home

Refactoring Code, Part 2

Containing the game loop

The game loop is intimidating.

Decisions made at this point affect everything that follows. The game loop is the foundation. Not that you put foundations on top of scaffolding, but yeah - everything up to this point is SCAFFOLDING - I don't care what language we're using, what engine/framework/library, whatever - I'm pretty sure we aren't making a game until we see that while() loop.

Honestly it's the reason I started this blog. I want to explore this. So let me take you for a ride while I contemplate design decisions. Let's refactor us a game loop.

Refactoring us a game loop

You might want to re-familiarize yourself with the source in its entirety.

Observe this simplest of loops:

int C = 0;

while (true) {
    C++
}

We have a value (C) outside of the loop and we use the loop to change it. That's all this is. And we are going to take this concept and, for Dot Game, apply it to Input Handling, Rendering, and State Management.

Now the obvious thing to do is this:

State state = initState();

while (true) {
    handleInput(state);
    handleRendering(state);
}

But I don't like it. Think about Context Containment - how are we going to effectively communicate to our AI how to handle input without sharing with it how state works as context? The same goes for rendering - the implementation of these "handoff" points seems really easy to screw up - if we separate concerns like conventional wisdom recommends we create choke points where the AI is going to struggle with context. Sticky wicket. And the AI is no help here - I try getting input on this and it basically recommends the "obvious" approach I mentioned above.

So lets talk state. I don't know what else to do.

What is state?

State is something I am scared of. It's that structure of values - each representing, for lack of a better word, the "state" of everything in the simulated universe. The game world. In this case its the location of each of the dots in Dot Game and the keys that are currently being held down by the player.

AI: So the game loop is intimidating and state is scary. Got it.

It IS scary - you have an all important data structure being updated with every iteration of the game loop - talk about potential for chaos!

Actually, you know, perhaps that's the solution to my game loop problem. We're going to try something retarded. State will not be monolithic. It's going to exists EVERYWHERE! Know what I'm saying? Let's start by working BACKWARDS.

AI: ...

AI: Please don't do this.

The all important dot

Behold the dot!

#include "raylib.h"
#include <cstdlib>
#include <ctime>
#include <string>

// State object to encapsulate all state-related data
struct DotState {
    Vector2 position;
    float radius;
    Color color;
};

// Dot class definition
class Dot {
private:
    DotState state;

public:
    Dot(float x, float y, float r, Color c) {
        state = { {x, y}, r, c };
    }

    // Handle input based on external commands
    void handleInput(const std::string& command, float deltaTime, float speed) {
        if (command == "UP")
            state.position.y -= speed * deltaTime;
        else if (command == "DOWN")
            state.position.y += speed * deltaTime;
        else if (command == "LEFT")
            state.position.x -= speed * deltaTime;
        else if (command == "RIGHT")
            state.position.x += speed * deltaTime;

        // Keep within screen bounds
        if (state.position.x < state.radius)
            state.position.x = state.radius;
        if (state.position.x > 800 - state.radius)
            state.position.x = 800 - state.radius;
        if (state.position.y < state.radius)
            state.position.y = state.radius;
        if (state.position.y > 600 - state.radius)
            state.position.y = 600 - state.radius;
    }

    // Render the dot
    void handleRendering() const {
        DrawCircleV(state.position, state.radius, state.color);
    }

    // Accessor for state (e.g., for collision detection)
    const DotState& getState() const {
        return state;
    }
};

Uhh, this actually feels familiar. I can totally see this being given a different name and using it as a building block in some sort of scene editor. This isn't new at all is it.

AI: No, this approach is often seen in entity-component systems (ECS) or object-oriented game engines. The Dot class we've implemented is essentially a self-contained entity with its own logic for input handling, rendering, and state management.

Huh, so there I was thinking I was on to something. Well, I guess it can't be TOO bad of an idea if its a common pattern. How common is it?

AI: Pretty common. It's used in many game engines and frameworks. For example:

  • Unity: GameObjects have components like Transform for position and custom scripts for behavior.
  • Unreal Engine: Actors encapsulate state and behavior.
  • Godot: Nodes are modular building blocks in a Scene Tree, each with specific functionality.
  • Defold: Game objects use components like sprites and scripts for lightweight, modular design.
  • Raylib: While simpler, it encourages modular design like this for game objects.

So yes, this Dot class could indeed be a building block for a scene editor or a larger game engine. In a scene editor, objects like Dot would be visualized, manipulated, and managed within a scene graph. This modular design is a stepping stone toward creating a more complex system, like a game engine or a level editor.

So all you gotta do is attach a script to each of the handler methods and all the sudden you're building a general purpose game engine? That's what all these engines floating around are doing? I don't know why I'm posing these statements as questions, you basically said as much.

AI: Pretty much, yes. By attaching scripts or modular components to objects like your Dot, you can create highly flexible and reusable systems. This is the foundation of many modern game engines. Each object becomes self-contained, managing its own state and behavior, while the game loop orchestrates the overall flow.

I gotta say this is a very odd pattern - it seems that designing for flexibility, like in the case of a general-purpose game engine, also happens to be well suited for my idea of Context Containment.

I gotta think about this. That's all for today.

Next: Dot Game V2 Previous: Refactoring Code, Part 1