Mode Management Engine

From Hero of Allacrost Wiki
Jump to: navigation, search

Introduction[edit]

This page describes the game mode stack mechanism in the Allacrost engine and how these game modes are processed and managed. A game mode is simply a state of operation of the game. For example, walking around on a map may be one mode, while traversing a menu may be another. There are a small number of "core" modes implemented in Allacrost that control the core functionality of the game, but a nearly unlimited number of modes may be created for the purpose of mini-games, etc. All game modes in Allacrost are processed precisely the same way from the engine's perspective, so it is critical that a programmer understand this method before creating their own game mode. The code for game mode management can be found in src/engine/mode_manager.h and src/engine/mode_manager.cpp.

GameMode Class[edit]

The GameMode class is the class that all game modes inherit from. The GameMode class is a virtual class, hence you would never want or need to create an instance of this class by itself. Below is the definition of this class, so lets examine it to determine what it represents and what it actually does.

class GameMode {
	friend class GameModeManager;
	
protected:
	//! Indicates what 'mode' this object is in (what type of inherited class).
	uint8 mode_type;
	
private:
	//! Copy constructor is private, because making a copy of a game mode object is a \b bad idea.
	GameMode(const GameMode& other);
	//! Copy assignment operator is private, because making a copy of a game mode object is a \b bad idea.
	GameMode& operator=(const GameMode& other);
	// Note: Should I make the delete and delete[] operators private too?
public:
	GameMode();
	//! \param mt The mode_type to set the new GameMode object to.
	GameMode(uint8 mt);
	//! Destructor is virutal, since the inherited class holds all the important data.
	virtual ~GameMode();
	
	//! Updates the state of the game mode.
	virtual void Update() = 0;
	//! Draws the next screen frame for the game mode.
	virtual void Draw() = 0;
	/** \brief Resets the state of the class.
	***
	*** This function is called whenever the game mode is made active (ie, it is made the new active game mode
	*** on the top of the game modestack). This includes when the game mode is first created and pushed onto the
	*** game stack, so in that manner it can also be viewed as a helper function to the constructor.
	**/
	virtual void Reset() = 0;
}; // class GameMode

Mode Types[edit]

First we observe the mode_type member. This is simply an identifier for the mode so that the mode manager can determine what type of inherited game mode object it is dealing with (checking a variable is much easier and safer than trying to dynamically cast a class object). This member is set to 0 (invalid) by default, so it needs to be assigned a non-zero value for it to be a real mode. The values for mode_type are defined just above the GameMode class. Whenever you wish to create a new mode, you'll have to reserve a mode type constant for it in this file. The current reserved modes are defined below.

//! \name Game States/Modes
//@{
//! \brief Different modes of operation that the game can be in.
const uint8 MODE_MANAGER_DUMMY_MODE  = 0;
const uint8 MODE_MANAGER_BOOT_MODE   = 1;
const uint8 MODE_MANAGER_MAP_MODE    = 2;
const uint8 MODE_MANAGER_BATTLE_MODE = 3;
const uint8 MODE_MANAGER_MENU_MODE   = 4;
const uint8 MODE_MANAGER_SHOP_MODE   = 5;
const uint8 MODE_MANAGER_PAUSE_MODE  = 6;
const uint8 MODE_MANAGER_QUIT_MODE   = 7;
const uint8 MODE_MANAGER_SCENE_MODE  = 8;
const uint8 MODE_MANAGER_WORLD_MODE  = 9;
//@}

Updating and Drawing[edit]

Next we notice the Update() and Draw() functions. Since both of these functions are purely virtual, that means that the inheriting class is required to define them. These functions are pretty straight-forward in what they do. The Update() function that you create in your inherited class will almost certainly need to make one or more references to the uint32 SystemManager->GetUpdateTime() singleton method in order to find the amount of time (in milliseconds) that elapsed between the last call and current call to Update(). The Draw() function does just that: it draws objects and images to the screen. Note that it actually doesn't drawn them directly on the screen, but rather on a back buffer, so the user can not see the objects being drawn in real-time. Right after the Draw() call returns, another function in the main game loop will take the back buffer and display it on the screen.

Game modes are usually very complex, requiring the updating and drawing of multiple sprites, menus, or other objects all in a single frame. When implementing a game mode, it is advised to split these tasks into various helper functions, rather than trying to write all of the update code and all of the draw code in a single function. This improves readability and also makes it easier to modify your update or draw code when changes need to be made.

Restoring State[edit]

Finally we have the Reset() function. This function is called whenever the game mode is made the active game mode (only the active game mode will have its Update() and Draw() methods called on an iteration through the main game loop). This function is created so that the programmer can make sure that they can make correct assumptions about the state of the engine. For example, all game modes use their own video engine coordinate system for drawing objects to the correct places on the screen. If another game mode changes the active coordinate system in the video engine, we want to be able to restore the desired coordinate system for our game mode. Thus, the Reset() function is executed once for each time that the game mode becomes the active game mode, just before its Update() and Draw() functions are called. The result of any code executed in this Reset() function can thus be assumed to remain unchanged to the rest of the code in the game mode, so long as the mode does not make changes to this result elsewhere.


GameModeManager Class[edit]

