Поговорим о движущихся платформах в платформере Что мы знаем о них? Они платформы и они двигаются. Итак, нам необходимо создать класс платформы, он будет наследоваться от Entity, как и все остальные нынешние классы, т.к у платформы есть высота, ширина, координаты, спрайт, текстура и так далее. Ни к чему описывать это каждый раз, поэтому просто унаследуем от Entity, аналогично мы поступали с игроком и врагом.
Видеоверсия http://www.youtube.com/watch?v=pFUWlKubvsc
Класс движущихся платформ назовем … MovingPlatform !
Давайте посмотрим как он выглядит:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class MovingPlatform : public Entity{//класс движущейся платформы public: MovingPlatform(Image &image, String Name, Level &lvl, float X, float Y, int W, int H) :Entity(image, Name, X, Y, W, H){ sprite.setTextureRect(IntRect(0, 0, W, H));//прямоугольник dx = 0.08;//изначальное ускорение по Х } void update(float time)//функция обновления платформы. { x += dx * time;//реализация движения по горизонтали moveTimer += time;//наращиваем таймер if (moveTimer>2000) { dx*= -1; moveTimer = 0; }//если прошло примерно 2 сек, то меняется направление движения платформы,а таймер обнуляется sprite.setPosition(x+w/2, y+h/2);//задаем позицию спрайту } }; |
Объект платформы имеет такой же конструктор, как и враг:
1 |
MovingPlatform(Image &image, String Name, Level &lvl, float X, float Y, int W, int H) :Entity(image, Name, X, Y, W, H) |
Первым параметром передаем изображение платформы, затем её имя (для удобства оно соответствует имени в .tmx карте) и в будущем мы сможем расставлять платформы в тайлмап редакторе, а не программно (как с врагами ранее). Затем передаем уровень игры, а так же координаты появления платформы (затем это могут быть координаты объекта платформы из карты tmx, то есть так же, как и с врагами), ну и ширина и высота платформы соответственно.
В функции update и описана логика движения платформы – она меняет своё направление примерно каждые две секунды. Помимо этого у неё может быть и другая логика. Например платформа может ездить от стенки до стенки, аналогично нашему врагу. Всего-то и надо прописан похожие вещи в update платформы.
Прежде чем создать платформу нужно найти для нее спрайт. Я просто накидал спрайт в паинте для теста:
Если вы в реальной игре будете использовать такую же уродливую платформу как эта, то я вам советую использовать прямоугольник в sfml, нежели спрайт. А вобще найдите красивую платформу, а лучше нарисуйте сами. У моей платформы размер 95*22, пригодятся эти цифры при создании самой платформы.
В ф-ции main в месте, где мы создаем различные изображения, добавим еще изображения для платформы:
1 2 |
Image movePlatformImage; movePlatformImage.loadFromFile("images/MovingPlatform.png"); |
это изображение мы передадим по ссылке в конструктор, при создании объекта движущейся платформы (так же делали с врагом).
Я поставил платформу вот здесь:
Теперь наша задача считать её координаты из карты перед созданием(не устану это повторять, но с врагами мы уже тоже так делали:))
Вот кусочек кода – новое с комментариями:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
std::vector<Object> e = lvl.GetObjects("EasyEnemy"); for (int i = 0; i < e.size(); i++) entities.push_back(new Enemy(easyEnemyImage, "EasyEnemy", lvl, e[i].rect.left, e[i].rect.top, 200, 97)); Object player=lvl.GetObject("player"); Player p(heroImage, "Player1", lvl, player.rect.left, player.rect.top, 40, 30); e = lvl.GetObjects("MovingPlatform");//забираем все платформы в вектор for (int i = 0; i < e.size(); i++) entities.push_back(new MovingPlatform(movePlatformImage, "MovingPlatform", lvl, e[i].rect.left, e[i].rect.top, 95, 22));//закидываем платформу в список.передаем изображение имя уровень координаты появления (взяли из tmx карты), а так же размеры Clock clock; while (window.isOpen()) |
Как вы заметили мы считываем все платформы с карты, затем пробегаясь по вектору, состоящего из этих платформ, добавляем платформы в список, забирая координаты у элемента вектора. ТАК ЖЕ БЫЛО И С ВРАГАМИ!
Итак, храним мы платформы всё в том же списке entities, в котором мы храним врагов. Тогда нам не нужно будет создавать новый список типа MovingPlatform, кидать туда платформы и потом по нему лишний раз пробегаться, чтобы вызвать update или draw только лишь для платформы.
Ещё разок: так как мы уже закинули платформу в список – нам не нужно еще раз вызывать функцию update для неё, поскольку она уже была вызвана для всех объектов этого списка (см. предыдущ уроки), аналогично с рисованием платформы.
Далее мы пропишем взаимодействие игрока с этой платформой, то есть столкновение с ней. Для этого в том же цикле for где мы взаимодействуем с врагами (до или после этого, я напишу до) напишем (новый код помечен комментариями):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
for (it = entities.begin(); it != entities.end(); it++)//проходимся по эл-там списка { if (((*it)->name == "MovingPlatform") && ((*it)->getRect().intersects(p.getRect())))//если игрок столкнулся с объектом списка и имя этого объекта movingplatform { Entity *movPlat = *it; if ((p.dy>0) || (p.onGround == false))//при этом игрок находится в состоянии после прыжка, т.е падает вниз if (p.y + p.h<movPlat->y + movPlat->h)//если игрок находится выше платформы, т.е это его ноги минимум (тк мы уже проверяли что он столкнулся с платформой) { p.y = movPlat->y - p.h + 3; p.x += movPlat->dx*time; p.dy = 0; p.onGround = true; // то выталкиваем игрока так, чтобы он как бы стоял на платформе } } if ((*it)->getRect().intersects(p.getRect())) { if ((*it)->name == "EasyEnemy"){//и при этом имя объекта EasyEnemy,то.. |
Ниже пошло старое взаимодействие с врагом и как видим в случае с платформой мы проверили имя объекта списка и столкновение его с игроком в одной строке, а в случае с врагом мы это сделали в две строки. можно оставить и так и так, но я предпочитаю написать это в одну строку как с платформой.
ф-цию update игрока перенесем после всех этих взаимодействий с объектами списка:
Вот собственно и всё. Это будет уже работать , т.к рисование объектов списка и ф-цию update мы вызываем для всех в списке Entity, а наша платформа как раз там.
Это был простейший пример с платформами. Есть еще двусторонние платформы, если вкратце – герой ударится о нее головой. Так же с платформ в некоторых играх можно спрыгивать вниз. Все это темы для следующих уроков, сейчас проходим основное и такие нюансы нам не важны.
Скидываю слегка измененный main.cpp, а так же весь проект: https://yadi.sk/d/nHS_nbO5hoesh
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 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
#include <SFML/Graphics.hpp> #include <SFML/Audio.hpp> #include "view.h" #include <iostream> //#include <sstream>//сейчас не нужно. раньше выводили текст ( в 13 ом уроке ) #include "mission.h" #include "iostream" #include "level.h" #include <vector> #include <list> using namespace sf; ////////////////////////////////////Общий класс-родитель////////////////////////// class Entity { public: std::vector<Object> obj; float dx, dy, x, y, speed,moveTimer; int w,h,health; bool life, isMove, onGround; Texture texture; Sprite sprite; String name; Entity(Image &image, String Name, float X, float Y, int W, int H){ x = X; y = Y; w = W; h = H; name = Name; moveTimer = 0; speed = 0; health = 100; dx = 0; dy = 0; life = true; onGround = false; isMove = false; texture.loadFromImage(image); sprite.setTexture(texture); sprite.setOrigin(w / 2, h / 2); } FloatRect getRect(){ return FloatRect(x, y, w, h); } virtual void update(float time) = 0;//все потомки переопределяют эту ф-цию }; ////////////////////////////////////////////////////КЛАСС ИГРОКА//////////////////////// class Player :public Entity { public: enum { left, right, up, down, jump, stay } state; int playerScore; Player(Image &image, String Name, Level &lev, float X, float Y, int W, int H) :Entity(image, Name, X, Y, W, H ){ playerScore = 0; state = stay; obj = lev.GetAllObjects(); if (name == "Player1"){ sprite.setTextureRect(IntRect(4, 19, w, h)); } } void control(){ if (Keyboard::isKeyPressed){ if (Keyboard::isKeyPressed(Keyboard::Left)) { state = left; speed = 0.1; } if (Keyboard::isKeyPressed(Keyboard::Right)) { state = right; speed = 0.1; } if ((Keyboard::isKeyPressed(Keyboard::Up)) && (onGround)) { state = jump; dy = -0.6; onGround = false; } if (Keyboard::isKeyPressed(Keyboard::Down)) { state = down; } } } void checkCollisionWithMap(float Dx, float Dy) { for (int i = 0; i<obj.size(); i++) if (getRect().intersects(obj[i].rect)) { if (obj[i].name == "solid") { if (Dy>0) { y = obj[i].rect.top - h; dy = 0; onGround = true; } if (Dy<0) { y = obj[i].rect.top + obj[i].rect.height; dy = 0; } if (Dx>0) { x = obj[i].rect.left - w; } if (Dx<0) { x = obj[i].rect.left + obj[i].rect.width; } } // else { onGround = false; } } } void update(float time) { control(); switch (state) { case right:dx = speed; break; case left:dx = -speed; break; case up: break; case down: dx = 0; break; case stay: break; } x += dx*time; checkCollisionWithMap(dx, 0); y += dy*time; checkCollisionWithMap(0, dy); sprite.setPosition(x+w/2,y+h/2); if (health <= 0){ life = false; } if (!isMove){ speed = 0; } if (life) { setPlayerCoordinateForView(x, y); } dy = dy + 0.0015*time; } }; class Enemy :public Entity{ public: Enemy(Image &image, String Name,Level &lvl, float X, float Y, int W, int H) :Entity(image, Name, X, Y, W, H){ obj = lvl.GetObjects("solid");//инициализируем.получаем нужные объекты для взаимодействия врага с картой if (name == "EasyEnemy"){ sprite.setTextureRect(IntRect(0, 0, w, h)); dx = 0.1; } } void checkCollisionWithMap(float Dx, float Dy) { for (int i = 0; i<obj.size(); i++) if (getRect().intersects(obj[i].rect)) { if (obj[i].name == "solid")//если встретили препятствие { if (Dy>0) { y = obj[i].rect.top - h; dy = 0; onGround = true; } if (Dy<0) { y = obj[i].rect.top + obj[i].rect.height; dy = 0; } if (Dx>0) { x = obj[i].rect.left - w; dx = -0.1; sprite.scale(-1, 1); } if (Dx<0) { x = obj[i].rect.left + obj[i].rect.width; dx = 0.1; sprite.scale(-1, 1); } } } } void update(float time) { if (name == "EasyEnemy"){ //moveTimer += time;if (moveTimer>3000){ dx *= -1; moveTimer = 0; }//меняет направление примерно каждые 3 сек(альтернативная версия смены направления) checkCollisionWithMap(dx, 0); x += dx*time; sprite.setPosition(x+w/2, y+h/2); if (health <= 0){ life = false; } } } }; class MovingPlatform : public Entity{//класс движущейся платформы public: MovingPlatform(Image &image, String Name, Level &lvl, float X, float Y, int W, int H) :Entity(image, Name, X, Y, W, H){ sprite.setTextureRect(IntRect(0, 0, W, H));//прямоугольник dx = 0.08;//изначальное ускорение по Х } void update(float time)//функция обновления платформы. { x += dx * time;//реализация движения по горизонтали moveTimer += time;//наращиваем таймер if (moveTimer>2000) { dx*= -1; moveTimer = 0; }//если прошло примерно 2 сек, то меняется направление движения платформы,а таймер обнуляется sprite.setPosition(x+w/2, y+h/2);//задаем позицию спрайту } }; int main() { RenderWindow window(VideoMode(640, 480), "Lesson 27. kychka-pc.ru"); view.reset(FloatRect(0, 0, 640, 480)); Level lvl; lvl.LoadFromFile("map.tmx"); Image heroImage; heroImage.loadFromFile("images/MilesTailsPrower.gif"); Image easyEnemyImage; easyEnemyImage.loadFromFile("images/shamaich.png"); easyEnemyImage.createMaskFromColor(Color(255, 0, 0)); Image movePlatformImage; movePlatformImage.loadFromFile("images/MovingPlatform.png"); std::list<Entity*> entities; std::list<Entity*>::iterator it; std::list<Entity*>::iterator it2;//второй итератор.для взаимодействия между объектами списка std::vector<Object> e = lvl.GetObjects("EasyEnemy"); for (int i = 0; i < e.size(); i++) entities.push_back(new Enemy(easyEnemyImage, "EasyEnemy", lvl, e[i].rect.left, e[i].rect.top, 200, 97)); Object player=lvl.GetObject("player"); Player p(heroImage, "Player1", lvl, player.rect.left, player.rect.top, 40, 30); e = lvl.GetObjects("MovingPlatform");//забираем все платформы в вектор for (int i = 0; i < e.size(); i++) entities.push_back(new MovingPlatform(movePlatformImage, "MovingPlatform", lvl, e[i].rect.left, e[i].rect.top, 95, 22));//закидываем платформу в список.передаем изображение имя уровень координаты появления (взяли из tmx карты), а так же размеры Clock clock; while (window.isOpen()) { float time = clock.getElapsedTime().asMicroseconds(); clock.restart(); time = time / 800; Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); } for (it = entities.begin(); it != entities.end();)//говорим что проходимся от начала до конца { Entity *b = *it;//для удобства, чтобы не писать (*it)-> b->update(time);//вызываем ф-цию update для всех объектов (по сути для тех, кто жив) if (b->life == false) { it = entities.erase(it); delete b; }// если этот объект мертв, то удаляем его else it++;//и идем курсором (итератором) к след объекту. так делаем со всеми объектами списка } for (it = entities.begin(); it != entities.end(); it++)//проходимся по эл-там списка { if (((*it)->name == "MovingPlatform") && ((*it)->getRect().intersects(p.getRect())))//если игрок столкнулся с объектом списка и имя этого объекта movingplatform { Entity *movPlat = *it; if ((p.dy>0) || (p.onGround == false))//при этом игрок находится в состоянии после прыжка, т.е падает вниз if (p.y + p.h<movPlat->y + movPlat->h)//если игрок находится выше платформы, т.е это его ноги минимум (тк мы уже проверяли что он столкнулся с платформой) { p.y = movPlat->y - p.h + 3; p.x += movPlat->dx*time; p.dy = 0; p.onGround = true; // то выталкиваем игрока так, чтобы он как бы стоял на платформе } } if ((*it)->getRect().intersects(p.getRect())) { if ((*it)->name == "EasyEnemy"){//и при этом имя объекта EasyEnemy,то.. ////////выталкивание врага if ((*it)->dx>0)//если враг идет вправо { std::cout << "(*it)->x" << (*it)->x << "\n";//коорд игрока std::cout << "p.x" << p.x << "\n\n";//коорд врага (*it)->x = p.x - (*it)->w; //отталкиваем его от игрока влево (впритык) (*it)->dx = 0;//останавливаем std::cout << "new (*it)->x" << (*it)->x << "\n";//новая коорд врага std::cout << "new p.x" << p.x << "\n\n";//новая коорд игрока (останется прежней) } if ((*it)->dx < 0)//если враг идет влево { (*it)->x = p.x + p.w; //аналогично - отталкиваем вправо (*it)->dx = 0;//останавливаем } ///////выталкивание игрока if (p.dx < 0) { p.x = (*it)->x + (*it)->w;}//если столкнулись с врагом и игрок идет влево то выталкиваем игрока if (p.dx > 0) { p.x = (*it)->x - p.w;}//если столкнулись с врагом и игрок идет вправо то выталкиваем игрока } } for (it2 = entities.begin(); it2 != entities.end(); it2++) { if ((*it)->getRect() != (*it2)->getRect())//при этом это должны быть разные прямоугольники if (((*it)->getRect().intersects((*it2)->getRect())) && ((*it)->name == "EasyEnemy") && ((*it2)->name == "EasyEnemy"))//если столкнулись два объекта и они враги { (*it)->dx *= -1;//меняем направление движения врага (*it)->sprite.scale(-1, 1);//отражаем спрайт по горизонтали } } } p.update(time); window.setView(view); window.clear(Color(77,83,140)); lvl.Draw(window); for (it = entities.begin(); it != entities.end(); it++){ window.draw((*it)->sprite); } window.draw(p.sprite); window.display(); } return 0; } |