Такая вещь как притяжение спрайта к земле существует в играх типа Марио, Tiny Toon ещё помню:-) Всё довольно просто реализуется, приступим:
Видеоверсия: https://youtu.be/yHrJdhjRnlw
Вторая часть видео: https://youtu.be/M4MvHAlwmqQ
К сожалению, нам многое придется переделать с прошлого урока в плане игровой логики. Ну, всё потому, что у нас игра стала превращаться в стратегию, а это ни разу не платформер Марио )) В этом уроке мы изменим движение, реализуя лишь два направления – вправо и влево, к этим направлениям добавим прыжок.
В классе игрока добавим логическую переменную , которая будет отвечать за состояние нахождения объекта на земле:
1 |
bool life, isMove,isSelect,onGround;//добавили переменную состояния нахождения на земле |
Для себя я оставлю переменные из прошлых уроков ( isMove,isSelect ), хоть они, возможно, и не годятся для платформера, но для создания дальнейших уроков мне ещё понадобятся. Удалите их, если они вам не нужны.
В конструкторе класса инициализируем эту переменную:
1 2 3 4 |
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; isMove = false; isSelect = false; onGround = false; |
Я немного изменил высоту картинки спрайта чтобы не было пустоты (было 96, стало 54. видно будет в ф-ции int main при создании объекта player p). В конструкторе изменил так же sprite.setTextureRect(IntRect(0, 134, w, h)); т.к нам нужно загрузить одно из направлений движения(картинку льва) при первом появлении его на экране.
Как мы помним у нас есть строка sprite.setOrigin(w/2, h/2); , которую мы перенесем в конструктор класса в самый конец (если она где-то в другом месте). Так вот эта строка задает середину спрайту. То есть его основная точка теперь не левый верхний угол, а середина. И когда наш спрайт коснется земли – он будет приподнят над ней из-за этой середины, потому что мы уже рисуем от середины, а у нас раньше рисование было сверху слева, значит при рисовании сместимся ниже и правее. Вобщем появится пустое пространство и будет пропасть между спрайтом и землей. Нам просто нужно убрать эту пропасть (или можно убрать setOrigin, но я думаю удобнее когда основная точка спрайта в середине). В ф-ции update изменим строку задания позиции спрайту на такую:
1 |
sprite.setPosition(x+w/2, y+h/2); //задаем позицию спрайта равную его середине |
Таким образом графически и логически подружатся setOrigin и SetPosition.
Теперь в классе добавим переменную перечисляемого типа:
1 |
enum { left,right,up,down,jump,stay } state;//добавляем тип перечисления - состояние объекта |
у нашего персонажа может быть несколько состояний. мы уходим от переменной направления dir и надо её удалить. Инициализируем в конструкторе state = stay;
Немного переходя к ООП перенесем управление персонажем из функции int main в метод класса игрока. Назовем этот метод control(), он будет отвечать за нажатие клавиш. Вызовем его мы потом внутри метода update.
Итак, я просто перенес из int main управление в метод control, пока что оставив закомментированной анимацию, с ней мы потом разберемся.
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 |
void control(){ if (Keyboard::isKeyPressed(Keyboard::Left)) { state = left; speed = 0.1; //currentFrame += 0.005*time; //if (currentFrame > 3) currentFrame -= 3; //p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 135, 96, 54)); } if (Keyboard::isKeyPressed(Keyboard::Right)) { state = right; speed = 0.1; // currentFrame += 0.005*time; // if (currentFrame > 3) currentFrame -= 3; // p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 232, 96, 54)); } if ((Keyboard::isKeyPressed(Keyboard::Up)) && (onGround)) { state = jump; dy = -0.5; onGround = false;//то состояние равно прыжок,прыгнули и сообщили, что мы не на земле //currentFrame += 0.005*time; //if (currentFrame > 3) currentFrame -= 3; //p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 307, 96, 96)); } if (Keyboard::isKeyPressed(Keyboard::Down)) { state = down; speed = 0.1; //currentFrame += 0.005*time; //if (currentFrame > 3) currentFrame -= 3; //p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 0, 96, 96)); } |
Мы видим, что при каждом нажатии клавиши у нас переменная состояния объекта (state) принимает различные значения. Так же легко видеть, что анимация сейчас занимает довольно много строк кода и хорошо бы её где-то описать и потом просто вызвать. Что-то подобное сделаем на следующем уроке, а пока займемся логикой прыжка. (анимацию пока кинул в комменты)
Если вы посмотрите выше на код для кнопки вверх, то увидите, что нажимать кнопку вверх мы можем только тогда, когда находимся на земле. Если мы на земле и нажали вверх, то переменная состояния равна “прыжку”, dy=-0.5 это и есть высота прыжка, и тут же сообщение, что мы не на земле (благодаря этому активируется притяжение к земле дальше в update увидите).
Далее функция update возьмет на себя некоторые изменения, ведь у нас больше не будет dir, у нас теперь состояния. Так же необходимо обрабатывать коллизии (столкновения с объектами карты) по Х и по Y отдельно, иначе будут глюки и выход за предел массива. Дело в том, что у нас появилось пересечение по диагонали (прыжок и движение вправо например), поэтому нам нужно после каждого движения персонажа (х+=,у+=) делать проверку на столкновение. Иначе персонаж как бы застрянет в карте (глазами не увидим т.к ошибка вылетит моментально, но программно это так) и вылетит ошибка.
Таким образом, ф-цию update (см комментарии) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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 jump: break;//здесь может быть вызов анимации case stay: break;//и здесь тоже } x += dx*time; checkCollisionWithMap(dx, 0);//обрабатываем столкновение по Х y += dy*time; checkCollisionWithMap(0, dy);//обрабатываем столкновение по Y if (!isMove) speed = 0; sprite.setPosition(x+w/2, y+h/2); //задаем позицию спрайта в место его центра if (health <= 0){life = false;} dy = dy + 0.0015*time;делаем притяжение к земле } |
Все, теперь осталось реализовать ф-цию столкновений с картой. Я создал новую ф-цию checkCollisionWithMap вместо старой iteractionWithMap. Они похожи друг на друга, но взаимодействие с картой сделаем уже в этой новой функции, а ту можем удалить. Ф-ция chechCollisionWithMap выглядит вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void checkCollisionWithMap(float Dx, float Dy)//ф ция проверки столкновений с картой { 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; dy = 0; onGround = true; }//по Y вниз=>идем в пол(стоим на месте) или падаем. В этот момент надо вытолкнуть персонажа и поставить его на землю, при этом говорим что мы на земле тем самым снова можем прыгать if (Dy<0){y = i * 32 + 32; dy = 0;}//столкновение с верхними краями карты(может и не пригодиться) if (Dx>0){x = j * 32 - w;}//с правым краем карты if (Dx<0){x = j * 32 + 32;}// с левым краем карты } else {onGround=false;} } } |
На этом всё, на следующем уроке еще больше перейдем к ООП и будем готовиться к созданию врага.
Для вопросов есть форум (ссылка наверху)
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 |
#include <SFML/Graphics.hpp> #include "map.h" #include "view.h" #include <iostream> #include <sstream> #include "mission.h" using namespace sf; ////////////////////////////////////////////////////КЛАСС ИГРОКА//////////////////////// class Player { public: float w,h,dx,dy,x,y,speed; int dir, playerScore, health; bool life, isMove,isSelect,onGround;//добавили переменные состояния нахождения на земле enum { left,right,up,down,jump,stay } state;//добавляем тип перечисления - состояние объекта 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; isMove = false; isSelect = false; onGround = false; 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, 134, w, h)); sprite.setOrigin(w / 2, h / 2); } void control(){ if (Keyboard::isKeyPressed(Keyboard::Left)) { state = left; speed = 0.1; //currentFrame += 0.005*time; //if (currentFrame > 3) currentFrame -= 3; //p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 135, 96, 54)); } if (Keyboard::isKeyPressed(Keyboard::Right)) { state = right; speed = 0.1; // currentFrame += 0.005*time; // if (currentFrame > 3) currentFrame -= 3; // p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 232, 96, 54)); } if ((Keyboard::isKeyPressed(Keyboard::Up)) && (onGround)) { state = jump; dy = -0.4; onGround = false;//то состояние равно прыжок,прыгнули и сообщили, что мы не на земле //currentFrame += 0.005*time; //if (currentFrame > 3) currentFrame -= 3; //p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 307, 96, 96)); } if (Keyboard::isKeyPressed(Keyboard::Down)) { state = down; speed = 0.1; //currentFrame += 0.005*time; //if (currentFrame > 3) currentFrame -= 3; //p.sprite.setTextureRect(IntRect(96 * int(currentFrame), 0, 96, 96)); } } void update(float time) { control();//функция управления персонажем switch (state)//тут делаются различные действия в зависимости от состояния { case right: dx = speed;break;//состояние идти вправо case left: dx = -speed;break;//состояние идти влево case up: break;//будет состояние поднятия наверх (например по лестнице) case down: break;//будет состояние во время спуска персонажа (например по лестнице) case jump: break;//здесь может быть вызов анимации case stay: break;//и здесь тоже } x += dx*time; checkCollisionWithMap(dx, 0);//обрабатываем столкновение по Х y += dy*time; checkCollisionWithMap(0, dy);//обрабатываем столкновение по Y if (!isMove) speed = 0; sprite.setPosition(x+w/2, y+h/2); //задаем позицию спрайта в место его центра if (health <= 0){life = false;} dy = dy + 0.0015*time;//делаем притяжение к земле } float getplayercoordinateX(){ return x; } float getplayercoordinateY(){ return y; } void checkCollisionWithMap(float Dx, float Dy)//ф ция проверки столкновений с картой { 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; dy = 0; onGround = true; }//по Y вниз=>идем в пол(стоим на месте) или падаем. В этот момент надо вытолкнуть персонажа и поставить его на землю, при этом говорим что мы на земле тем самым снова можем прыгать if (Dy<0){y = i * 32 + 32; dy = 0;}//столкновение с верхними краями карты(может и не пригодиться) if (Dx>0){x = j * 32 - w; }//с правым краем карты if (Dx<0){x = j * 32 + 32;}// с левым краем карты } } } }; class SpriteManager{//это задел на следующие уроки,прошу не обращать внимания на эти изменения) public: Image image; Texture texture; Sprite sprite; String name; String file; int widthOfSprite; int heightOfSprite; SpriteManager(String File,String Name){ file = File; name = Name; image.loadFromFile("images/" + file); texture.loadFromImage(image); sprite.setTexture(texture); } }; int main() { RenderWindow window(VideoMode(640, 480), "Lesson 20. 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); SpriteManager playerSprite("hero.png", "Hero");//это задел на следующие уроки,прошу не обращать внимания) Player p("hero.png", 250, 500, 96, 54); float currentFrame = 0; Clock clock; float dX = 0; float dY = 0; int tempX = 0;//временная коорд Х.Снимаем ее после нажатия прав клав мыши int tempY = 0;//коорд Y float distance = 0;//это расстояние от объекта до тыка курсора while (window.isOpen()) { float time = clock.getElapsedTime().asMicroseconds(); clock.restart(); time = time / 800; Vector2i pixelPos = Mouse::getPosition(window);//забираем коорд курсора Vector2f pos = window.mapPixelToCoords(pixelPos);//переводим их в игровые (уходим от коорд окна) Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); if (event.type == Event::MouseButtonPressed)//если нажата клавиша мыши if (event.key.code == Mouse::Left){//а именно левая if (p.sprite.getGlobalBounds().contains(pos.x, pos.y))//и при этом координата курсора попадает в спрайт { p.sprite.setColor(Color::Green);//красим спрайт в зеленый,тем самым говоря игроку,что он выбрал персонажа и может сделать ход p.isSelect = true; } } if (p.isSelect)//если выбрали объект if (event.type == Event::MouseButtonPressed)//если нажата клавиша мыши if (event.key.code == Mouse::Right){//а именно правая p.isMove = true;//то начинаем движение p.isSelect = false;//объект уже не выбран p.sprite.setColor(Color::White);//возвращаем обычный цвет спрайту tempX = pos.x;//забираем координату нажатия курсора Х tempY = pos.y;//и Y float dX = pos.x - p.x;//вектор , колинеарный прямой, которая пересекает спрайт и курсор float dY = pos.y - p.y;//он же, координата y float rotation = (atan2(dY, dX)) * 180 / 3.14159265;//получаем угол в радианах и переводим его в градусы std::cout << rotation << "\n";//смотрим на градусы в консольке p.sprite.setRotation(rotation);//поворачиваем спрайт на эти градусы } } if (p.isMove){ distance = sqrt((tempX - p.x)*(tempX - p.x) + (tempY - p.y)*(tempY - p.y));//считаем дистанцию (расстояние от точки А до точки Б). используя формулу длины вектора if (distance > 2){//этим условием убираем дергание во время конечной позиции спрайта p.x += 0.1*time*(tempX - p.x) / distance;//идем по иксу с помощью вектора нормали p.y += 0.1*time*(tempY - p.y) / distance;//идем по игреку так же } else { p.isMove = false; std::cout << "priehali\n"; }//говорим что уже никуда не идем и выводим веселое сообщение в консоль } ///////////////////////////////////////////Управление персонажем с анимацией//////////////////////////////////////////////////////////////////////// if (p.life) {getplayercoordinateforview(p.getplayercoordinateX(), p.getplayercoordinateY()); } p.update(time); window.setView(view); window.clear(); 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); } window.draw(p.sprite); window.display(); } return 0; } |
Извините, если вопрос глупый. Как можно сделать двойной прыжок в игре? Я пробовал сделать еще одно условие в условии прыжка, но ничего не подошло. Может вы подскажите?
Все, получилось. Извините.
В следующий раз пишите вопрос на форум, пожалуйста. А ещё было бы не плохо увидеть в разделе форумы “советы и готовые решения” – как же у вас все таки получилось. Другим будет полезно знать, а наш форум видят поисковики.
Я сделал, но там очень много переменных, код сам по себе построен криво. Такое на форум даже выкладывать стыдно
Отредактировал. Выложил на форум
Лев продолжает бежать, даже, если отпустишь кнопки, нужно добавить
и else перед условиями в методе control( );
у меня одного такая ошибка ?
она у меня появилась только тогда когда я использую :
enum { left,right,up,down,jump,stay} state;
Вложение:
получилось решить эту проблему?