На этом sfml уроке введем понятие миссия в нашу игру, а так же попробуем с её помощью как-то сэкономить затрачиваемые ресурсы ПК. Будем выводить различный текст на экран, в зависимости от миссии. Вывод текста будет происходить на фоне свитка (изображение), а так же при нажатии клавиши TAB
Видеоверсия:
В main.cpp подключим:
1 2 |
#include "mission.h" #include <iostream> |
Уберу жирность и подчеркивание текста, это была лишь демонстрация. В main.cpp удалю:
1 2 |
text.setColor(Color::Red);//удаляем эти text.setStyle(sf::Text::Bold | sf::Text::Underlined);//две строки |
Но поставлю черный цвет текста:
1 |
text.setColor(Color::Black); |
Итак, создадим файл mission.h. В Нем будет содержаться различный текст, а так же храниться номер текущей миссии.
код в файле:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
///////////////////////////////////НОМЕР МИССИИ////////////////////////////////// int getCurrentMission(int x)//ф-ция номера миссия, которая меняет номер миссии, в зависимости от координаты игрока Х (сюда будем передавать эту координату) { int mission = 0; if ((x>0) && (x<600)) { mission = 0; } //знакомим игрока с игрой if (x>400) { mission = 1; } //игрок на первой миссии if (x>700) { mission = 2; }//2ой if (x>2200) { mission = 3; }//и тд return mission;//ф-ция возвращает номер миссии } /////////////////////////////////////ТЕКСТ МИССИИ///////////////////////////////// std::string getTextMission(int currentMission) { std::string missionText = "";//текст миссии и его инициализация switch (currentMission)//принимается номер миссии и в зависимости от него переменной missionText присваивается различный текст { case 0: missionText = "\nНачальный этап и \nинструкции к игре"; break; case 1: missionText = "\nMission 1\n\nВот твоя первая\n миссия, на\n этом уровне \nтебе стоит опасаться\n ... бла-бла-бла ..."; break; case 2: missionText = "\nMission 2\n Необходимо решить\n логическую задачку,\n чтобы пройти дальше "; break; case 3: missionText = "\nИ так далее \nи тому подобное....."; break; } return missionText;//ф-ция возвращает текст }; |
По комментариям думаю понятно.
Перенос строк нужен для того, чтобы уместить по вертикали наш текст в рамку свитка. (о свитке ниже)
Теперь в main.cpp объявим переменную:
1 |
bool showMissionText = true;//логическая переменная, отвечающая за появление текста миссии на экране |
и добавим картинку свитка:
1 2 3 4 5 6 7 8 9 |
Image quest_image; quest_image.loadFromFile("images/missionbg.jpg"); quest_image.createMaskFromColor(Color(0, 0, 0)); Texture quest_texture; quest_texture.loadFromImage(quest_image); Sprite s_quest; s_quest.setTexture(quest_texture); s_quest.setTextureRect(IntRect(0, 0, 340, 510)); //приведение типов, размеры картинки исходные s_quest.setScale(0.6f, 0.6f);//чуть уменьшили картинку, => размер стал меньше |
Скачайте её, кстати:
Теперь, насколько помните из урока 2 у нас есть основной цикл, пока открыто окно и в нем событие “если окно закрыто”:
1 2 3 4 |
while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); |
Так вот, у нас есть возможность обрабатывать не только событие закрытия окна, но и , например, нажатия клавиши. Выглядит так: “if (event.type == Event::KeyPressed)
Вкратце говоря, весь код этого события теперь будет выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); if (event.type == Event::KeyPressed)//событие нажатия клавиши if ((event.key.code == Keyboard::Tab)) {//если клавиша ТАБ switch (showMissionText) {//переключатель, реагирующий на логическую переменную showMissionText case true: { std::ostringstream playerHealthString;//строка здоровья игрока playerHealthString << p.health; //заносим в строку здоровье std::ostringstream task;//строка текста миссии task << getTextMission(getCurrentMission(p.getplayercoordinateX()));//вызывается функция getTextMission (она возвращает текст миссии), которая принимает в качестве аргумента функцию getCurrentMission(возвращающую номер миссии), а уже эта ф-ция принимает в качестве аргумента функцию p.getplayercoordinateX() (эта ф-ция возвращает Икс координату игрока) text.setString("Здоровье: " + playerHealthString.str()+"\n" + task.str());//задаем text.setPosition(view.getCenter().x + 125, view.getCenter().y - 130);//позиция всего этого текстового блока s_quest.setPosition(view.getCenter().x + 115, view.getCenter().y - 130);//позиция фона для блока showMissionText = false;//эта строка позволяет убрать все что мы вывели на экране break;//выходим , чтобы не выполнить условие "false" (которое ниже) } case false: { text.setString("");//если не нажата клавиша таб, то весь этот текст пустой showMissionText = true;// а эта строка позволяет снова нажать клавишу таб и получить вывод на экран break; } } } } |
Вставьте и посмотрите. В студии он не будет таким корявым по форматированию, как тут.
Сюда же я перенес снизу переменную здоровья и её отображение, так же на нажатие клавиши TAB.
Получается, что если нажата клавиша ТАБ, то запускается switch, который имеет два состояния логической переменной (если нажата клавиша, то выполняется одно, иначе – другое.). Каждое нажатие меняет значение логической переменной showMissionText с истины на ложь или со лжи на истину.
Так же там есть такая строка:
1 |
getTextMission(getCurrentMission(p.getplayercoordinateX())); |
Тут вызывается функция getTextMission (она возвращает текст миссии), которая принимает в качестве аргумента функцию getCurrentMission(возвращающую номер миссии), а уже эта ф-ция принимает в качестве аргумента функцию p.getplayercoordinateX() (эта ф-ция возвращает Икс координату игрока).
В конце , (где мы все рисуем с помощью window.draw()) до рисования текста (чтобы текст был поверх спрайта) напишем:
1 |
if (!showMissionText) { window.draw(s_quest); window.draw(text); }//рисуем спрайт свитка (фон для текста миссии). а затем и текст. все это завязано на логическую переменную, которая меняет свое состояние от нажатия клавиши ТАБ |
А рисование текста, как мы видим, немного переместилось и попало в условие.
Если запустить, то получится такой результат:
Как видите лев смог придти прямо на текст миссии. А всё потому, что мы устанавливаем позицию всего этого блока лишь при нажатии клавиши ТАБ. то есть обновление (update) происходит только в этот момент. Чтобы он не мог наступить на наш текст и свиток, необходимо будет перенести строки задания позиции текста и свитка в функцию “пока открыто окно”, например до рисования этих объектов. Таким образом рисование этих объектов будет постоянным, пока нажата клавиша TAB. Итак, вот эти строки
1 2 |
text.setPosition(view.getCenter().x + 125, view.getCenter().y - 130);//позиция всего этого текстового блока s_quest.setPosition(view.getCenter().x + 115, view.getCenter().y - 130);//позиция фона для блока |
Перенесем туда же (не скопируем), в конец, получим:
1 2 3 4 5 |
if (!showMissionText) { text.setPosition(view.getCenter().x + 125, view.getCenter().y - 130);//позиция всего этого текстового блока s_quest.setPosition(view.getCenter().x + 115, view.getCenter().y - 130);//позиция фона для блока window.draw(s_quest); window.draw(text); //рисуем спрайт свитка (фон для текста миссии). а затем и текст. все это завязано на логическую переменную, которая меняет свое состояние от нажатия клавиши ТАБ } |
Теперь при запуске наш блок инфы сдвигается вместе с камерой.
С помощью увеличения миссии можно изменить игру до неузнаваемости. Новая миссия? Получай врагов с +1000к хп и +200 к дамагу:))
Работа с уровнями будет темой позднего урока, но это нечто похожее.
Понятие миссии весьма полезная вещь, можно таким образом рисовать не всю карту целиком разными тайлами, а лишь то место, где находится игрок. Или же рисовать другую карту, или вариацию этой. Например там, где мы рисуем карту внизу, напишем:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
if ((getCurrentMission(p.getplayercoordinateX())) == 0) { //Если текущая миссия 0, то рисуем карту вот так for (int i = 0; i < HEIGHT_MAP; i++) for (int j = 0; j < WIDTH_MAP; j++) { if (TileMap[i][j] == ' ') s_map.setTextureRect(IntRect(0, 0, 32, 32)); if (TileMap[i][j] == 's') s_map.setTextureRect(IntRect(32, 0, 32, 32)); if ((TileMap[i][j] == '0')) s_map.setTextureRect(IntRect(64, 0, 32, 32)); if ((TileMap[i][j] == 'f')) s_map.setTextureRect(IntRect(96, 0, 32, 32)); if ((TileMap[i][j] == 'h')) s_map.setTextureRect(IntRect(128, 0, 32, 32)); s_map.setPosition(j * 32, i * 32); window.draw(s_map); } } if ((getCurrentMission(p.getplayercoordinateX())) >= 1) { //Если текущая миссия 1, то рисуем карту вот так for (int i = 0; i < HEIGHT_MAP; i++) for (int j = 0; j < WIDTH_MAP; j++) { if (TileMap[i][j] == ' ') s_map.setTextureRect(IntRect(64, 0, 32, 32));//для примера поменял местами вывод спрайта для этого символа и.. if (TileMap[i][j] == 's') s_map.setTextureRect(IntRect(32, 0, 32, 32)); if ((TileMap[i][j] == '0')) s_map.setTextureRect(IntRect(0, 0, 32, 32));//и для вот этого. логически-игровой смысл их остался таким же if ((TileMap[i][j] == 'f')) s_map.setTextureRect(IntRect(96, 0, 32, 32)); if ((TileMap[i][j] == 'h')) s_map.setTextureRect(IntRect(128, 0, 32, 32)); s_map.setPosition(j * 32, i * 32); window.draw(s_map); } } |
Получается, что я здесь для примера поменял спрайтики местами для символов ‘0’ и ‘ ‘. Я бы мог нарисовать там всё по другому просто еще один раз, но тогда ресурсов наша игра жрала бы больше, поскольку загружала бы намного больше спрайтов. В данном случае ресурсам игры пофиг, поскольку я ничего не добавлял, а лишь поменял местами для демонстрации. Но думаю вы поняли для чего это и где можно применить.
Скрин результата:
Так же, чтобы дать игроку понять, что он на новой миссии, где требуется нажать клавишу TAB и посмотреть задание, можно выдавать какое-то звуковое оповещение, или фразу “нажмите TAB” (это уже работа со звуком, урок в будущем). Или же, например, можно завести для этого дела надпись “нажмите таб”, каждый раз, когда значение миссии увеличивается. Еще вариант – выводить тайлик на карте (заранее задать ему символ, например ‘t’) с картинкой tab в соответствующем месте, когда миссия поменялась.
Вот это один из способов изменить геймплей. Пока что работаем вручную со всеми вещами (никаких тебе дополнительных редакторов и библиотек) для лучшего понимания процесса.
Для вопросов есть форум!:-)
код урока
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
#include <SFML/Graphics.hpp> #include "map.h" #include "view.h" #include <iostream> #include <sstream> #include "mission.h" using namespace sf; ////////////////////////////////////////////////////КЛАСС ИГРОКА//////////////////////// class Player { private: float x, y = 0; public: float w, h, dx, dy, speed; int dir, playerScore, health; bool life; String File; Image image; Texture texture; Sprite sprite; Player(String F, float X, float Y, float W, float H){ dir = 0; speed = 0; playerScore = 0; health = 100; dx=0;dy=0; life = true; File = F; w = W; h = H; image.loadFromFile("images/" + File); image.createMaskFromColor(Color(41, 33, 59)); texture.loadFromImage(image); sprite.setTexture(texture); x = X; y = Y; sprite.setTextureRect(IntRect(0, 0, w, h)); } void update(float time) { switch (dir) { case 0: dx = speed; dy = 0; break; case 1: dx = -speed; dy = 0; break; case 2: dx = 0; dy = speed; break; case 3: dx = 0; dy = -speed; break; } x += dx*time; y += dy*time; speed = 0; sprite.setPosition(x, y); interactionWithMap(); if (health <= 0){ life = false; } } float getplayercoordinateX(){ return x; } float getplayercoordinateY(){ return y; } void interactionWithMap() { for (int i = y / 32; i < (y + h) / 32; i++) for (int j = x / 32; j<(x + w) / 32; j++) { if (TileMap[i][j] == '0') { if (dy>0) { y = i * 32 - h; } if (dy<0) { y = i * 32 + 32; } if (dx>0) { x = j * 32 - w; } if (dx < 0) { x = j * 32 + 32; } } if (TileMap[i][j] == 's') { playerScore++; TileMap[i][j] = ' '; } if (TileMap[i][j] == 'f') { health -= 40; TileMap[i][j] = ' '; } if (TileMap[i][j] == 'h') { health += 20; TileMap[i][j] = ' '; } } } }; int main() { RenderWindow window(VideoMode(640, 480), "Lesson 14. kychka-pc.ru"); view.reset(FloatRect(0, 0, 640, 480)); Font font; font.loadFromFile("CyrilicOld.ttf"); Text text("", font, 20); text.setColor(Color::Black); Image map_image; map_image.loadFromFile("images/map.png"); Texture map; map.loadFromImage(map_image); Sprite s_map; s_map.setTexture(map); Image quest_image; quest_image.loadFromFile("images/missionbg.jpg"); quest_image.createMaskFromColor(Color(0, 0, 0)); Texture quest_texture; quest_texture.loadFromImage(quest_image); Sprite s_quest; s_quest.setTexture(quest_texture); s_quest.setTextureRect(IntRect(0, 0, 340, 510)); //приведение типов, размеры картинки исходные s_quest.setScale(0.6f, 0.6f);//чуть уменьшили картинку, => размер стал меньше Player p("hero.png", 250, 250, 96.0, 96.0); bool showMissionText = true;//логическая переменная, отвечающая за появление текста миссии на экране float currentFrame = 0; Clock clock; Clock gameTimeClock; int gameTime = 0; while (window.isOpen()) { float time = clock.getElapsedTime().asMicroseconds(); if (p.life) gameTime = gameTimeClock.getElapsedTime().asSeconds(); else { view.move(0.8, 0); } clock.restart(); time = time / 800; Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); if (event.type == Event::KeyPressed)//событие нажатия клавиши if ((event.key.code == Keyboard::Tab)) {//если клавиша ТАБ switch (showMissionText) {//переключатель, реагирующий на логическую переменную showMissionText case true: { std::ostringstream playerHealthString;//строка здоровья игрока playerHealthString << p.health; //заносим в строку здоровье std::ostringstream task;//строка текста миссии task << getTextMission(getCurrentMission(p.getplayercoordinateX()));//вызывается функция getTextMission (она возвращает текст миссии), которая принимает в качестве аргумента функцию getCurrentMission(возвращающую номер миссии), а уже эта ф-ция принимает в качестве аргумента функцию p.getplayercoordinateX() (эта ф-ция возвращает Икс координату игрока) text.setString("Здоровье: " + playerHealthString.str() + "\n" + task.str()); showMissionText = false;//эта строка позволяет убрать все что мы вывели на экране break;//выходим , чтобы не выполнить условие "false" (которое ниже) } case false: { text.setString("");//если не нажата клавиша таб, то весь этот текст пустой showMissionText = true;// а эта строка позволяет снова нажать клавишу таб и получить вывод на экран break; } } } } ///////////////////////////////////////////Управление персонажем с анимацией//////////////////////////////////////////////////////////////////////// if (p.life) { if (Keyboard::isKeyPressed(Keyboard::Left)) { p.dir = 1; p.speed = 0.1; currentFrame += 0.005*time; if (currentFrame > 3) currentFrame -= 3; p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 96, 96, 96)); } if (Keyboard::isKeyPressed(Keyboard::Right)) { p.dir = 0; p.speed = 0.1; currentFrame += 0.005*time; if (currentFrame > 3) currentFrame -= 3; p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 192, 96, 96)); } if (Keyboard::isKeyPressed(Keyboard::Up)) { p.dir = 3; p.speed = 0.1; currentFrame += 0.005*time; if (currentFrame > 3) currentFrame -= 3; p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 307, 96, 96)); } if (Keyboard::isKeyPressed(Keyboard::Down)) { p.dir = 2; p.speed = 0.1; currentFrame += 0.005*time; if (currentFrame > 3) currentFrame -= 3; p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 0, 96, 96)); } getplayercoordinateforview(p.getplayercoordinateX(), p.getplayercoordinateY()); } p.update(time); window.setView(view); window.clear(); if ((getCurrentMission(p.getplayercoordinateX())) == 0) { //Если текущая миссия 0, то рисуем карту вот так for (int i = 0; i < HEIGHT_MAP; i++) for (int j = 0; j < WIDTH_MAP; j++) { if (TileMap[i][j] == ' ') s_map.setTextureRect(IntRect(0, 0, 32, 32)); if (TileMap[i][j] == 's') s_map.setTextureRect(IntRect(32, 0, 32, 32)); if ((TileMap[i][j] == '0')) s_map.setTextureRect(IntRect(64, 0, 32, 32)); if ((TileMap[i][j] == 'f')) s_map.setTextureRect(IntRect(96, 0, 32, 32)); if ((TileMap[i][j] == 'h')) s_map.setTextureRect(IntRect(128, 0, 32, 32)); s_map.setPosition(j * 32, i * 32); window.draw(s_map); } } if ((getCurrentMission(p.getplayercoordinateX())) >= 1) { //Если текущая миссия 1, то рисуем карту вот так for (int i = 0; i < HEIGHT_MAP; i++) for (int j = 0; j < WIDTH_MAP; j++) { if (TileMap[i][j] == ' ') s_map.setTextureRect(IntRect(64, 0, 32, 32));//для примера поменял местами вывод спрайта для этого символа и.. if (TileMap[i][j] == 's') s_map.setTextureRect(IntRect(32, 0, 32, 32)); if ((TileMap[i][j] == '0')) s_map.setTextureRect(IntRect(0, 0, 32, 32));//и для вот этого. логически-игровой смысл их остался таким же if ((TileMap[i][j] == 'f')) s_map.setTextureRect(IntRect(96, 0, 32, 32)); if ((TileMap[i][j] == 'h')) s_map.setTextureRect(IntRect(128, 0, 32, 32)); s_map.setPosition(j * 32, i * 32); window.draw(s_map); } } if (!showMissionText) { text.setPosition(view.getCenter().x + 125, view.getCenter().y - 130);//позиция всего этого текстового блока s_quest.setPosition(view.getCenter().x + 115, view.getCenter().y - 130);//позиция фона для блока window.draw(s_quest); window.draw(text); //рисуем спрайт свитка (фон для текста миссии). а затем и текст. все это завязано на логическую переменную, которая меняет свое состояние от нажатия клавиши ТАБ } window.draw(p.sprite); window.display(); } return 0; } |
mission.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
///////////////////////////////////НОМЕР МИССИИ////////////////////////////////// int getCurrentMission(int x)//ф-ция номера миссия, которая меняет номер миссии, в зависимости от координаты игрока Х (сюда будем передавать эту координату) { int mission = 0; if ((x>0) && (x<400)) { mission = 0; } //знакомим игрока с игрой if (x>400) { mission = 1; } //игрок на первой миссии if (x>700) { mission = 2; }//2ой if (x>2200) { mission = 3; }//и тд return mission;//ф-ция возвращает номер миссии } /////////////////////////////////////ТЕКСТ МИССИИ///////////////////////////////// std::string getTextMission(int currentMission) { std::string missionText = "";//текст миссии и его инициализация switch (currentMission)//принимается номер миссии и в зависимости от него переменной missionText присваивается различный текст { case 0: missionText = "\nНачальный этап и \nинструкции к игре"; break; case 1: missionText = "\nMission 1\n\nВот твоя первая\n миссия, на\n этом уровне \nтебе стоит опасаться\n ... бла-бла-бла ..."; break; case 2: missionText = "\nMission 2\n Необходимо решить\n логическую задачку,\n чтобы пройти дальше "; break; case 3: missionText = "\nИ так далее \nи тому подобное....."; break; } return missionText;//ф-ция возвращает текст }; |
Спасибо за уроки!)
пожалуйста!
Шикарные уроки!
Все работает!
Теперь буду воплощать свои задумки)
Спасибо большое:) Делитесь потом с нами:)
“Вот мы плавно переходим на ООП” – заявлено было в одном из уроков. Вынесли камеру и карту в отдельный файл, и то не полностью – продолжаем всё лепить в main. Когда мне захотелось что-то изменить или добавить в соответствии с уроком – у меня забомбило, потому что понять где что было не просто. Не так уж и сложно самому под ООП переписать, но как по мне – плохой пример подаёте. Надеюсь в уроках 15 и дальше вы исправились, но я их пока не смотрел.
Солидарен с тобой, бро!