Пишем игры на 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/
Добавить комментарий