+
Вход

Въведи своя e-mail и парола за вход, ако вече имаш създаден профил в DEV.BG/Jobs

Забравена парола?
+
Създай своя профил в DEV.BG/Jobs

За да потвърдите, че не сте робот, моля отговорете на въпроса, като попълните празното поле:

71+18 =
+
Забравена парола

Въведи своя e-mail и ще ти изпратим твоята парола

C++ [SDL] Играта 15 – Начален екран

C++

Съдържание

  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 {
Днес те питаме…

Стартирахме нов канал @DEV.BG в TikTok. Какво е мнението ти?
Loading ... Loading …
  •          static string STANDART_CURSOR = Constants::RESOURCE_DIR + Constants::pathSeparator + „cursor-icon.png“;
  • }
  • #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;

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

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

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

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

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

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