📜 ⬆️ ⬇️

C ++ Game State Management

Hello dear readers!

We actively disagree on the third dopning of the extremely successful book “We study C ++ through the programming of games” . Therefore, today your attention is invited to the translation of an interesting article on one of the narrow topics related to the programming of games in C ++. We also ask you to participate in the survey.

I first got the impression of various states of the game many years ago when I was watching one demo. It was not a “preview of the upcoming game”, but something old-school, “from the site scene.org ”. Anyway, such demos completely imperceptibly passed from one effect to another. From any two-dimensional whirlwinds, the game could immediately switch to the complex rendering of a three-dimensional scene. I remember that it seemed to me that several separate programs were required to implement these effects.

Multiple states are important not only in demos, but in any games. Any game starts with a screensaver, then opens a certain menu, after which the gameplay begins. When you are finally defeated, the game changes to the “game over” state, which is usually followed by a return to the menu. In most games, you can simultaneously be in two or more states. For example, during gameplay, you can usually open a menu.
')
As a rule, multiple states are processed using a series of if
if
, switches and cycles. The program starts with the splash screen and remains in this state until the key is pressed. Then the menu is displayed, it remains on the screen until you make a selection. Then the gameplay begins, working in a loop until the game is over. At any time during the game cycle, the program should check what should be done - draw the menu or simply display the next frame. In addition, that part of the program that is busy processing events should check whether user input will affect the menu or game state as such. All this develops into the main loop, which is very difficult to track, which means it is difficult to debug and maintain.

What is a condition?

As mentioned above, the state is almost a separate program in the game. In each state, events are processed in their own way, their elements are drawn on the screen. Each state processes its own events, updates the game world, and draws the next frame on the screen. So we defined three methods that should be contained in our state class.

In addition, the game state should be able to load the graphics and initialize itself, as well as get rid of unnecessary resources when the task is solved. It happens when we want to pause a state and resume it later. For example, we want to pause the game for a while in order to display the menu. So far we have such a game state class:

 class CGameState { public: void Init(); void Cleanup(); void Pause(); void Resume(); void HandleEvents(); void Update(); void Draw(); }; 


Such a scheme should fully meet all our needs related to the game state. It turns out a beautiful base class from which we can inherit others that correspond to each of the game's states — the intro, menu, gameplay, etc.

State manager

Next, you need to develop a mechanism for managing these states - the state manager. In my code, the state manager is part of the game engine. Another programmer could create a separate class for the state manager, but it seemed to me that it would be easier to add it directly to the engine. Again, you can specifically find out what the game engine should do, and then write for it a class that will implement these functions.

In our simple example, the engine should only initialize the SDL and perform a cleanup when everything is done. Since we are going to use the engine in the main loop, we will also need to check if it continues to work, order it to finish the work, process ordinary events that occur in the process, update the game world, draw the sequence of frames.

The part of the engine that is associated with the state manager is, in essence, very simple. In order for some states to exist on top of others, you need to arrange them as a stack. I'm going to implement such a stack using a vector from STL. In addition, I will need methods for changing states, as well as for moving them up and down in a stack.

So, the class of the game engine takes the following form:

 class CGameEngine { public: void Init(); void Cleanup(); void ChangeState(CGameState* state); void PushState(CGameState* state); void PopState(); void HandleEvents(); void Update(); void Draw(); bool Running() { return m_running; } void Quit() { m_running = false; } private: //   vector<CGameState*> states; bool m_running; }; 


Writing some of these functions is easy. HandleEvents()
, Update()
Update()
and Draw()
Draw()
- all of them will simply call the appropriate function from the state that is at the top of the stack. Since this will often require access to the game engine data, I will return to the game state class and add a pointer to the game engine class as a parameter to each of these member functions.

The last question is how to move between states. How does the engine know when to go from one state to another? No Only the current state will know about the need to move to the next state. So, we will return again to the class of game states and add a function there to switch between them.

In this case, we will create an abstract base class, and we will make the majority of its members pure virtual functions. This way, it is guaranteed that the inherited class will implement them. With all these changes, the finished game state class will look like this:

 class CGameState { public: virtual void Init() = 0; virtual void Cleanup() = 0; virtual void Pause() = 0; virtual void Resume() = 0; virtual void HandleEvents(CGameEngine* game) = 0; virtual void Update(CGameEngine* game) = 0; virtual void Draw(CGameEngine* game) = 0; void ChangeState(CGameEngine* game, CGameState* state) { game->ChangeState(state); } protected: CGameState() { } }; 


Now adding states to our game is extremely simple: we inherit the base class and define seven pure virtual functions. Since in any case we will need no more than one instance of any particular state, let's implement them in the form of singles. If you are not familiar with the loner pattern, I’ll explain: it just allows you to make sure that the object exists in exactly one instance. For this, the constructor is made protected, and then a function is made that returns a pointer to a static instance of this class.

So that you can imagine how this method can simplify the whole game, pay attention to the following listing, where the whole main.cpp
file is located main.cpp
:

 #include "gameengine.h" #include "introstate.h" int main ( int argc, char *argv[] ) { CGameEngine game; //   game.Init( "Engine Test v1.0" ); //   game.ChangeState( CIntroState::Instance() ); //   while ( game.Running() ) { game.HandleEvents(); game.Update(); game.Draw(); } //   game.Cleanup(); return 0; } 


Files

In this example, three states are described: a screen saver serving on a black background, gameplay, and a game menu. At the time of working with the menu, the gameplay pauses, and after closing the menu it resumes. Each state corresponds to a simple background image.

stateman.zip - Source code, graphics and project files for Visual C ++
stateman.tar.gz - Source code, graphics and project files for Linux.

The sample code uses SDL. If you are not familiar with SDL, read my tutorial Getting Started with SDL . If you do not have SDL installed on your computer, you will not be able to compile and run this example.

Resources

If you are just starting to learn C ++, you should definitely get acquainted with the book " Learning C ++ through Game Programming. " This is a great introduction to the C ++ programming language; the author uses simple games as examples. For intermediate level programmers, I recommend C ++ For Game Programmers . This book will help you to deepen your knowledge of C ++. Finally, to learn how to master patterns, read the book Design Patterns by Gang of Four.

Source: https://habr.com/ru/post/276165/


All Articles