Writing C ++ Games, Part 2/3 - State-based Programming

  • Tutorial
We write games in C ++, Part 1/3 - Writing a mini-framework
We write 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?


  • We will try to comprehend state-based programming, with which new levels and menus are made very easy.


In the next post there will be natural games :)




2.1. States


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

Let's say 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 a code like:
    void Update()
	{
        switch(state)
		{
		case State::MENU:
            // 100 строк
		case State::SETTINGS:
            // 200 строк
		case State::LEVEL1:
            // Страшно считать
        }
    }

It causes fierce neglect in terms of convenience.

How about making each state its own descendant 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 get pointers to Graphics and Input.
Its virtual functions for the heirs:
  • Start - call every time at start (state assignment)
  • Update - call every cycle
  • Destroy - a call to destroy (terminating a program or assigning another state)


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


Updating 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 from main.cpp we pass the object of our descendant Screen

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, resetting the old one.
GetScreen, in my opinion, is almost useless - except for reloading a level like this
SetScreen(GetScreen());

But it's stupid to reload all resources. In general, decide for yourself :)

2.2. Compile! Compile!


Let's play a bit?
Open the main.cpp file and change it to this 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 print a standard Windows message.

An attentive user will pay attention to the fact that the window does not close by clicking on the red X, and the title of the window would also help to remove it. No problem - take the job:
#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 don’t want to work with the 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 more knowledgeable programmer in terms of performance will immediately realize that it is pointless to draw pictures every cycle if they do not change their location. Really - lines
		graphics->DrawImage(test,0,0);
		graphics->Flip();

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

2.3. Summary


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

For all questions, contact the PM, and if you are unlucky to be registered on the hub, write to the email izarizar@mail.ru


Also popular now: