Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I realize there are already many Q&As on this site about GameState/GameScreen management, state machines, state stacks, etc. This question is meant as a follow-up:

Suppose hypothetically I decide to go with a simple state machine for the fundamental transitions in the program:

[intro loop] <---> [main menu] <---> [gameplay] <---> [pause menu]
                       \               /     \
                        `-<---[game-over]      `---> [ending]

of course that is not meant to be a comprehensive diagram by any means. (And I'm aware of the suggestion to not use a game state machine at all, but let's say I would like to go ahead and make one).

I happen to be using the SFML framework in C++, so in my code my main loop happens to look like this:

MainLoop.cpp:

while(app->isRunning())
{
    while(app->win->pollEvent(event))
    {
        switch(event.type)
        {
        case sf::Event::Closed:
            app->exit();
            break;

        case sf::Event::Resized:
            app->resize(event.w, event.h);
            break;

        case sf::Event::KeyPressed:
            app->handleKeyPress(event.key));
            break;

        case sf::Event::KeyReleased:
            app->handleKeyRelease(event.key));
            break;

        // ... etc.

This means I have an 'app' object implementing a certain interface:

Application.h

public:
    virtual bool isRunning() const = 0;
    virtual bool isPaused() const = 0;
    virtual void pause() = 0;
    virtual void resume() = 0;
    virtual void exit() = 0;
    virtual void resize(int w, int h) = 0;
    virtual void render() = 0;
    virtual void handleKeyPress(int key) = 0;
    virtual void handleKeyRelease(int key) = 0;
    virtual void handleMouseClick(int btn) = 0;
    // ... etc.

but following the principle of favouring composition over inheritance, my 'app' object (say MyGame) which implements this interface is not a state machine, but contains a StateMachine as a member, like so:

MyGame.h

class MyGame : public Application
{
private:
    StateMachine * myStateMachine;
    // ... etc.

So for things like handleKeyPress() and handleMouseButton() it simply delegates these calls to its member state machine like so:

MyGame.h

public:
    void handleKeyPress(int k) { myStateMachine->handleKeyPress(k); }
    void handleMouseClick(int b) { myStateMachine->handleMouseClick(b); }

Meanwhile, the StateMachine object DOES THE SAME EXACT THING, i.e. it forwards these same calls to the "current" State:

StateMachine.h

class StateMachine : public Application
{
private:
    State * currentState;

public:
    void handleKeyPress(int k) { currentState->handleKeyPress(k); }
    void handleMouseClick(int b) { currentState->handleMouseClick(b); }

which -- to me, looking at the big picture now -- seems a bit crazy because now each time the user moves the mouse, presses a key, or breathes the wrong way I'm going through a chain of THREE virtual method calls?

Every class in the chain seems to implement this same Application interface of handleThis() and handleThat() and enter() and exit() and so forth. StateMachine might as well inherit from Application, and while we're at it, each State could also! But each of them ends up passing the buck down to the next lower layer:

[MainLoop] keyPressed:
  `-> [MyGame] handleKeyPress()
        `-> [StateMachine] handleKeyPress()
                   `-> [State] handleKeyPress()
                          `-> actual code that handles keyboard input

Should I be trying to "flatten" this architecture, so to speak, to avoid all this virtual method chaining & delegation? If so, how do I do that while still maintaining a reasonable separation of concerns, where Application, MyGame, StateMachine, and each individual State are not all stuffed into the same class? (Or maybe they should be.)

I may be worrying about optimization prematurely, since some of these virtual method calls (esp. from StateMachine --> State) can be inlined. So who knows, maybe this design is not all that unreasonable.

Obviously: first-time game architecture ever, guys... this exercise has been quite humbling.

share|improve this question

2 Answers 2

up vote 7 down vote accepted

Your class tree in general confuses me, namely the fact that you have multiple "Application" derived classes that presumably get implemented all in the same application. It further seems to be a grab bag of all sorts of unrelated functionality... simulation state management, layout, rendering, input... which seems to be why you have so much forwarding: These classes don't really have any reason to be dealing with these events. This is most evident by the fact that they don't deal with these events: they just forward them on for other classes to deal with.

Sometimes forwarding is appropriate... most components in a UI render their children, for example. Sometimes this is best handled with a single base class that forwards everything, leaving the classes deriving from that to override the methods they don't want forwarded. But here I'd rather break the classes up, and bring the state machine closer to the surface.

Say instead we had these classes. They're effectively the base state classes, each corresponding to one concern only, not meant to be used with multiple inheritance:

BaseStates.hpp

class GameSimulationState
{
    virtual bool isPaused() const = 0;
    virtual void pause() = 0;
    virtual void resume() = 0;
};

class RenderState
{
    virtual void resize(int w, int h) = 0;
    virtual void render() = 0;
};

class LocalPlayerInputState
{
    virtual void handleKeyPress(int key) = 0;
    virtual void handleKeyRelease(int key) = 0;
    virtual void handleMouseClick(int btn) = 0;
};

This could perhaps be all tied together by a final "ApplicationState" containing these states, although they don't need to be. Imagine we want to implement the escape menu of an RTS. By splitting things up like this, we gain more flexibility.

Consider multiplayer: Perhaps the user can't pause? Or can only pause so many times? We're free to substitute a different SimulationState for multiplayer vs single player without having to reimplement rendering and input, right at this level. We couldn't really do that with a single monolithic "Application" without breaking it down at some later date in some subclass.

Maybe we want to implement a fancy effect where we swish between two screens when bringing up the pause menu: We can do this with a new render state that references the render state we were switching out of and the render state we were switching to. We can reuse this for other screens, even though we might not even have a simulation state (maybe we're doing the intro movies and ESRB ratings etc.)

And if you're doing local coop where everyone has their own gamepad with built in keyboard, you can have multiple LocalPlayerInputStates without having multiple SimulationStates. Maybe player one is looking at their inventory like a noob while player two does all the killing ;)

isRunning()/exit() can be simplified into not even being part of the state, or ever specialized or forwarded: If an application doesn't want to be running anymore, it can simply call exit().

This isn't the best or only way to break things up, but it hopefully is at least a decent example of how it can be done. We could keep just the StateMachine class, and have it expose what the current states are (reasonable: we're focusing on letting it manage states, selecting what new states to use, things of that nature) rather than a bunch of rendering and input specific stuff that only serve to forward things to the true workhorse: the states.

MainLoop2.cpp

while(isRunning())
{
    while(win->pollEvent(event))
    {
        switch(event.type)
        {
        case sf::Event::Closed:
            exitMainLoop();
            break;

        case sf::Event::Resized:
            // Assuming everything is initalized to states, even if they do nothing,
            // instead of doing a bunch of if() checks
            stateMachine->currentRenderState()->resize(event.w, event.h);
            break;

        case sf::Event::KeyPressed:
            stateMachine->currentInputState()->handleKeyPress(event.key));
            break;

        case sf::Event::KeyReleased:
            stateMachine->currentInputState()->handleKeyRelease(event.key));
            break;

    // ... etc.
share|improve this answer
    
Thank you @MaulingMonkey for taking the time to read my long-winded n00b question, and for composing such a well-written response! It may be a trivial and obvious pattern to an experienced SW architect, but this is the type of thing a beginner could get hung up on for days, sadly... You hit the nail on the head: it makes no sense for everything to inherit from this vague monolithic Application interface. I also didn't want to write an unwieldy: app->stateMachine()->currentState()->handleWhatever() explicitly over and over again either. So -- flattening it is! –  Mediocritus Feb 1 '13 at 7:44
    
Oh, I should clarify: the intent of calling app->exit() instead of just hard-calling the global exit() was to allow the program to do any last-minute things before actually killing itself. SFML sends the sf::Event::Closed on Alt-F4, or user closing the window frame via [X] corner control or OS context menu. Then we could prompt the user to save, ask are you sure, or at least have a chance to auto-quicksave the game in progress without prompting. But let me know if [spoiler alert] this turns out to be a horrible idea -- because I don't have any past experience to go on... –  Mediocritus Feb 1 '13 at 7:54
    
"handleUserWantsToExit()" might be a better name, but the idea is sound. Exiting all the way out of main 'cleanly' (what I originally guessed app->exit() was meant for) instead of through the quick-abort standard library exit() is also fine: It can make it easier to use memory leak detection tools, as things on the stack deallocate instead of showing up as false positives. –  MaulingMonkey Feb 2 '13 at 9:14

Why are you worrying about 3 virtual function calls per key-press? Modern computers can execute roughly 100,000,000,000 instructions per second and you're worried about a handful of function calls for events that happen once or twice per second?

Your structure is fine. Factor out the Application interface to one called EventHandler or similar, have the Game/Statemachine/States implement that instead, and you're done.

share|improve this answer
    
Point well taken! And thanks for the note of encouragement. Setting aside the performance nitpicks, my main concern was the "code smell" of everything in my call stack inheriting from this one all-encompassing Application interface... refactoring/flattening this out will definitely help me sleep better at night. –  Mediocritus Feb 1 '13 at 7:35

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.