Съдържание
- Описание на играта 15.
- Начален екран – планиране.
- Основни класове.
Описание на играта 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
- #ifndef BACKGROUND_H
- #define BACKGROUND_H
- #include „StaticSprite.h“
- #include „SpriteManager.h“
- class Background : public StaticSprite
- {
- public:
- Background();
- virtual ~Background();
- void render(SDL_Surface* screen);
- protected:
- private:
- };
- #endif // BACKGROUND_H
В директорията src имаме файла:
Background.cpp
- #include „../include/Background.h“
- Background::Background() : StaticSprite()
- {
- type = Constants::SpriteTypes::BACKGROUND;
- name = „background“;
- setX(0);
- setY(0);
- }
- Background::~Background()
- {
- //dtor
- }
- void Background::render(SDL_Surface* screen) {
- StaticSprite::render(screen);
- }
В конструктора инициализираме някои променливи, които могат да са полезни за работата на SpriteManager. Например може да искаме да премахнем спрайт с дадено име или множество спрайтове от даден тип.
В render показваме заредената картинка. Всичко, необходимо за това, се наследява от StaticSprite.
Да добавим нашия клас Background към играта. Както споменахме в C++ Game engine на SDL2, основният клас на играта ще е gsrc/FifteenGame.cpp и gsrc/FifteenGame.h.
За удобство ще ги покажем и тук, но вече с добавен background:
FifteenGame.h
- #ifndef FIFTEENGAME_H
- #define FIFTEENGAME_H
- #include „../include/Game.h“
- class FifteenGame : public Game {
- public:
- void init();
- void update(double deltaTime);
- void render();
- void freeResources();
- FifteenGame();
- ~FifteenGame();
- private:
- Background* background;
- };
- #endif // FIFTEENGAME_H
- FifteenGame.cpp
- #include <dirent.h>
- #include <stdio.h>
- #include „FifteenGame.h“
- FifteenGame::FifteenGame() {
- }
- void FifteenGame::init() {
- this->setTitle(„Игра 15“);
- // създаваме си обекта background
- background = new Background();
- // зареждаме картинката
- background->setImage(Constants::RESOURCE_DIR + Constants::pathSeparator + „background.png“);
- // прибавяме към SpriteManager – Game наследява SpriteManager
- addSprite(background);
- }
- void FifteenGame::update(double deltaTime) {
- }
- void FifteenGame::render() {
- // показваме
- background->render(screen);
- }
- void FifteenGame::freeResources() {
- }
- FifteenGame::~FifteenGame() {
- // премахваме обекта
- delete background;
- background = 0;
- }
Трябва да получим това:
Ако сте прегледали първата статия, сте забелязали, че сега методът init() е по-прост. Липсват
pixelFormat = getPixelFormat();
ImageInstruments::setPixelFormat(pixelFormat);
които сега са преместени на нивото на Game engine.
Клас Cursor.
Няма да имаме и високи изисквания за курсора на мишката.
Ще искаме естествено да можем да заместваме стандартния курсор с потребителски във вид на картинка.
Ще искаме да можем да сменяме картинката на курсора.
Освен това ще искаме класът, в който ще реализираме курсора, да е Singleton.
Ето ги и класовете:
include/Cursor.cpp
- #ifndef CURSOR_H
- #define CURSOR_H
- #include „AnimatedSprite.h“
- class Cursor : public AnimatedSprite
- {
- public:
- static Cursor* getCursor();
- // първоначално сетване на пътя до курсора
- static void setPath(string& pathCursor);
- virtual ~Cursor();
- void render(SDL_Surface* screen);
- // смяна на курсора
- void changeCursor(string& pathCursor);
- private:
- Cursor();
- static Cursor* cursor;
- // път до картинката с курсора
- static string pathCursor;
- };
- #endif // CURSOR_H
src/Cursor.cpp
- #include „../include/Cursor.h“
- /**
- * Смяна на курсора
- * @brief Cursor::changeCursor
- * @param pathCursor
- */
- void Cursor::changeCursor(string &pathCursor) {
- // освобождаваме старата картинка
- SDL_FreeSurface(this->getImage());
- this->pathCursor = pathCursor;
- // зареждаме новата
- setImage(pathCursor);
- }
- void Cursor::render(SDL_Surface* screen) {
- // показване
- StaticSprite::render(screen);
- }
- Cursor* Cursor::getCursor() {
- if (cursor == 0) {
- return cursor = new Cursor();
- }
- return cursor;
- }
- /**
- * Вземаме инстанция на курсора
- * @brief Cursor::Cursor
- */
- Cursor::Cursor(): AnimatedSprite() {
- // скриваме стандартният курсор
- SDL_ShowCursor(0);
- type = Constants::SpriteTypes::MOUSE_CURSOR;
- name = „MouseCursor“;
- // позиционираме го в горният ляв ъгъл на играта ни
- setX(0);
- setY(0);
- if (Cursor::pathCursor != „“) {
- // зареждаме
- setImage(Cursor::pathCursor);
- }
- }
- void Cursor::setPath(string &pathCursor) {
- Cursor::pathCursor = pathCursor;
- }
- Cursor::~Cursor() {
- delete Cursor::cursor;
- }
- Cursor* Cursor::cursor = 0;
- string Cursor::pathCursor = „“;
Класът ни за курсор е много примитивен. Могат да се добавят проверки, зареждане на курсор по подразбиране, ако нашият липсва, скриване на курсора при излизането му извън играта.
Но за сега и това състояние ни върши работа.
В FifteenGame.h добавяме декларация за курсор:
- private:
- Background* background;
- Cursor* cursor;
като включваме FConstants.h
#include „FConstants.h“
FConstants.h, който ще съдържа константи, свързани само с играта ни и ще се намира в директория gsrc.
- #ifndef FCONSTANTS_H
- #define FCONSTANTS_H
- namespace FConstants {
За сега в него ще имаме само пътя до картинката със стандартния ни курсор.
В метода FifteenGame::init() добавяме:
- // зареждаме курсора
- string cursor_path = FConstants::STANDART_CURSOR;
- Cursor::setPath(cursor_path);
- cursor = Cursor::getCursor();
- addSprite(cursor);
В FifteenGame::render() накрая (важно е курсорът да се рендва последен) добавяме:
- cursor->render();
и в FifteenGame::~FifteenGame() добавяме:
- delete cursor;
Компилираме и стартираме – вече имаме курсор.
Може би сте забелязали използването на класа AnimatedSprite. Какво прави той? Той ни дава достъп и известен контрол върху анимирани спрайтове. Един такъв представлява resources/magicStars.png (ако сте свалили кода, спрайтът е там). Отворете го и ще видите фреймовете (кадрите), които представляват спрайта.
Да сложим един.
В FifteenGame.h добавяме:
AnimatedSprite* sparks;
В FifteenGame.cpp в FifteenGame::init() накрая добавяме:
- // анимиран спрайт
- sparks = new AnimatedSprite();
- // показва геометрията на спрайта – 12 фрейма (кадъра) в в 1 ред
- sparks->setTilesXY(12, 1);
- // зареждаме картинката
- sparks->setImage(Constants::RESOURCE_DIR + Constants::pathSeparator + „magicStars.png“);
- // определяме име
- sparks->setName(„sparks“);
- // кои фреймове ще показваме
- sparks->setFrameBeginEnd(0, 12);
- // с каква скорост ще се показват фреймовете
- sparks->calculate(10);
- // разполагаме го в горният десен ъгъл на началният екран
- sparks->setX(getWidth() – sparks->getSpriteWidth() – 20);
- sparks->setY(20);
- // добавяме към SpriteManage
- addSprite(sparks);
В FifteenGame::update добавяме:
- if (sparks) {
- sparks->update(deltaTime);
- }
В FifteenGame::render() след background->render(screen); добавяме
- sparks->render(screen);
В FifteenGame::~FifteenGame:
- delete sparks;
- sparks = 0;
Компилираме и стартираме – в горния десен ъгъл вече имаме анимация.
Добавяне на клас за бутон.
Бутонът е друг важен елемент на потребителския интерфейс. Но и към него няма да имаме високи изисквания:
1. Искаме да е rollover т.е когато курсорът на мишката е върху него да се показва друга картинка.
2. Искаме да реагира на click.
3. Останалите неща ги имаме вече в Game engine.
В include/Button.h се намира header на класът Button. Той е доста прост:
- #ifndef BUTTON_H
- #define BUTTON_H
- #include „StaticSprite.h“
- #include „SpriteManager.h“
- class Button : public StaticSprite
- {
- public:
- Button();
- virtual ~Button();
- void render(SDL_Surface* screen);
- };
- #endif // BUTTON_H
Ето и gsrc/Button.cpp:
- #include „../include/Button.h“
- Button::Button(): StaticSprite()
- {
- name = „BUTTON“;
- type = Constants::SpriteTypes::UI_BUTTON;
- setX(0);
- setY(0);
- }
- Button::~Button() {
- }
- //смяна на картинката, когато курсора е върху бутона (или го напуска)
- void Button::render(SDL_Surface* screen) {
- // ако курсорът е върху бутона
- if ( this->isCursorInSprite() ) {
- // казваме на Game engine от къде е последното събитие
- SpriteManager::mouseEventFrom = this->getName();
- // показваме rollover частта
- StaticSprite::render(screen, image,this->getX(),this->getY(),0,image->h/2,image->w, image->h/2);
- } // Ако сме върху курсора и сме натиснали бутон на мишката
- else if (this->isCursorInSprite() && this->isMouseButtonDown()) {
- // казваме на Game engine от къде е последното събитие
- SpriteManager::mouseEventFrom = this->getName();
- } else {
- // показваме другата част от rollover картинката
- StaticSprite::render(screen, image,this->getX(),this->getY(),0,0,image->w, image->h/2);
- }
- }
Ще ни трябват 3 бутона. За изход, за започване на играта, и бутон, който ще отваря прозорче, показващо информация за играта.
Да ги декларираме в FifteenGame.h
- Button* btnExitGame;
- Button* btnGoGame;
- Button* btnAboutGame;
В FifteenGame.cpp в FifteenGame::init() добавяме:
- // бутон за излизане
- btnExitGame = new Button();
- btnExitGame->setTilesXY(1,2);
- btnExitGame->setImage(Constants::RESOURCE_DIR + Constants::pathSeparator + „exitGame.png“);
- btnExitGame->setName(„btnExitGame“);
- btnExitGame->setX((getWidth() – btnExitGame->getSpriteWidth())/2);
- int exitBtnBottom = getHeight() – btnExitGame->getSpriteHeight() – 25;
- btnExitGame->setY(exitBtnBottom);
- addSprite(btnExitGame);
- btnGoGame = new Button();
- btnGoGame->setTilesXY(1,2);
- btnGoGame->setImage(Constants::RESOURCE_DIR + Constants::pathSeparator + „goGame.png“);
- btnGoGame->setName(„btnGoGame“);
- btnGoGame->setX((getWidth() – btnGoGame->getSpriteWidth())/2);
- btnGoGame->setY(exitBtnBottom – 2*btnGoGame->getSpriteHeight() – 30);
- addSprite(btnGoGame);
- btnAboutGame = new Button();
- btnAboutGame->setTilesXY(1,2);
- btnAboutGame->setImage(Constants::RESOURCE_DIR + Constants::pathSeparator + „aboutGame.png“);
- btnAboutGame->setName(„btnAboutGame“);
- btnAboutGame->setX((getWidth() – btnAboutGame->getSpriteWidth())/2);
- btnAboutGame->setY(exitBtnBottom – btnAboutGame->getSpriteHeight() – 15);
- addSprite(btnAboutGame);
В FifteenGame::render() след sparks->render(screen);
добавяме
- btnExitGame->render(screen);
- btnGoGame->render(screen);
- btnAboutGame->render(screen);
В FifteenGame::~FifteenGame() добавяме
- delete btnExitGame;
- btnExitGame = 0;
- delete btnGoGame;
- btnGoGame = 0;
- delete btnAboutGame;
- btnAboutGame = 0;
компилираме и стартираме – и получаваме:
В следващата част ще добавим текст за заглавието, обработчици на бутоните, смяна на курсора и екрана за самата игра.
Кода може да свалите оттук: тук
Приятно кодиране 🙂
Автор: Янко Попов