-
Notifications
You must be signed in to change notification settings - Fork 19
States
A common idiom for managing game states is the 'State Stack', an implementation of which is included with xygine. The use of the StateStack
class is not enforced, however, as it may be preferable to employ a user defined state management system.
The idea of a state stack is to allow several states to be active at once by 'stacking' them on top of each other before updating and drawing them from the top down. Any state can consume the update or event input, preventing those inputs from reaching the state below. For example while a game state is active a player may wish to pause the game. The game state itself should not be removed, as the player will lose all their progress. Pushing a 'pause' state on to the top of the stack allows consumption of input events, as well as the update time - preventing the game state from being updated, effectively pausing it. The pause state, however, will still receive inputs and updates allowing it to remain responsive. To resume the game the 'pause' state is simply popped off the top of the state stack. Another example may be a game's menu: A single state which renders the background sits at the bottom of the stack, while the main menu, options or high scores states are pushed on to and popped off of the top as they are browsed. In such a case the menu states would not consume updates, so that the background will be updated.
To use the StateStack
class it is recommended an instance be created as a member of the class which inherits xy::App
. From here xygine can pass down any events, messages and update time to every active state. An example of forwarding this data to a state stack can be seen in the source of the example application. Each function (handleMessage()
, handleEvent()
etc.) passes on the relevant data to the state stack.
Concrete game states are created by inheriting xy::State
and implementing the virtual functions:
bool handleEvent(const sf::Event& evt)
void handleMessage(const xy::Message&)
bool update(float dt)
void draw()
StateID stateID() const
handleEvent()
and update()
return a bool depending on whether or not the state stack should continue to update states below it. In the 'pause' state example above both of these functions would return false so that the 'game' state underneath would not receive events or updates, although in the menu example these functions would return true. StateIDs are important unique identifiers for each state. Normally this can be defined as an enum, but StateID is simply an alias for a 32 bit integer, so a value may be derived in any way which ensures that it is unique. Once a new unique state has been created it needs to be registered with the state stack via registerState<T>(StateID)
. This is done for each state which will exist at some point during the game as in this example. There is also an overload for registerState
which employs a variadic template, allowing custom construction parameters to be passed to the state being registered. This is useful for scenarios where multiple menu states might wish to share a single font resource to reduce loading times. It should be noted that parameters are passed by reference and so any objects required for state construction should live for at least as long as the state stack itself. Once a state is registered with the stack a new instance can be created at any time using the push function:
stateStack.pushState(StateID);
passing in the StateID of the requested state. To remove the topmost state popState()
can be called. Alternatively the entire stack can be cleared with a call to clearStates()
. IMPORTANT When using a state stack in a xygine application the finalise()
function of xy::App
should be implemented, in which a call to clearStates()
should be made. This will ensure any active states will be properly removed before the application exits, which may otherwise lead to errors.
As states are implemented via unique pointers and allocated on the heap, creating a state at run time can lead to a lengthy pause as assets used by the state are loaded. As a state usually encompasses a large section of a game or menu it is often desirable to have resource managers as members of individual states. This will ensure proper management of resources during the lifetime of the state. As a utility it is possible to display a loading screen, updated in a seperate thread, while assests are loaded. In a state constructor the flow would look something like
launchLoadingScreen();
//---load assets, maps etc---//
quitLoadingScreen();
It should be noted that, as the loading screen uses its own renderering thread, on linux systems XInitThread() must be called from the main() function, before any other code is executed. Loading screens can be customised by overriding updateLoadingScreen()
in the State
class. It is recommended that loading screens should be as resource-light as possible, else they themselves will suffer long loading times...