📜 ⬆️ ⬇️

Writing games in C ++, Part 2/3 - State-based programming

Writing games in C ++, Part 1/3 - Writing a mini-framework
Writing games in C ++, Part 3/3 - Classics of the genre

Hello, Habrahabr!

Congratulations if you read the first lesson! He is big enough. I promise that there will be less code, and more results :)
')

What is this part about?




In the next post will be natural games :)




2.1. States


Now it would be nice to understand what the game actually consists of.

Suppose we have a game where there are a lot of menus, levels and other “states”. How can you interact with them? It is clear that the code type:
void Update() { switch(state) { case State::MENU: // 100  case State::SETTINGS: // 200  case State::LEVEL1: //   } } 

Causes a cruel failure in terms of convenience.

How about each state to make its heir from some class with the name, say, Screen, and use it in the Game?

Create Screen.h
 #ifndef SCREEN_H #define SCREEN_H #include "Project.h" #include "Game.h" class Game; class Screen { protected: Game* game; public: void SetController(Game* game); virtual void Start(); virtual void Update(); virtual void Destroy(); }; #endif 

This class has an instance of Game, from where the heirs take pointers to Graphics and Input.
Its virtual functions for successors:


Screen.cpp
 #include "Screen.h" void Screen::SetController(Game* game) { this->game = game; } void Screen::Start() { } void Screen::Update() { } void Screen::Destroy() { } 


Update Game.h and Game.cpp
 #ifndef _GAME_H_ #define _GAME_H_ #include "Project.h" #include "Graphics.h" class Graphics; #include "Input.h" class Input; #include "Screen.h" class Screen; class Game { private: bool run; Graphics* graphics; Input* input; Screen* screen; public: Game(); int Execute(Screen* startscreen, int width, int height); Graphics* GetGraphics(); Input* GetInput(); Screen* GetScreen(); void SetScreen(Screen* screen); void Exit(); }; #endif 

The Screen object is included in the Game class and the Execute function is changed, where we transfer the object of our successor Screen from main.cpp.

Game.cpp
 #include "Game.h" Game::Game() { run = true; } int Game::Execute(Screen* startscreen, int width, int height) { graphics = new Graphics(width,height); input = new Input(); screen = startscreen; screen->SetController(this); this->screen->Start(); while(run) { input->Update(); screen->Update(); } screen->Destroy(); delete graphics; delete input; delete screen; SDL_Quit(); return 0; } Graphics* Game::GetGraphics() { return graphics; } Input* Game::GetInput() { return input; } Screen* Game::GetScreen() { return screen; } void Game::SetScreen(Screen* screen) { this->screen->Destroy(); delete this->screen; this->screen = screen; this->screen->SetController(this); this->screen->Start(); } void Game::Exit() { run = false; } 

The Execute method undergoes important changes - it processes the current state.
SetScreen sets a new state by resetting the old one.
GetScreen, in my opinion, is almost useless - except to reload the level in this way
 SetScreen(GetScreen()); 

But stupidity re-upload all resources. In general, decide for yourself :)

2.2. Compile! Compile!


Play around?
Open the main.cpp file and change it to the following state:
 #include "Project.h" class MyScreen : public Screen { public: void Start() { MessageBox(0,"Hello, HabraHabr!","Message",MB_OK); } }; int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) { Game game; return game.Execute(new MyScreen(),500,350); } 

All he does is display a standard Windows message.

Attentive user will pay attention to the fact that the window is not closed by clicking on the red cross, and the title of the window, too, would not hurt to remove. No problem - take work:
 #include "Project.h" class MyScreen : public Screen { private: Input* input; public: void Start() { input = game->GetInput(); SDL_WM_SetCaption("Hello, HabraHabr!",0); MessageBox(0,"Hello, HabraHabr!","Message",MB_OK); } void Update() { if(input->IsKeyDown('w') || input->IsExit()) game->Exit(); } }; int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) { Game game; return game.Execute(new MyScreen(),500,350); } 

We do not want to work with a black window, let's draw something?

 #include "Project.h" class MyScreen : public Screen { private: Input* input; Graphics* graphics; Image* test; public: void Start() { input = game->GetInput(); graphics = game->GetGraphics(); SDL_WM_SetCaption("Hello, HabraHabr!",0); test = graphics->NewImage("habr.bmp"); } void Update() { if(input->IsExit()) game->Exit(); graphics->DrawImage(test,0,0); graphics->Flip(); } }; int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) { Game game; return game.Execute(new MyScreen(),300,225); } 


A programmer who is more knowledgeable in terms of performance will immediately realize that it is pointless to draw pictures every cycle if they do not change their location. Indeed - lines
  graphics->DrawImage(test,0,0); graphics->Flip(); 

It is necessary to move from Update () to the end of Start ()

2.3. Results


I hope you were impressed and learned a lot. :)
Then go to the third lesson without a doubt.

For all questions, please contact the PM, and if you are not lucky enough to be registered in the Habré, write to me at izarizar@mail.ru

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


All Articles