На этом уроке еще больше переберемся под ООП , почистим int main ф-цию от лишних вещей, сделаем главный класс от которого унаследуем класс игрока, а потом и врага.
Видеоверсия: http://www.youtube.com/watch?v=R8Uml4XdXIw
Всё-таки я решил удалить лишний код в виде перемещения персонажа по клику мыши, и переменную isSelect, поскольку они характерны для стратегий. Еще вернемся к этому, пока что разберем более подробно тип игры платформер.
После чистки от всяких ненужностей функция int main имеет вид:
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 |
int main() { RenderWindow window(VideoMode(640, 480), "Lesson 21. kychka-pc.ru"); view.reset(FloatRect(0, 0, 640, 480)); Image map_image; map_image.loadFromFile("images/map.png"); Texture map; map.loadFromImage(map_image); Sprite s_map; s_map.setTexture(map); Player p("hero.png", 250, 500, 96, 54);//объект класса игрока 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(); } p.update(time);// Player update function 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; } |
Слежение за камеры игроком (переименовал её на более логичное ‘set’) я перенес в функцию update класса игрока
1 |
if (life) { setPlayerCoordinateForView(x, y); } |
Как видите функция int main уменьшилась в плане количества строк кода, её ещё сильнее можно уменьшить, убрав связанные с созданием и прорисовкой карты строки. Но делать этого не будем пока что. Займемся созданием класса врага.
Что общего между врагом и игроком? Очень многое: жизнь/смерть, здоровье, координаты у обоих есть, ширина,высота,функция обновления(update), столкновение с картой и многое многое другое. Нам ведь не нужно создавать класс врага и прописывать всё тоже самое для него , добавляя ещё больше лишних строк кода? Да, нам этого не надо и мы будем использовать наследование. Наследование даст нам те же переменные и функции классу потомку и мы сможем их использовать, не описывая при этом по новой по сути тоже самое. При этом мы можем переопределять те же функции, если функции родителя нас не устраивают. В частности мы затроним функцию update , поскольку поведение игрока (управление и тд) и врага (поведение врага в игре) совсем различны.
Итак, сейчас мой класс игрока выглядит вот так:
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 |
class Player { public: float w,h,dx,dy,x,y,speed; int playerScore, health; bool life,isMove, 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){ speed = 0; playerScore = 0; health = 100; dx = 0; dy = 0; life = true; onGround = false; isMove = false; state = stay; 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; } if (Keyboard::isKeyPressed(Keyboard::Right)) { state = right; speed = 0.1; } if ((Keyboard::isKeyPressed(Keyboard::Up)) && (onGround)) { dy = -0.4; onGround = false;//то состояние равно прыжок,прыгнули и сообщили, что мы не на земле } if (Keyboard::isKeyPressed(Keyboard::Down)) { state = down; speed = 0.1; } } 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);//обрабатываем столкновение по Y sprite.setPosition(x+w/2, y+h/2); //задаем позицию спрайта в место его центра if (health <= 0){life = false;} if (!isMove){ speed = 0; } dy = dy + 0.0015*time;//делаем притяжение к земле if (life) { setPlayerCoordinateForView(x, 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;}// с левым краем карты } } } }; |
Как видите я убрал функции получения и задания ширины,высоты спрайта, а так же координат х и y (у нас была ф-ция getPlayerCoordinateX() например для взятия координаты “х” у спрайта). В дальнейшем я сделаю эти переменные приватными и мы сможем изменять их значение (или извлекать их значение) при помощи написанных set и get ф-ций.
Безусловно, вы можете использовать стандартные sfml функции для взятия, например координат (sprite.getPosition().x;) , или высоты (sprite.getTextureRect().height;), в дальнейшем я буду делать так же.
Итак, у нас куча объектов и они имеют общие черты, поэтому создадим один общий для них класс, от которого будем наследоваться. В этом классе и будут содержаться эти общие черты.
Функция управления персонажем control() ,безусловно, останется в классе игрока. У всех объектов: врагов, пулей, игрока и прочих – своя игровая логика и функция update для каждого из объектов будет разной.
Итак, создадим класс Entity , в котором и будут храниться общие поля и методы. Кстати, практически всю инициализацию мы будем делать в этом классе и нам не придется повторять строки инициализации в других конструкторах (врага, игрока и тд).
Класс Entity выглядит вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Entity { public: float dx, dy, x, y, speed,moveTimer;//добавили переменную таймер для будущих целей int w,h,health; bool life, isMove, onGround; Texture texture; Sprite sprite; String name;//враги могут быть разные, мы не будем делать другой класс для врага.всего лишь различим врагов по имени и дадим каждому свое действие в update в зависимости от имени Entity(Image &image, float X, float Y,int W,int H,String Name){ 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); } }; |
Вот они – общие переменные в public. Все они будут доступны для других дочерних классов , наследуемых от Entity. Появилась переменная moveTimer, её будем потом использовать для реализации врага, или двигающейся платформы (которая каждые 3 сек меняет направление, например).
Важный момент с изображением (которое передается первым параметром в конструкторе) – мы передаем значение по ссылке. В функции main будем создавать изображения, например вот так:
1 2 |
Image heroImage; heroImage.loadFromFile("images/MilesTailsPrower.gif"); |
И потом заряжать их в конструктор дочернего класса,в конструкторе дочернего класса оно передастся в конструктор класса родителя, а уже в конструкторе класса родителя (то есть Entity) оно закидывается в текстуру, затем в спрайт. Если бы мы передавали не по ссылке – каждый раз при создании нового врага создавалась бы копия этого изображения, что раздувало бы нам память. В нашем же случае, получается, что мы создаем изображение всего 1 раз.
Далее в конструкторе Entity принимаются такие параметры как: координаты появления объекта, ширина и высота объекта, и имя. Зачем нужно имя? Так мы сможем создавать различных врагов с разными изображениями и поведением и все они будут экземплярами класса Enemy. То есть нам не нужно делать отдельно класс HardEnemy, EasyEnemy, BossEnemy, и тд. Мы даём имя врагу и в зависимости от его имени реализуем внутри класса логику для этого врага. Аналогично с игроком – может быть два игрока и два имени – “Player1″ и “Player2″. Вобщем после всех этих параметров в конструкторе идет инициализация переменных. Эти переменные будут инициализированы сразу для всех дочерних классов при создании их экземпляров.
После создания класса Entity мы сможем удалить часть класса Player , поскольку многое уже перешло в Entity.
Класс Player выглядит теперь так:
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 |
class Player :public Entity { public: enum { left, right, up, down, jump, stay } state;//добавляем тип перечисления - состояние объекта int playerScore;//эта переменная может быть только у игрока Player(Image &image, float X, float Y,int W,int H,String Name):Entity(image,X,Y,W,H,Name){ playerScore = 0; state = stay; 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 = 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; }//надо убрать т.к мы можем находиться и на другой поверхности или платформе которую разрушит враг } } 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);//обрабатываем столкновение по Y sprite.setPosition(x + w / 2, y + h / 2); //задаем позицию спрайта в место его центра if (health <= 0){ life = false; } if (!isMove){ speed = 0; } //if (!onGround) { dy = dy + 0.0015*time; }//убираем и будем всегда притягивать к земле if (life) { setPlayerCoordinateForView(x, y); } dy = dy + 0.0015*time;//постоянно притягиваемся к земле } }; |
Класс игрока на данный момент самый большой класс. Не удивительно, ведь последние 20 уроков мы только и делали, что прокачивали игрока:))
Всего две переменные имеет Игрок, этих переменных нет у других. Это очки игрока и состояние игрока. Причем состояние, возможно, придется перенести в будущем в Entity, ведь у нас могут быть крутые и не тупые враги, имеющие свою логику. Плюс каждому врагу проще будет давать анимацию, если он будет иметь эти состояния. Пока оставим так и не будем усложнять сейчас задачу.
В параметрах конструктора игрока мы имеем все то же изображение (именно то, которое загрузили один раз в int main), координаты и размеры будущего спрайта. Затем мы передаем их в базовый класс (класс родитель) Entity. Там уже эти переменные инициализируются и изображение запиливается в спрайт.
В самом конструкторе, внутри, для первого игрока делаем прямоугольник спрайта. Ведь если у нас будет второй игрок – у него может быть другой спрайт и другие размеры. Вот это и прописали. (4 и 19 в самом начале, это координаты в картинке, с которых мы начинаем рисовать). Я взял другой спрайт вместо того льва, теперь у меня спрайт знаменитого Лисёнка из Соника. Вот он:
С этим понятно, теперь разберем функции игрока или как ещё говорят – методы.
в control() происходит управление, (я убрал пока анимацию и увеличил высоту прыжка). Эту функцию мы потом вызываем в update, что делает её всегда активной.
Далее идёт та же функция обработки коллизий (столкновений) с картой.
Затем функция update, в которой мы вызываем эти функции. Именно функцию update мы вызываем в функции int main() внутри цикла “пока открыто окно”.
Теперь остался враг. У врага тоже много общего между игроком. Даже конструктор такой же. Смотрим как враг выглядит:
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 |
class Enemy :public Entity{ public: Enemy(Image &image, float X, float Y,int W,int H,String Name):Entity(image,X,Y,W,H,Name){ if (name == "EasyEnemy"){ sprite.setTextureRect(IntRect(0, 0, w, h)); dx = 0.1;//даем скорость.этот объект всегда двигается } } 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; }//по Y вниз=>идем в пол(стоим на месте) или падаем. В этот момент надо вытолкнуть персонажа и поставить его на землю, при этом говорим что мы на земле тем самым снова можем прыгать if (Dy<0){ y = i * 32 + 32; }//столкновение с верхними краями карты(может и не пригодиться) if (Dx>0){ x = j * 32 - w; dx = -0.1; sprite.scale(-1, 1); }//с правым краем карты if (Dx<0){ x = j * 32 + 32; 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; } } } }; |
Многое похоже, а местами и тоже самое. Смотрите, в конструкторе я даю начальную скорость врагу с именем “EasyEnemy”, это будет тупой враг, который меняет своё направление влево-вправо.
Реализация того самого влево-вправо может быть разной. Сейчас я сделал так, что враг изменяет свое направление , когда столкнется со стеной. Это видно в функции checkCollisionWithMap – при столкновении с правой стеной он едет влево, с левой – едет вправо. А sprint,scale(-1,1) это отражение спрайта по горизонтали в момент столкновения со стеной. Эту функцию можно использовать в таких случаях (то есть когда она 1 раз выполняется при определенном условии). Но если напишете её в update – спрайт будет влево вправо быстро танцевать, а значит эта функция тут не подойдет. Мы знаем из первых уроков, что отражать спрайт можно при помощи setTextureRect.
Ну а в функции update мы реализуем (пока что простую) логику для врага. Кстати вы видите там в комментарии есть второй вариант реализации движения влево-вправо врага , а именно по таймеру. То есть он будет менять свое направление через заданный промежуток времени. Можно делать и так тоже.
Все, теперь у нас есть три класса и когда мы создадим, например, пули или ещё что – они так же будут дочерними от класса Entity.
Давайте загрузим изображение для нашего врага:
Закинем наши новые два изображения в ту же папку images , что и ранее.
теперь в функции int main до цикла пока открыто окно, напишем:
1 2 |
Image heroImage; heroImage.loadFromFile("images/MilesTailsPrower.gif"); |
1 2 3 |
Image easyEnemyImage; easyEnemyImage.loadFromFile("images/shamaich.png"); easyEnemyImage.createMaskFromColor(Color(255, 0, 0));//сделали маску по цвету.но лучше изначально иметь прозрачную картинку. как найду комп с фотошопом - исправлю спрайт шамаича:) |
Мы загрузили два изображения. Теперь необходимо их передать в конструктор класса по ссылке. Если передать по значению, то в памяти выделится место под ещё два таких изображения.
Сразу ниже создадим игрока и врага:
1 2 |
Player p(heroImage, 750, 500,40,30,"Player1");//объект класса игрока Enemy easyEnemy(easyEnemyImage, 850, 671,200,97,"EasyEnemy");//простой враг, объект класса врага |
Как видим закидывается изображение в конструктор, указываются координаты, ширина, высота будущего спрайта и имя объекта. Как обычно вызовем функцию update , только уже для врага:
1 2 3 4 5 6 7 8 9 10 |
Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); } p.update(time);// Player update function easyEnemy.update(time);//easyEnemy update function window.setView(view); window.clear(); |
И осталось только нарисовать наши объекты:
1 2 |
window.draw(easyEnemy.sprite); window.draw(p.sprite); |
Я изменил карту в map.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 |
sf::String TileMap[HEIGHT_MAP] = { "0000000000000000000000000000000000000000", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 s 0000 0", "0 000000000000 0", "0 000000000000 0", "0 f h 000000000000 0", "0000000000000000000000000000000000000000", }; |
Более подходит для нашего полигона испытаний.
Результат работы программы:
Мы знаем, что врагов может быть много, что же нам, каждый раз создавать новый объект в int main еще и с разным именем? Враги должны быть динамическими, об этом мы поговорим на следующих уроках. Взаимодействие с врагом так же рассмотрим на следующих уроках.
Вопросы на форум (В соответствующий раздел по sfml – Simple and fast multimedia
Кстати, можете раскидать классы по разным файлам
Код урока:
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 |
#include <SFML/Graphics.hpp> #include "map.h" #include "view.h" #include <iostream> #include <sstream> #include "mission.h" using namespace sf; ////////////////////////////////////Общий класс родитель////////////////////////// class Entity { public: float dx, dy, x, y, speed,moveTimer;//добавили переменную таймер для будущих целей int w,h,health; bool life, isMove, onGround; Texture texture; Sprite sprite; String name;//враги могут быть разные, мы не будем делать другой класс для различающегося врага.всего лишь различим врагов по имени и дадим каждому свое действие в update в зависимости от имени Entity(Image &image, float X, float Y,int W,int H,String Name){ 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); } }; ////////////////////////////////////////////////////КЛАСС ИГРОКА//////////////////////// class Player :public Entity { public: enum { left, right, up, down, jump, stay } state;//добавляем тип перечисления - состояние объекта int playerScore;//эта переменная может быть только у игрока Player(Image &image, float X, float Y,int W,int H,String Name):Entity(image,X,Y,W,H,Name){ playerScore = 0; state = stay; 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 = 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; }//надо убрать т.к мы можем находиться и на другой поверхности или платформе которую разрушит враг } } 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);//обрабатываем столкновение по Y sprite.setPosition(x + w / 2, y + h / 2); //задаем позицию спрайта в место его центра if (health <= 0){ life = false; } if (!isMove){ speed = 0; } //if (!onGround) { dy = dy + 0.0015*time; }//убираем и будем всегда притягивать к земле if (life) { setPlayerCoordinateForView(x, y); } dy = dy + 0.0015*time;//постоянно притягиваемся к земле } }; class Enemy :public Entity{ public: Enemy(Image &image, float X, float Y,int W,int H,String Name):Entity(image,X,Y,W,H,Name){ if (name == "EasyEnemy"){ sprite.setTextureRect(IntRect(0, 0, w, h)); dx = 0.1;//даем скорость.этот объект всегда двигается } } 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; }//по Y вниз=>идем в пол(стоим на месте) или падаем. В этот момент надо вытолкнуть персонажа и поставить его на землю, при этом говорим что мы на земле тем самым снова можем прыгать if (Dy<0){ y = i * 32 + 32; }//столкновение с верхними краями карты(может и не пригодиться) if (Dx>0){ x = j * 32 - w; dx = -0.1; sprite.scale(-1, 1); }//с правым краем карты if (Dx<0){ x = j * 32 + 32; 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; } } } }; int main() { RenderWindow window(VideoMode(640, 480), "Lesson 21. kychka-pc.ru"); view.reset(FloatRect(0, 0, 640, 480)); Image map_image; map_image.loadFromFile("images/map.png"); Texture map; map.loadFromImage(map_image); Sprite s_map; s_map.setTexture(map); Image heroImage; heroImage.loadFromFile("images/MilesTailsPrower.gif"); Image easyEnemyImage; easyEnemyImage.loadFromFile("images/shamaich.png"); easyEnemyImage.createMaskFromColor(Color(255, 0, 0));//сделали маску по цвету.но лучше изначально иметь прозрачную картинку Player p(heroImage, 750, 500,40,30,"Player1");//объект класса игрока Enemy easyEnemy(easyEnemyImage, 850, 671,200,97,"EasyEnemy");//простой враг, объект класса врага 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(); } p.update(time);// Player update function easyEnemy.update(time);//easyEnemy update function 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(easyEnemy.sprite); window.draw(p.sprite); window.display(); } return 0; } |
map.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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
#include <SFML/Graphics.hpp> #include <iostream> const int HEIGHT_MAP = 25; const int WIDTH_MAP = 40; sf::String TileMap[HEIGHT_MAP] = { "0000000000000000000000000000000000000000", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 0", "0 s 0000 0", "0 000000000000 0", "0 000000000000 0", "0 f h 000000000000 0", "0000000000000000000000000000000000000000", }; void randomMapGenerate(){//рандомно расставляем камни int randomElementX = 0;//случайный элемент по горизонтали int randomElementY = 0;//случ эл-т по вертикали srand(time(0));//рандом int countStone = 1;//количество камней 1 while (countStone > 0){ randomElementX = 1 + rand() % (WIDTH_MAP - 1);//рандомное по иксу от 1 до ширина карты-1, чтобы не получать числа бордюра карты randomElementY = 1 + rand() % (HEIGHT_MAP - 1);//по игреку так же if (TileMap[randomElementY][randomElementX] == ' ') {//если встретили символ пробел, TileMap[randomElementY][randomElementX] = 's'; //то ставим туда камень. std::cout << "coordinate of Stone X:" << randomElementX << "\n" << "coordinate of Stone Y:" << randomElementY << "\n\n"; countStone--;//создали камень=>счетчик камней будет "текущий минус 1" } } } |
Павел здравствуйте. Подскажите пожалуйста. Если персонаж приседает, чтобы пройти под препятствием. Как сделать чтобы размер его прямоугольника уменьшился и как изменить столкновения с препятствием, если он вдруг встанет под препятствиями?
Здравствуйте, на setPlayerCoordinateForView пишет, что идентификатор не определен, подскажите в чем может быть причина?
Вложение:
в библиотеке view.h измени название функции getplayercoordinatefoeview на setplayercoordinateforview.