The GameModeManager class is responsible for handling all of the GameMode objects in a stack structure, where the top stack item is the active game mode, or AGM. This is a singleton class that is referenced by the handle ModeManager, which is defined in the hoa_mode_manager namespace. The duties of this mode management engine are listed as follows:

-# Keeping a stack of pointers to all game mode objects that have been created -# Deletion of game mode objects when they are finished, or when the game exists -# Executing the Update() and Draw() methods for the active game mode on each iteration through the main game loop -# Calling the Reset() method when a non-active game mode is put on the top of the stack and made active -# Maintaining consistency when game modes are pushed or popped off of the stack

Mode Management[edit]

The only tricky part of the mode manager's job is when it comes time to change the state of the stack (i.e., when game modes are pushed or popped off the stack). But who does the pushing and the popping of game modes? The answer is the game modes do this themselves. For example, when walking around on a map (MapMode) a random encounter may occur, forcing the player to fight a battle. When MapMode detects that a battle has occurred, it creates a BattleMode class object and pushes it onto the game mode stack. This makes BattleMode the new active game mode, and forces MapMode to wait until the battle is finished before it regains control and may run again. When the battle is finished, BattleMode will simply make a call to the mode manager telling it to pop the top-most stack item (which is itself), causing the battle mode object to be deleted.

So what's the trick? The trick is that it is unsafe for battle mode (or any game mode) to self-terminate. When battle mode makes the call to the mode manager to pop the top-most stack item, the mode manager will delete battle mode, but battle mode still hasn't returned from its own code when it called the mode manager! So what the mode manager does is instead increment a private counter variable when BattleMode tells it to pop the AGM. Then, after battle mode has returned and before the next Update() call is made to the AGM, the mode manager examines itself, and determines if any game modes need to be popped or pushed, and does so if it finds either or both of these conditions to be true. After it has determined that the current internal stack of game modes that it maintains is correct, it will then call the Reset() function if there is a new active game mode, and then the Update() function for the AGM as usual. Below are the defintions for the two push and pop methods of the GameModeManager class.

	//! \brief Increments by one the number of game modes to pop off the stack
	void GameModeManager::Pop();
	/** \brief Pushes a new GameMode object on top of the stack.
	*** \param gm The new GameMode object that will go to the top of the stack.
	*** \note This should be obvious, but once you push a new object on the stack
	*** top, it will automatically become the new active game state.
	**/
	void GameModeManager::Push(GameMode* gm);


Multiple push and pop operations may be done in the same iteration of the main game loop (although if this is the case, you should be questioning whether this is necessary in your own code). The push and pop operations are kept in-order with respect to themselves, but not with respect to each other. First, all pop operations are processed, and only then are all push operations processed. So if your game stack looks like A->B->C->D->E (where E is the top of the stack and A is the bottom), if E makes 2 Pop() calls, a Push(A) call, another Pop() call, and a Push(X) call, the new stack will look like: A->B->A->E. If the user then tries to make 6 Pop() calls and one Push(C) call, the stack will be emptied, a warning message will be printed because someone requested to pop more modes than were available, and C will become the only item on the game mode stack. If the user requests to pop all of the game modes off the stack without pushing a new mode, the game mode manager will catch this, fire a warning, and create a BootMode object and place it on the empty stack. Thus, it will never be the case that the game mode stack is completely empty (unless the game is exiting). Finally, note that game modes can not be arbitrarily removed or inserted in places other than the top of the stack, just like you can not remove an arbitrary plate from a stack of plates. Game modes may only be pushed on to the top of the stack, or removed from the top of the stack.

Example[edit]

Let's take a quick look at a real example from the Allacrost code. When in boot mode if the player requests to start a new game, first boot mode pops itself and then it pushes a new map mode object onto the stack. Note that if the pop call were to be made after the push call, the resulting changes to the game mode stack would be exactly the same.

ModeManager->Pop();
MapMode *MM = new MapMode();
ModeManager->Push(MM);

Unexpected Changes in Game State[edit]

Please be aware that the active game mode does not always have sole control of modifying the game stack. If the user registers a pause or quit event during the game, the input management code automatically handles this case, and will push either a new PauseMode or QuitMode onto the stack. This means that your game mode may be unexpectedly interrupted by the user. This can cause issues in some cases, for example, where music and visuals need to be synchronized in a scene. There are mechanisms available for game modes to pre-emptively define what should happen to audio when a pause or quit event is registered (whether to pause music, silence it, reduce the volume, etc). Those mechanisms will be added to this page at a later date.


Summary[edit]

You should now have a good understanding of the operation of the Allacrost game mode stack implementation. The important points you should take away from documentation are listed below.

- All states of operation of the game are called game modes, and these are represented by classes that inherit from the GameMode class. - The game maintains a stack of all game modes in existance, where the top-stack item is called the active game mode. - The active game mode (AGM) is the only game mode that will have its Update() and Draw() methods executed for each iteration of the main game loop - Whenever a game mode becomes the AGM, it has its Reset() method invoked so that it can restore any game state that it needs to before its update or draw code is executed - Game modes may be created and pushed onto the top of the stack, or can be popped from the top of the stack and removed. Game modes that are popped from the stack are deleted. - When performing multiple push and pop operations on the game stack, first all of the pop operations are processed in-order, and then all push operations are processed in-order - The game stack state may be modified by code outside the realm of the AGM code (by a registered pause or quit event by the user, for example).