Пишем игры на C++, Часть 2/3 — State-based программирование

от автора

Пишем игры на C++, Часть 1/3 — Написание мини-фреймворка
Пишем игры на C++, Часть 3/3 — Классика жанра

Здравствуй, Хабрахабр!

Поздравляю вас, если вы прочитали первый урок! Он достаточно большой. Обещаю, что тут кода будеть меньше, а результатов больше 🙂

О чем эта часть?

  • Мы попытаемся постичь state-based programming, с помощью которого новые уровни и меню делаются очень легко

В следующем посте будут натуральные игры 🙂

2.1. Состояния

Теперь неплохо бы понять, из чего, собственно, состоит игра.

Допустим, у нас есть игра, где много менюшек, уровней и прочих «состояний». Как можно с ними взаимодействовать? Понятно, что код типа:

    void Update() 	{         switch(state) 		{ 		case State::MENU:             // 100 строк 		case State::SETTINGS:             // 200 строк 		case State::LEVEL1:             // Страшно считать         }     } 

Вызывает лютый незачет в плане удобства.

Как насчет того, чтобы каждому состоянию сделать своего наследника от какого-нибудь класса с названием, допустим, Screen, и использовать его в Game?

Создайте Screen.h

#ifndef SCREEN_H #define SCREEN_H  #include "Incl.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 

Этот класс имеет экземпляр Game, откуда наследники берут указатели на Graphics и Input
Его виртуальные функции для наследников:

  • Start — вызов каждый раз при старте (назначение состоянием)
  • Update — вызов каждый цикл
  • Destroy — вызов по уничтожению (завершение работы программы либо назначение другого состояния)

Screen.cpp

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

Обновляем Game.h и 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 

В класс Game включается объект Screen и изменяется функция Execute, куда из main.cpp передаем объект своего наследника 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; } 

Важным изменениям подвергается метод Execute — он обрабатывает текущее состояние
SetScreen устанавливает новое состояние, сбрасывая старое.
GetScreen, на мой взгляд, почти бесполезен — разве что для перезагрузки уровня таким макаром

SetScreen(GetScreen()); 

Но глупость заново загружать все ресурсы. В общем, решайте сами 🙂

2.2. Компилировать! Компилировать!

Поиграемся?
Откройте файл main.cpp и измените его до такого состояния:

#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); } 

Все, что он делает — выводит стандартное Windows-сообщение.

Внимательный пользователь обратит внимание на то, что окно не закрывается по клику на красный крестик, и заглавие окна тоже не помешало бы убрать. Нет проблем — принимайте работу:

#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); } 

Мы не хотим работать с черным окном, давайте что-нибудь нарисуем?

#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); } 

Более сведущий в плане производительности программист сразу поймет, что бессмысленно каждый цикл рисовать картинки, если они не меняют свое местоположение. Действительно — строчки

		graphics->DrawImage(test,0,0); 		graphics->Flip(); 

Надо переместить из Update() в конец Start()

2.3. Итоги

Я надеюсь, вы были впечатлены и узнали много нового 🙂
Тогда переходите к третьему уроку без сомнений

По всем вопросам обращайтесь в ЛС, а если вам не повезло быть зарегистрированным на хабре, пишите на мейл izarizar@mail.ru

ссылка на оригинал статьи http://habrahabr.ru/post/197280/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *