Game Controls and Logic Question

Started by
11 comments, last by Tac-Tics 12 years, 8 months ago
I've been messing around with OpenGL/GLUT lately, and there's always been this design problem bugging me since I started creating Windows applications (instead of console) back with SDL. I would think it appropriate to separate the window control stuff from the actual game logic (e.g. separating the code that checks for mouse clicks from the code that actually moves the player to the location that you clicked the mouse). But this has posed a great deal of stress on me. I've yet to figure out a way to do this without using a ton of globals (or by faking globals through using static class singletons). Can someone please post a very small design snippet of an efficient and organized interface or help me out with this problem somehow?

One idea I had was to have all the game data extend a base window class which has all the control information in it like so:

class Window
{
// stuff for window events and callbacks
// updating key states and mouse states, etc.
};

class Game
{
public:
void Update()
{
// loop through all game objects and
// call their update functions
}

private:
GameObject objs[10]; // create 10 game objects
};

class GameObject : public Window
{
// inherits all of the window traits such
// as the current mouse coordinates, window width/height,
// etc.
};


But from a true inheritance viewpoint, it makes absolutely no sense whatever to have a game object inherit window properties. However, what alternative is there?
Advertisement
Use callbacks or delegates to break the direct dependency.
Can you show a small example of exactly how you mean?
Something like this:

class MouseListener
{
public:
virtual ~MouseListener(){}
virutal void onClick(int x, int y, MouseButton button, /* ... */) = 0;
};

class Window
{
public:
Window(Mouse *mouse, MouseListener *mouseListener)
:
mouse(mouse),
mouseListener(mouseListener)
{
}

void update()
{
if(mouse->clicked())
{
mouseListener->onClick(mouse->x(), mouse->y(), mouse->button(), /* ... */);
}
}
private:
Mouse *mouse;
MouseListener *mouseListener;
};

class Player
{
public:
void shoot();

// ...
};

class PlayerShootMouseListener : public MouseListener
{
public:
PlayerShootListener(Player *player)
:
player(player)
{
}

virtual void onClick(/*... */)
{
player->shoot();
}
private:
Player *player;
};

int main()
{
Player player;
PlayerShootMouseListener mouseListener(&player);
Mouse mouse;
Window window(&mouse, &mouseListener);

while(true)
{
// input checking
mouse.click(42, 13, MouseButton::Left);

// Later ...
window.update();

// Rendering etc...
}
}

As you can see, the GUI knows nothing about game objects and the game object knows nothing about the GUI. If you're using boost then something like boost::function<> can be used to reduce the amount of boilerplate code required.

Something like this:

class MouseListener
{
public:
virtual ~MouseListener(){}
virutal void onClick(int x, int y, MouseButton button, /* ... */) = 0;
};

class Window
{
public:
Window(Mouse *mouse, MouseListener *mouseListener)
:
mouse(mouse),
mouseListener(mouseListener)
{
}

void update()
{
if(mouse->clicked())
{
mouseListener->onClick(mouse->x(), mouse->y(), mouse->button(), /* ... */);
}
}
private:
Mouse *mouse;
MouseListener *mouseListener;
};

class Player
{
public:
void shoot();

// ...
};

class PlayerShootMouseListener : public MouseListener
{
public:
PlayerShootListener(Player *player)
:
player(player)
{
}

virtual void onClick(/*... */)
{
player->shoot();
}
private:
Player *player;
};

int main()
{
Player player;
PlayerShootMouseListener mouseListener(&player);
Mouse mouse;
Window window(&mouse, &mouseListener);

while(true)
{
// input checking
mouse.click(42, 13, MouseButton::Left);

// Later ...
window.update();

// Rendering etc...
}
}

As you can see, the GUI knows nothing about game objects and the game object knows nothing about the GUI. If you're using boost then something like boost::function<> can be used to reduce the amount of boilerplate code required.


Lovely example, thank you very much! :)

Edit: Would it be a good idea to make Window class have a dynamic array of mouse listeners and to say loop through all of them and call their individual click functions when the mouse is clicked? This way I can have multiple mouse listeners. I just wonder if that would effect the performance greatly in some negative way.
Relative to your application, mouse clicks are rare. It probably won't matter if you have a whole bucket of mouse listeners.

Generally there are two types of game GUI - the kind where clicking anywhere triggers the same limited set of responses (move/attack/shoot) or a button oriented GUI. In button oriented GUIs, you might want each button to have a listener rather than iterating across every listener.

For hybrid games (e.g. RTS with a button oriented control panel and an action-oriented game map) you might want to divide the screen view port into two separate objects. But for such a game you probably shouldn't be rolling your own GUI library.

Relative to your application, mouse clicks are rare. It probably won't matter if you have a whole bucket of mouse listeners.

Generally there are two types of game GUI - the kind where clicking anywhere triggers the same limited set of responses (move/attack/shoot) or a button oriented GUI. In button oriented GUIs, you might want each button to have a listener rather than iterating across every listener.

For hybrid games (e.g. RTS with a button oriented control panel and an action-oriented game map) you might want to divide the screen view port into two separate objects. But for such a game you probably shouldn't be rolling your own GUI library.


Thank you so much. You've been very helpful, and that polymorphism example you've showed me is really going to help with a lot of my future applications.
Using polymorphism is a simple way to do it, requiring no additional dependencies. Using boost::function (soon to be std::function) you won't even need to write such interfaces. For stateless callbacks a function pointer can be the simplest solution, but it is also the least flexible.

As an aside, we generally prefer not to mark threads as "solved" on these forums. This is a discussion. For example, someone else may want to weigh in with some more advice, or perhaps a concrete example using boost/std function or might have a question about this design.

Using polymorphism is a simple way to do it, requiring no additional dependencies. Using boost::function (soon to be std::function) you won't even need to write such interfaces. For stateless callbacks a function pointer can be the simplest solution, but it is also the least flexible.

As an aside, we generally prefer not to mark threads as "solved" on these forums. This is a discussion. For example, someone else may want to weigh in with some more advice, or perhaps a concrete example using boost/std function or might have a question about this design.


Ah, sorry about that. Didn't know.
What I'm about to suggest is most-likely overkill for most hobbyist games. While the listener pattern does work great for this, the logic will still be tied to the specific callbacks. If you wanted to provide support for joysticks or other external controllers your logic would need to be re-factored.

You can completely decouple input from logic by having a central location where window input events are translated into logic input events. For example: you can have events like:


class PlayerMoveEvent : public LogicEvent {

public:

PlayerMoveEvent(const Vector3& movement);

};

class PlayerShootEvent : public LogicEvent {

public:

PlayerShootEvent(BulletType bulletType, const Vector3& direction);

};


These events are dispatched to whatever code handles the logic. This could also be implemented with listeners, however I prefer using events which gives me the option of asynchronous event handling (also my logic is implemented in Lua and events are more natural to implement than polymorphism).

This topic is closed to new replies.

Advertisement