...

воскресенье, 13 октября 2013 г.

Пишем игры на 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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий