Съдържание

  1. Описание на играта 15.
  2. Начален екран – планиране.
  3. Основни класове.

 

Описание на играта 15.

След като сложихме началото на нашата игра в C++ Game engine на SDL2, чрез файловете FifteenGame.h и FifteenGame.cpp, е време да започнем с писането на самата игра. А тя ще бъде „Игра 15“. Представа за нея може да получите от тук и тук. Поради разнообразните описания на играта в Интернет, няма да се спирам обстойно на описанието й. Само ще спомена, че тя е логическа игра от тип пъзел и представлява числата от 1 до 15, разположени в разбъркан ред в квадрат 4х4. Може да преместваме число (квадратче) в съседно, само ако там няма друго число и само в посоките наляво, надясно, горе и долу (естествено без да излизаме от големия квадрат 4х4). Целта е да се подредят числата в определен ред – например в естествен нарастващ ред от горния ляв ъгъл, ред по ред. Тази игра може лесно да се напише и без да се използват библиотеки от типа на SDL, но целта ни е да демонстрираме основни техники в 2d игрите, а читателите да продължат самостоятелно нататък.

Кодът може да свалите от тук

Начален екран – планиране.

Това, което ще искаме да постигнем, е ето това:
Уроци по програмиране

Като споменахме основни 2d техники, само ще спомена, че описаният Game engine в C++ Game engine на SDL2 е действително много базов. Той няма система за обработка на събитията, няма класове за потребителски интерфейс и още много неща. Ще започнем с най-простия клас – Background.
Някои от необходимите неща за класовете вече са реализирани в Game engine, така че няма да е трудно тяхното добавяне. Играта няма да се стартира във fullscreen и няма да може да й се променя размера. Той ще е фиксиран. За целите на урока ще изберем да е 500×500 пиксела.

Основни класове.

Клас Background.
Този клас просто показва картинка като фон на играта.

В директорията include имаме файла:
Background.h

  1. #ifndef BACKGROUND_H
  2. #define BACKGROUND_H
  3. #include „StaticSprite.h“
  4. #include „SpriteManager.h“
  5. class Background : public StaticSprite
  6. {
  7.       public:
  8.      Background();
  9.      virtual ~Background();
  10.      void render(SDL_Surface* screen);
  11.      protected:
  12.      private:
  13. };
  14. #endif // BACKGROUND_H

В директорията src имаме файла:
Background.cpp

  1. #include „../include/Background.h“
  2. Background::Background() : StaticSprite()
  3. {
  4.         type = Constants::SpriteTypes::BACKGROUND;
  5.         name = „background“;
  6.         setX(0);
  7.         setY(0);
  8. }
  9. Background::~Background()
  10. {
  11.         //dtor
  12. }
  13. void Background::render(SDL_Surface* screen) {
  14.       StaticSprite::render(screen);
  15. }

В конструктора инициализираме някои променливи, които могат да са полезни за работата на SpriteManager. Например може да искаме да премахнем спрайт с дадено име или множество спрайтове от даден тип.

В render показваме заредената картинка. Всичко, необходимо за това, се наследява от StaticSprite.

Да добавим нашия клас Background към играта. Както споменахме в C++ Game engine на SDL2, основният клас на играта ще е gsrc/FifteenGame.cpp и gsrc/FifteenGame.h.
За удобство ще ги покажем и тук, но вече с добавен background:

FifteenGame.h

  1. #ifndef FIFTEENGAME_H
  2. #define FIFTEENGAME_H
  3. #include „../include/Game.h“
  4. class FifteenGame : public Game {
  5. public:
  6.        void init();
  7.        void update(double deltaTime);
  8.       void render();
  9.       void freeResources();
  10.       FifteenGame();
  11.      ~FifteenGame();
  12.      private:
  13.      Background* background;
  14. };
  15. #endif // FIFTEENGAME_H
  16. FifteenGame.cpp
  17. #include <dirent.h>
  18. #include <stdio.h>
  19. #include „FifteenGame.h“
  20. FifteenGame::FifteenGame() {
  21. }
  22. void FifteenGame::init() {
  23.       this->setTitle(„Игра 15“);
  24.       // създаваме си обекта background
  25.       background = new Background();
  26.       // зареждаме картинката
  27.       background->setImage(Constants::RESOURCE_DIR + Constants::pathSeparator + „background.png“);
  28.       // прибавяме към SpriteManager – Game наследява SpriteManager
  29.      addSprite(background);
  30. }
  31. void FifteenGame::update(double deltaTime) {
  32. }
  33. void FifteenGame::render() {
  34.      // показваме
  35.      background->render(screen);
  36. }
  37. void FifteenGame::freeResources() {
  38. }
  39. FifteenGame::~FifteenGame() {
  40.      // премахваме обекта
  41.     delete background;
  42.     background = 0;
  43. }

Трябва да получим това:
Уроци по програмиране

Ако сте прегледали първата статия, сте забелязали, че сега методът init() е по-прост. Липсват
pixelFormat = getPixelFormat();
ImageInstruments::setPixelFormat(pixelFormat);
които сега са преместени на нивото на Game engine.

Клас Cursor.

Няма да имаме и високи изисквания за курсора на мишката.
Ще искаме естествено да можем да заместваме стандартния курсор с потребителски във вид на картинка.
Ще искаме да можем да сменяме картинката на курсора.
Освен това ще искаме класът, в който ще реализираме курсора, да е Singleton.
Ето ги и класовете:
include/Cursor.cpp

  1. #ifndef CURSOR_H
  2. #define CURSOR_H
  3. #include „AnimatedSprite.h“
  4. class Cursor : public AnimatedSprite
  5. {
  6.     public:
  7.            static Cursor* getCursor();
  8.           // първоначално сетване на пътя до курсора
  9.           static void setPath(string& pathCursor);
  10.           virtual ~Cursor();
  11.           void render(SDL_Surface* screen);
  12.           // смяна на курсора
  13.           void changeCursor(string& pathCursor);
  14.    private:
  15.          Cursor();
  16.          static Cursor* cursor;
  17.          // път до картинката с курсора
  18.          static string pathCursor;
  19. };
  20. #endif // CURSOR_H

src/Cursor.cpp

  1. #include „../include/Cursor.h“
  2. /**
  3.  * Смяна на курсора
  4.  * @brief Cursor::changeCursor
  5.  * @param pathCursor
  6.  */
  7. void Cursor::changeCursor(string &pathCursor) {
  8.       // освобождаваме старата картинка
  9.      SDL_FreeSurface(this->getImage());
  10.      this->pathCursor = pathCursor;
  11.      // зареждаме новата
  12.      setImage(pathCursor);
  13. }
  14. void Cursor::render(SDL_Surface* screen) {
  15.       // показване
  16.       StaticSprite::render(screen);
  17. }
  18. Cursor* Cursor::getCursor() {
  19.         if (cursor == 0) {
  20.         return cursor = new Cursor();
  21.         }
  22.         return cursor;
  23. }
  24. /**
  25.  * Вземаме инстанция на курсора
  26.  * @brief Cursor::Cursor
  27.  */
  28. Cursor::Cursor(): AnimatedSprite() {
  29.        // скриваме стандартният курсор
  30.        SDL_ShowCursor(0);
  31.        type = Constants::SpriteTypes::MOUSE_CURSOR;
  32.        name = „MouseCursor“;
  33.        // позиционираме го в горният ляв ъгъл на играта ни
  34.        setX(0);
  35.        setY(0);
  36.        if (Cursor::pathCursor != „“) {
  37.            // зареждаме
  38.           setImage(Cursor::pathCursor);
  39.        }
  40. }
  41. void Cursor::setPath(string &pathCursor) {
  42.     Cursor::pathCursor = pathCursor;
  43. }
  44. Cursor::~Cursor() {
  45.        delete Cursor::cursor;
  46. }
  47. Cursor* Cursor::cursor = 0;
  48. string Cursor::pathCursor = „“;

Класът ни за курсор е много примитивен. Могат да се добавят проверки, зареждане на курсор по подразбиране, ако нашият липсва, скриване на курсора при излизането му извън играта.
Но за сега и това състояние ни върши работа.

В FifteenGame.h добавяме декларация за курсор:

  1. private:
  2. Background* background;
  3. Cursor* cursor;

като включваме FConstants.h

#include „FConstants.h“

FConstants.h, който ще съдържа константи, свързани само с играта ни и ще се намира в директория gsrc.

  1. #ifndef FCONSTANTS_H
  2. #define FCONSTANTS_H
  3. namespace FConstants {
  4.          static string STANDART_CURSOR = Constants::RESOURCE_DIR + Constants::pathSeparator + „cursor-icon.png“;
  5. }
  6. #endif // FCONSTANTS_H

За сега в него ще имаме само пътя до картинката със стандартния ни курсор.

В метода FifteenGame::init() добавяме:

  1.     // зареждаме курсора
  2.     string cursor_path = FConstants::STANDART_CURSOR;
  3.     Cursor::setPath(cursor_path);
  4.     cursor = Cursor::getCursor();
  5.     addSprite(cursor);

В FifteenGame::render() накрая (важно е курсорът да се рендва последен) добавяме:

  1. cursor->render();

и в FifteenGame::~FifteenGame() добавяме:

  1. delete cursor;

Компилираме и стартираме – вече имаме курсор.

Може би сте забелязали използването на класа AnimatedSprite. Какво прави той? Той ни дава достъп и известен контрол върху анимирани спрайтове. Един такъв представлява resources/magicStars.png (ако сте свалили кода, спрайтът е там). Отворете го и ще видите фреймовете (кадрите), които представляват спрайта.

Да сложим един.
В FifteenGame.h добавяме:

AnimatedSprite* sparks;
В FifteenGame.cpp в FifteenGame::init() накрая добавяме:

  1.        // анимиран спрайт
  2.       sparks = new AnimatedSprite();
  3.       // показва геометрията на спрайта – 12 фрейма (кадъра) в в 1 ред
  4.       sparks->setTilesXY(12, 1);
  5.       // зареждаме картинката
  6.       sparks->setImage(Constants::RESOURCE_DIR + Constants::pathSeparator + „magicStars.png“);
  7.       // определяме име
  8.       sparks->setName(„sparks“);
  9.      // кои фреймове ще показваме
  10.      sparks->setFrameBeginEnd(0, 12);
  11.      // с каква скорост ще се показват фреймовете
  12.      sparks->calculate(10);
  13.      // разполагаме го в горният десен ъгъл на началният екран
  14.     sparks->setX(getWidth() sparks->getSpriteWidth() 20);
  15.     sparks->setY(20);
  16.     // добавяме към SpriteManage
  17.     addSprite(sparks);

