I am, of course, assuming that everybody is familiar with the code and knows what I'm talking about. Sorry about that -- I need to remember to be detailed about these things.
I'm using NAS2D which has what we called a State Machine. Basically, you derive a class from an object called State. State has the basics for automatically having update and init functions being called. The state machine pretty much just calls init() function when a state comes into focus and calls update() every frame. The idea behind it was to break apart game logic. It's not the greatest system in the world and I'll be extending functionality in the future but the general idea is that I create a state that represents a particular state in a game and it defines all of the logic, etc.
In NAS2D, you can subscribe to specific events including mouse motion, mouse buttons, key handling, window events, joystick events and probably in the future network events.
GameState in the case of OutpostHD is the state in which the tile map is displayed. It draws the mouse and responds to clicks within the tile window. It also handles responding to UI events. E.g., when a UI Control gets clicked on, it raises an event which the GameState object is subscribed to. Say, for instance, ButtonTurns... when it gets clicked it raises an event. GameState subscribed to ButtonTurns.Click() and used void buttonTurnsClicked() as it's 'delegate' (listener).
I haven't shown any code atm because I figured this was a high level problem but I'll provide some sample code to illustrate my point.
class GameState
{
public:
GameState();
~GameState();
protected:
bool update();
void onMouseDown(int x, int y, MouseButton button);
private:
Button btnTurns;
Button btnSystem;
Button btnRobots;
Menu menuRobots;
Menu menuTubes;
Dialog diggerDirectionDialog;
Dialog tubeTypeDialog;
TileMap map;
};
Fairly straight forward. Again, this is simplified, but I hope it illustrates my point.
GameState connects itself to the EventHandler class. The EventHandler class just takes in system events and forwards them out to subscribers. So if the application gets a MouseDown event from the operating system, EventHandler captures that and throws it at any subscribers. GameState connects its onMouseDown() function to EventHandler for MouseDown events. So any time a user clicks the mouse within the application window, GameState's onMouseDown function gets called. It's handled similarly to Qt's Signals & Slots.
GameState::onMouseDown() looks generally like this.
void GameState::onMouseDown(int x, int y, MouseButton button)
{
if(button == MOUSE_BUTTON_LEFT)
{
if(isPointInRect(x, y, map.area())
{
// do stuff
}
}
}
This is a fairly simple setup. Straight forward, effective, does the job.
UI elements, called Control's in OutpostHD (modeled off the Windows API), ALSO subscribe themselves to these same events. They do so automatically upon instantiation. This is to make it easy for me when I'm building UI's so I don't have to manually connect each Control to the EventHandler or forward events manually to each Control (e.g., if GameState::onMouseDown() is called, I don't have to do something like btnTurns.onMouseDown(x, y, button); ).
GameState::onMouseDown() responds to MouseDown events in OutpostHD specifically when it comes to inserting objects into the TileMap or picking tiles, etc. That's its purpose. But consider this -- if a Control is in the same space as the TileMap, both the Control AND GameState will handle the MouseDown event at the same time (well, sequentially based on their subscribe order but that's not the point). In most cases this isn't a problem but what about a case where GameState is attempting to insert a digger but the DiggerDirection Dialog is within the bounds of the TileMap. Now the DiggerDirection dialog is responding while GameState is ALSO responding which can cause weird bugs (this was an actual bug in OutpostHD's code). So to prevent this, we do something like this:
void GameState::onMouseDown(int x, int y, MouseButton button)
{
if(button == MOUSE_BUTTON_LEFT)
{
if(isPointInRect(x, y, diggerDirectionDialog.area()) && diggerDirectionDialog.visible())
return;
if(isPointInRect(x, y, map.area())
{
// do stuff
}
}
}
Simple. Does the job. Easy enough to understand.
But what about the case where we start having a lot of potential dialog's? For each individual dialog or UI element that will also potentially be within the area of the TileMap, we need to add a special case for it. Which leads to larger code, etc. Could use a list and a loop to iterate over the list of UI Control's but that doesn't address the other problems.
But even beyond all of this, all of the initialization and event handler code for all of the UI Control's leads to a GameState class that starts to look like this:
class GameState
{
public:
GameState();
~GameState();
protected:
bool update();
void onMouseDown(int x, int y, MouseButton button);
private:
void onBtnTurnsClicked();
void onBtnSystemClicked();
void onBtnRobotsClicked();
void onMenuRobotsSelection();
void onMenuTubesSelection();
void onDiggerDirectionDialogSelection();
void onTubeTypeDialogSelection();
Button btnTurns;
Button btnSystem;
Button btnRobots;
Menu menuRobots;
Menu menuTubes;
Dialog diggerDirectionDialog;
Dialog tubeTypeDialog;
TileMap map;
};
You can see where the problem lies. As the UI gets more functional and more and more controls are added, you get more and more UI event handler functions and code. Not to mention each UI Control not only needs to be instantiated but also needs to be set up. That's kind of what you'd expect when developing a UI (I thought my setup was insane until I saw that both Qt and .Net do it practically the same way) and you end up with large functions that have a lot of this sort of code:
mBtnTurns.image("ui/icons/turns.png");
mBtnTurns.size(30, 30);
mBtnTurns.position(100, 200);
mBtnTurns.click().Connect(this, &GameState::btnTurnsClicked);
Normally when I develop in C++ I define a class in a header file (GameState.h) and all of its definition code in a source file (GameState.cpp). Because of all the UI code, the source file was getting huge and somewhat unmanageable. So I broke all of the UI handling and initialization code into a separate source file (GameStateUi.cpp). And it got my gears turning -- there are two ways of effectively using OOP. One, for Inheritance (Object B IS AN Object A). Two, for Composition (Object C HAS AN Object A AND AN Object B). Why don't I just create a class for GameState called GameStateUi that takes all of the initialization and event handling out of GameState and does it itself? That way GameState can focus on the LOGIC of the game and GameStateUi can handle the MANAGEMENT of the UI. The idea being that you'd get the following code readability improvements:
class GameState
{
public:
GameState();
~GameState();
protected:
bool update();
void onMouseDown(int x, int y, MouseButton button);
private:
GameStateUi ui;
TileMap map;
};
and
void GameState::onMouseDown(int x, int y, MouseButton button)
{
if(button == MOUSE_BUTTON_LEFT)
{
if(ui.mouseInUiElement(x, y));
{
return;
}
else if(isPointInRect(x, y, map.area())
{
// do stuff
}
}
}
I apologize for the length of this post but it's clear that I needed to provide specific examples of what I meant and what, exactly, I was asking about.
What exactly is this GameState object actually responsible for handling?
In this case, everything when the user is looking at the tile map. Basically it's the game itself. I remember another 2D game framework that uses the term Scene. Same basic idea.
Later on I will also have objects called TitleState (title screen and title menu options) and GameStartState (selecting difficulty, planet type, etc.). These objects represent specific 'states' of the game on a high level scale. State's themselves could probably be broken down into smaller states (e.g., GameState would be broken down into NO_INSERT, INSERT_STRUCTURE, INSERT_ROBOT, etc.) but I didn't see the need for that.