В FifteenGame::update добавяме:

  1. if (sparks {
  2.            sparks->update(deltaTime);
  3.     }

В FifteenGame::render() след background->render(screen); добавяме

  1.     sparks->render(screen);

В FifteenGame::~FifteenGame:

  1.     delete sparks;
  2.     sparks = 0;

Компилираме и стартираме – в горния десен ъгъл вече имаме анимация.

Добавяне на клас за бутон.

Бутонът е друг важен елемент на потребителския интерфейс. Но и към него няма да имаме високи изисквания:
1. Искаме да е rollover т.е когато курсорът на мишката е върху него да се показва друга картинка.
2. Искаме да реагира на click.
3. Останалите неща ги имаме вече в Game engine.

В include/Button.h се намира header на класът Button. Той е доста прост:

  1. #ifndef BUTTON_H
  2. #define BUTTON_H
  3. #include „StaticSprite.h“
  4. #include „SpriteManager.h“
  5. class Button : public StaticSprite
  6. {
  7.      public:
  8.            Button();
  9.            virtual ~Button();
  10.            void render(SDL_Surface* screen);
  11. };
  12. #endif // BUTTON_H

Ето и gsrc/Button.cpp:

  1. #include „../include/Button.h“
  2. Button::Button(): StaticSprite()
  3. {
  4.       name = „BUTTON“;
  5.       type = Constants::SpriteTypes::UI_BUTTON;
  6.       setX(0);
  7.       setY(0);
  8. }
  9. Button::~Button() {
  10. }
  11. //смяна на картинката, когато курсора е върху бутона (или го напуска)
  12. void Button::render(SDL_Surface* screen) {
  13.        // ако курсорът е върху бутона
  14.        if ( this->isCursorInSprite() ) {
  15.            // казваме на Game engine от къде е последното събитие
  16.           SpriteManager::mouseEventFrom = this->getName();
  17.           // показваме rollover частта
  18.           StaticSprite::render(screen, image,this->getX(),this->getY(),0,image->h/2,image->w, image->h/2);
  19.       } // Ако сме върху курсора и сме натиснали бутон на мишката
  20.       else if (this->isCursorInSprite() && this->isMouseButtonDown()) {
  21.              // казваме на Game engine от къде е последното събитие
  22.             SpriteManager::mouseEventFrom = this->getName();
  23.       } else {
  24.            // показваме другата част от rollover картинката
  25.            StaticSprite::render(screen, image,this->getX(),this->getY(),0,0,image->w, image->h/2);
  26.      }
  27. }

Ще ни трябват 3 бутона. За изход, за започване на играта, и бутон, който ще отваря прозорче, показващо информация за играта.

Да ги декларираме в FifteenGame.h

  1.     Button* btnExitGame;
  2.     Button* btnGoGame;
  3.     Button* btnAboutGame;

В FifteenGame.cpp в FifteenGame::init() добавяме:

  1.     // бутон за излизане
  2.     btnExitGame = new Button();
  3.     btnExitGame->setTilesXY(1,2);
  4.     btnExitGame->setImage(Constants::RESOURCE_DIR + Constants::pathSeparator + „exitGame.png“);
  5.     btnExitGame->setName(„btnExitGame“);
  6.     btnExitGame->setX((getWidth() btnExitGame->getSpriteWidth())/2);
  7.     int exitBtnBottom = getHeight() btnExitGame->getSpriteHeight() 25;
  8.     btnExitGame->setY(exitBtnBottom);
  9.     addSprite(btnExitGame);
  10.     btnGoGame = new Button();
  11.     btnGoGame->setTilesXY(1,2);
  12.     btnGoGame->setImage(Constants::RESOURCE_DIR + Constants::pathSeparator + „goGame.png“);
  13.     btnGoGame->setName(„btnGoGame“);
  14.     btnGoGame->setX((getWidth() btnGoGame->getSpriteWidth())/2);
  15.     btnGoGame->setY(exitBtnBottom 2*btnGoGame->getSpriteHeight() 30);
  16.     addSprite(btnGoGame);
  17.     btnAboutGame = new Button();
  18.     btnAboutGame->setTilesXY(1,2);
  19.     btnAboutGame->setImage(Constants::RESOURCE_DIR + Constants::pathSeparator + „aboutGame.png“);
  20.     btnAboutGame->setName(„btnAboutGame“);
  21.     btnAboutGame->setX((getWidth() btnAboutGame->getSpriteWidth())/2);
  22.     btnAboutGame->setY(exitBtnBottom btnAboutGame->getSpriteHeight() 15);
  23.     addSprite(btnAboutGame);

В FifteenGame::render() след sparks->render(screen);
добавяме

  1.             btnExitGame->render(screen);
  2.     btnGoGame->render(screen);
  3.     btnAboutGame->render(screen);

В FifteenGame::~FifteenGame() добавяме

  1.     delete btnExitGame;
  2.     btnExitGame = 0;
  3.     delete btnGoGame;
  4.     btnGoGame = 0;
  5.     delete btnAboutGame;
  6.     btnAboutGame = 0;

компилираме и стартираме – и получаваме:

Уроци по програмиране

В следващата част ще добавим текст за заглавието, обработчици на бутоните, смяна на курсора и екрана за самата игра.

Кода може да свалите оттук: тук

Приятно кодиране 🙂

Автор: Янко Попов