Работа с SFML на примере 2d- игры-платформера



Начинаем писать нашу первую простую графическую игру 2d-платформер, где персонаж находится на какой-то карте, прыгает и т.п. На этом примере можно понять многие принципы. Благодаря высокоуровневости библиотеки SFML, мы можем не загоняться о лишних строках кода и сосредоточиться чисто на алгоритме работы.

Для начала просто выведем круг. Можно сделать это так:

...

#include <SFML/Graphics.hpp> // подключение специальной библиотеки SFML

using namespace sf; // пространство имён sf чтобы лишний раз не писать везде

 

int main() {

RenderWindow window(sf::VideoMode(200, 200), "Test!"); // создаём окно 200 на 200 с именем Test!

CircleShape MrrowCircle(100); // создаём круг и называем его MrrowCircle, радиус равен 100

           MrrowCircle.setFillColor(Color::Green); // закрашиваем этот круг в зелёный цвет

  

    // главный цикл всего процесса

           while(window.isOpen()) { // пока окно открыто

                    Event event; // создаём некое событие

                        

                    // если событие заканчивается, то окошко закрывается

                    while(window.pollEvent(event)) {

                                    if(event.type == Event::Closed) window.close();

                           }

                               

                           window.clear(); // очистка экрана

                           window.draw(MrrowCircle); // рисуем круг

                           window.display(); // выводим круг на экран

                        

           }

 

return 0;

}

...

Советую тебе от руки написать этот код, чтобы лучше понять. Далее мы можем использовать уже чисто эту заготовку и вставлять в неё то что нам нужно. Можно попробовать поменять радиус круга и изменить его цвет. Ты заметишь, что круг появляется с левого верхнего угла- там, очевидно, и начинается система координат.

...

Теперь посмотрим как загружать картинки. Мы используем 3 элемента: Rect (прямоугольник), Texture (сама картинка) и Sprite (объединение картинки в прямоугольник- именно так представляются персонажи в игре)

RenderWindow window(sf::VideoMode(200, 200), "Test!"); // создаём окно 200 на 200 с именем Test!

//

Texture t; // создаём текстуру

t.loadFromFile("fang.png"); // загружаем картинку- помещаем её в папку с программой и всеми файлами

//

Sprite s; // cоздаём спрайт

s.setTexture(t); // помещаем в него нашу текстуру

s.setPosition(50,100); // указываем в какую координату будем рисовать, куда помещаем 

//

Далее поменять: window.draw(s); // рисуем уже спрайт

Картинку ты можешь скачать по ссылке тут: https://yadi.sk/d/dj0sHQi63Mm45U

...

Теперь научимся загружать только часть картинки. Для этого пишем:

Sprite s;

s.setTexture(t);

s.setTextureRect(IntRect(0,244,40,50)); // указываем в скобках координаты прямоугольника (т.е. мы по сути вырезаем кусочек картинки, помещая его в прямоугольную рамку)

s.setPosition(50,100);

Прямоугольник строится по 2 точкам, это можно будет ещё наблюдать ниже. Координаты можно посмотреть в пэинте. Координата нужной нам картинки из всего спрайт листа равна (0, 244). А сама картинка имеет координаты (40, 50) - т.е. сам кусочек. И мы это указываем.

Формат: IntRect(x,y, ширина, высота)

...

Теперь научимся делать анимацию. Для этого создадим дополнительную переменную:
float currentFrame= 0;

Далее добавим после цикла с window poll event (в цикле открытия окна уже- это главный цикл игры)

currentFrame+= 0.005; // скорость анимации

if(currentFrame>6) currentFrame-=6; // всего у нас 6 кадров, они нарисованы в одной горизонтали

s.setTextureRect(IntRect(40*(int)currentFrame,244,40,50)); // постепенно мы сдвигаем на 40 вправо. Если же достигли конца, то -6 обратно как строкой выше. Получается бесконечная анимация из 6 сменяющихся картинок, как будто человечек ходит

Если мы хотим чтобы человечек двигался только при нажатии правой стрелки на клавиатуре, то мы сделаем так:

if(Keyboard::isKeyPressed(Keyboard::Right)) { // если нажата правая клавиша клавиатуры

           s.move(0.1, 0); // сдвиг спрайта на координату 0.1 по горизонтали и 0 по вертикали

                                               

           currentFrame+= 0.005;

           if(currentFrame>6) currentFrame-=6;

           s.setTextureRect(IntRect(40*(int)currentFrame,244,40,50));     

}

Чтобы научить человечка двигаться ещё и налево, можно сделать зеркальное отображение так:

if(Keyboard::isKeyPressed(Keyboard::Left)) {

           s.move(-0.1, 0);

                                               

           currentFrame+= 0.005;

           if(currentFrame>6) currentFrame-=6;

           s.setTextureRect(IntRect(40*(int)currentFrame+40,244,-40,50));            

}

Тут мы берём координату ширины -40, копируя таким образом картинку справа налево. И начинаем с другого угла, поэтому прибавили 40 в первой координате. И движение уже в другую сторону, поэтому -0.1, как ты мог заметить.

...

Важный момент- наша анимация зависит от скорости нашего процессора, от скорости прорисовки. Т.е. на разных компьютерах скорость персонажа будет разная. Это 0.1 в методе спрайта move сдвиг за 1 такт процессора. Чем быстрее работает процессор, тем быстрее будет прибавляться. Поэтому хорошо бы привязать скорость анимации к времени тика процессора данного компьютера, т.е. приплюсовка currentFrame будет уже индивидуальной.

Главный цикл игры будет выглядеть так:

Clock clock;

while(window.isOpen()) {

                    Event event;

                        

                    float time= clock.getElapsedTime().asMicroseconds(); // взять прошедшее время в микросекундах

                    clock.restart(); // перезагрузка

  

                           // обработка закрытия окна

                    while(window.pollEvent(event)) {

                                    if(event.type == Event::Closed) window.close();

                           }

                                          

                           // если нажата левая клавиша

                           if(Keyboard::isKeyPressed(Keyboard::Left)) {

                                           s.move(-0.0003*time, 0);

                                           currentFrame+= 0.005*time;

                                           if(currentFrame>6) currentFrame-=6;

                                           s.setTextureRect(IntRect(40*(int)currentFrame+40,244,-40,50));            

                           }

                               

// если нажата правая клавиша

                           if(Keyboard::isKeyPressed(Keyboard::Right)) {

                                           s.move(0.0003*time, 0);

                                      currentFrame+= 0.005*time;

                                           if(currentFrame>6) currentFrame-=6;

                                           s.setTextureRect(IntRect(40*(int)currentFrame,244,40,50));     

                           }

                               

                           window.clear(); // очистка экрана

                           window.draw(s); // отрисовка спрайта

                           window.display(); // вывод отрисовки на экран

           }

Тут s.move(0.0003*time, 0); я уменьшил значение, т.к. персонаж летал очень быстро. А если поставить мало, то будет дрыгаться. Тут трудно поймать оптимальную цифру, но можно с этим поиграться. Или вернуть как было.

Либо можно регулировать скорость игры проще- специально разделив посчитанное время на какое-то число.

В итоге у меня так:

Clock clock;

// главный цикл игры

while(window.isOpen()) { // пока окно открыто

    Event event; // создаём некое событие

                        

    float time= clock.getElapsedTime().asMicroseconds();

    clock.restart();

    time/= 1000;

                        

    // если событие заканчивается, то окошко закрывается

    while(window.pollEvent(event)) {

                    if(event.type == Event::Closed) window.close();

           }

               

           // если нажата левая клавиша клавиатуры

           if(Keyboard::isKeyPressed(Keyboard::Left)) {

                           s.move(-0.1*time, 0); // сдвиг спрайта

                           currentFrame+= 0.005*time; // номер текущей картинки

                           if(currentFrame>6) currentFrame-=6; // бесконечнизация перебора картинок

                           s.setTextureRect(IntRect(40*(int)currentFrame+40,244,-40,50)); // выделение из всей картинки именно нужного нам кусочка

           }

                               

           if(Keyboard::isKeyPressed(Keyboard::Right)) {

                           s.move(0.1*time, 0);

                           currentFrame+= 0.005*time;

                           if(currentFrame>6) currentFrame-=6;

                           s.setTextureRect(IntRect(40*(int)currentFrame,244,40,50));     

           }

 

           window.clear(); // очистка экрана

           window.draw(s); // рисуем уже спрайт

           window.display(); // выводим на экран              

}

...

Теперь добавим гравитацию. Создадим специальный класс игрока.

class player{

public:

           float dx,dy; // скорости по горизонтали и вертикали

           FloatRect rect; // координаты

           bool onGround; // на земле ли мы

           Sprite sprite; // сюда отрисовывается картинка

           float currentFrame; // текущий кадр

               

           // конструктор класса

           player(Texture &image) {

                           sprite.setTexture(image); // загрузка картинки

                           rect= FloatRect(0,0,40,50); // вырезание кусочка

                           dx=dy=0; // скорости пока нулевые

                           currentFrame= 0; // кадр тоже нулевой

           }

               

           // изменение координаты картинки в зависимости от времени

           void update(float time) {

                           rect.left+= dx*time; // левая координата

                           if(!onGround) dy+= 0.0005*time; // если не на земле, то падаем с ускорением (ось по игреку идёт сверху вниз)- тут 0.0005 это ускорение

                           rect.top+= dy*time; // верхняя координата

                           onGround= false; // мы не на земле

                           if(rect.top > ground) { // если превысили максимальную глубину падения

                                           rect.top= ground; // всё-таки будем на земле

                                           dy= 0; // больше не падаем

                                           onGround= true; // мы на земле

                           }

                           sprite.setPosition(rect.left,rect.top); // поместить наш спрайт в нужную позицию окошка

           }

               

};

Уровень земли (переменную ground) задаём наверху программы как глобальную переменную: int ground= 150;

Добавим ещё движение влево-вправо:

if(rect.top > ground) {

           rect.top= ground;

           dy= 0;

           onGround= true;

}             

currentFrame+= 0.005*time;

if(currentFrame>6) currentFrame-=6;

if(dx>0) sprite.setTextureRect(IntRect(40*(int)currentFrame,244,40,50));       // вправо

if(dx<0) sprite.setTextureRect(IntRect(40*(int)currentFrame+40,244,-40,50)); // влево

sprite.setPosition(rect.left,rect.top);

dx= 0;

...

В итоге вся наша программа будет выглядеть теперь так:

#include <SFML/Graphics.hpp>

using namespace sf;

int ground= 150;

 

class player{

public:

           float dx,dy; // скорости по горизонтали и вертикали

           FloatRect rect; // координаты

           bool onGround; // на земле ли мы

           Sprite sprite; // сюда отрисовывается картинка

           float currentFrame; // текущий кадр

               

           // конструктор класса

           player(Texture &image) {

                           sprite.setTexture(image); // загрузка картинки

                           rect= FloatRect(0,0,40,50); // вырезание кусочка

                           dx=dy=0; // скорости пока нулевые

                           currentFrame= 0; // кадр тоже нулевой

           }

               

           // изменение координаты картинки в зависимости от времени

           void update(float time) {

                           rect.left+= dx*time; // левая координата

                           if(!onGround) dy+= 0.0005*time; // если не на земле, то падаем с ускорением (ось по игреку идёт сверху вниз)- тут 0.0005 это ускорение

                           rect.top+= dy*time; // верхняя координата

                           onGround= false; // мы не на земле

                           if(rect.top > ground) { // если превысили максимальную глубину падения

                                           rect.top= ground; // всё-таки будем на земле

                                           dy= 0; // больше не падаем

                                           onGround= true; // мы на земле

                           }                             

 

                           currentFrame+= 0.005*time;

                           if(currentFrame>6) currentFrame-=6;

                           if(dx>0) sprite.setTextureRect(IntRect(40*(int)currentFrame,244,40,50));           

                           if(dx<0) sprite.setTextureRect(IntRect(40*(int)currentFrame+40,244,-40,50));  

                               

                           sprite.setPosition(rect.left,rect.top);

                           dx= 0;

           }

               

};

 

int main() {

RenderWindow window(sf::VideoMode(200, 200), "Test!");

           Texture t;

           t.loadFromFile("fang.png");

           float currentFrame= 0;

           player p(t);

           Clock clock;

  

           while(window.isOpen()) {

                    Event event;

                        

                    float time= clock.getElapsedTime().asMicroseconds();

                    clock.restart();

                    time/= 1000;

                        

                    while(window.pollEvent(event)) {

                                    if(event.type == Event::Closed) window.close();

                           }

                               

                           if(Keyboard::isKeyPressed(Keyboard::Left)) p.dx= -0.1;

                           if(Keyboard::isKeyPressed(Keyboard::Right)) p.dx= 0.1;

                               

                           // если нажата клавиша вверх, то- если мы на земле, то даём прыжок (-0.25) и говорим что уже не на земле

                           if(Keyboard::isKeyPressed(Keyboard::Up)) {

                                           if(p.onGround) {

                                                           p.dy= -0.25;

                                                           p.onGround= false;

                                           }

                           }

                               

                           p.update(time); // изменение координаты кусочка картинки в нашем окне

                               

                           window.clear();

                           window.draw(p.sprite);

                           window.display();

           }

  

return 0;

}

...

Теперь пойдём дальше. Загрузка карты. Для этого создадим массив строк, по которому эта карта будет создаваться:

String TileMap[12] = {

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",

"B                           B B",

"B                           B B",

"B                           B B",

"B                           B     B",

"B    0000           BBBB B",

"B                           B B",

"BBB                         B B",

"B         BB           BB B",

"B         BB                 B",

"B B    BB    BB      B",

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",

};

Тут каждый символ обозначает своё. Например, В- это стенка. Каждая ячейка карты будет размером, например, 32 на 32. Сделаем эти ячейки как закрашенные прямоугольники.

Новая версия кода:

#include <SFML/Graphics.hpp>

using namespace sf;

int ground= 400;

 

class player{

public:

           float dx,dy;

           FloatRect rect;

           bool onGround;

           Sprite sprite;

           float currentFrame;

               

           player(Texture &image) {

                           sprite.setTexture(image);

                           rect= FloatRect(7*32,9*32,40,50);

                           dx=dy=0.1;

                           currentFrame= 0;

           }

               

           void update(float time) {

                           rect.left+= dx*time;

                           if(!onGround) dy+= 0.0005*time;

                           rect.top+= dy*time;

                           onGround= false;

                           if(rect.top > ground) { rect.top= ground; dy= 0; onGround= true; }

                           currentFrame+= 0.005*time;

                           if(currentFrame>6) currentFrame-=6;

                           if(dx>0) sprite.setTextureRect(IntRect(40*(int)currentFrame,244,40,50));           

                           if(dx<0) sprite.setTextureRect(IntRect(40*(int)currentFrame+40,244,-40,50));  

                           sprite.setPosition(rect.left,rect.top);

                           dx= 0;

           }

};

 

 

const int H=12;

const int W=40;

String TileMap[H] = {

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",

"B                           B B",

"B                           B B",

"B                           B B",

"B                           B B",

"B    0000           BBBB B",

"B                           B B",

"BBB                         B B",

"B         BB           BB B",

"B         BB                 B",

"B    B    BB    BB      B",

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",

};

 

int main() {

  RenderWindow window(sf::VideoMode(600, 450), "Test!");

           Texture t;

           t.loadFromFile("fang.png");

           float currentFrame= 0;

           player p(t);

           Clock clock;

 

           RectangleShape rectangle(Vector2f(32,32)); // создаём прямоугольник

  

           while(window.isOpen()) {

                    Event event;                             

                    float time= clock.getElapsedTime().asMicroseconds();

                    clock.restart();

                    time/= 1000;

                        

           while(window.pollEvent(event)) {

                                    if(event.type == Event::Closed) window.close();

                           }

                               

                           if(Keyboard::isKeyPressed(Keyboard::Left)) p.dx= -0.1;

                           if(Keyboard::isKeyPressed(Keyboard::Right)) p.dx= 0.1;

                           if(Keyboard::isKeyPressed(Keyboard::Up)) {

                                           if(p.onGround) { p.dy= -0.25; p.onGround= false; }

                           }

                           p.update(time);

                               

                           window.clear(Color::White); // очищаем экран и делаем его белым

                           for(int i=0; i<H; i++) {

                                           for(int j=0; j<W; j++) {

                                                           if (TileMap[i][j]=='B') rectangle.setFillColor(Color::Black); // если символ В, то на этом месте рисуем чёрный прямоугольник

                                             if (TileMap[i][j]=='0') rectangle.setFillColor(Color::Green);

                                                           if (TileMap[i][j]==' ') continue; // если символ пустой, ничего не красим

                                                           rectangle.setPosition(j*32,i*32); // каждому символу соответствует соответствующая координата, куда мы и рисуем прямоугольник

                                                           window.draw(rectangle); // закрашиваем символ треугольником

                                           }

                           }

                           window.draw(p.sprite);

                           window.display();

           }

  

return 0;

}

...

Но пока что наш персонаж никак не взаимодействует с картой. Добавим обработку столкновений. Столкновение может быть как по иксу (горизонталь), так и по игреку (вертикаль). Можно прописать эти 2 функции.

 

Ниже функция обработки столкновения по иксу в классе игрока:

           void CollisionX() {

                           for(int i=rect.top/32; i<(rect.top+rect.height)/32; i++) {

                                           for(int j= rect.left/32; j<(rect.left+rect.width)/32; j++) {

                                                           if(TileMap[i][j]=='B') {

                                                                           if(dx>0) rect.left= j*32 - rect.width;

                                                                           if(dx<0) rect.left= j*32+32;

                                                           }

                                           }

                           }

           }

Что тут происходит? Мы проходимся по тем ячейкам, где может находиться наш персонаж. В двойном цикле по сути перебираются номера этих ячеек. В индексе i мы смотрим вверх-вниз, а в индексе j вправо-влево. И если какая-то ячейка вдруг оказывается стенкой (Tilemap B) то мы обрабатываем столкновение. Если двигались вправо (dx>0), то тогда нам надо уйти левее на ширину картинки. Если двигались влево, то встаём сразу после стенки (+32). Аналогичную функцию пишем при обработке столкновений по координате Y (т.е. при прыжках и падениях):

void CollisionY() {

                           for(int i=rect.top/32; i<(rect.top+rect.height)/32; i++) {

                                           for(int j= rect.left/32; j<(rect.left+rect.width)/32; j++) {

                                                           if(TileMap[i][j]=='B') {

                                                                           if(dy>0) { rect.top= i*32 - rect.height; dy=0; onGround=true; }

                                                                           if(dy<0) { rect.top= i*32+32; dy=0; }

                                                           }

                                           }

                           }

           }

TileMap я поставил в начало кода, а параметр ground сделал равным 300.

Т.к. функции столкновения по иксу и по игреку похожи, мы можем объединить всё в одну функцию:

           void Collision() {

                           for(int i=rect.top/32; i<(rect.top+rect.height)/32; i++) {

                                           for(int j= rect.left/32; j<(rect.left+rect.width)/32; j++) {

                                                           if(TileMap[i][j]=='B') {

                                                                           if(dx>0) rect.left= j*32 - rect.width;

                                                                           if(dx<0) rect.left= j*32+32;

                                                                           if(dy>0) { rect.top= i*32 - rect.height; dy=0; onGround=true; }

                                                                           if(dy<0) { rect.top= i*32+32; dy=0; }

                                                           }

                                           }

                           }

           }
Но при этом нам важно, чтобы икс и игрек не работали одновременно. Ты можешь попробовать запустить и увидеть, что программа вылетает и даёт ошибку. Поэтому мы сделаем следующий трюк:

void update(float time) {

                           rect.left+= dx*time;

                           Collision(0);

                               

                           if(!onGround) dy+= 0.0005*time;

                           rect.top+= dy*time;

                           onGround= false;

                           Collision(1);

                               

                           currentFrame+= 0.005*time;

                           if(currentFrame>6) currentFrame-=6;

                           if(dx>0) sprite.setTextureRect(IntRect(40*(int)currentFrame,244,40,50));           

                           if(dx<0) sprite.setTextureRect(IntRect(40*(int)currentFrame+40,244,-40,50));  

                           sprite.setPosition(rect.left,rect.top);

                           dx= 0;

           }

               

           void Collision(int dir) {

                           for(int i=rect.top/32; i<(rect.top+rect.height)/32; i++) {

                                           for(int j= rect.left/32; j<(rect.left+rect.width)/32; j++) {

                                                           if(TileMap[i][j]=='B') {

                                                                           if((dx>0)&&(dir==0)) rect.left= j*32 - rect.width;

                                                                           if((dx<0)&&(dir==0)) rect.left= j*32+32;

                                                                           if((dy>0)&&(dir==1)) { rect.top= i*32 - rect.height; dy=0; onGround=true; }

                                                                           if((dy<0)&&(dir==1)) { rect.top= i*32+32; dy=0; }

                                                           }

                                           }

                           }

           }
И проверка на onGround нам уже не нужна, т.к. она прописана в столкновении, поэтому мы её убрали.

Добавим также взаимодействие с зелёными квадратиками- они будут исчезать, если мы их коснёмся.

if(TileMap[i][j]=='0') TileMap[i][j]= ' ';

Добавляем это после проверку на наличие чёрных квадратиков.

При этом увеличу силу прыжка, чтобы допрыгнуть до зелёных квадратиков и проверить:

                           if(Keyboard::isKeyPressed(Keyboard::Up)) {

                                           if(p.onGround) { p.dy= -0.35; p.onGround= false;}

                           }

При этом важно, чтобы персонаж находился в пределах карты, иначе будет ошибка, т.к. мы выйдем за пределы массива. Введём в игру теперь ещё и перемещение по карте.

На самом же деле персонаж остаётся на месте, а карта сама сдвигается в нужную сторону. Создадим переменные для учёта этого в самом верху программы как глобальные переменные:

float offsetX=0, offsetY=0;

Тут у нас будет смещение карты по иксу и по игреку. Вычтем их при прорисовках, а также привяжем к координатам персонажа и при этом вычтем половину ширины или высоты экрана, чтобы персонаж попал в середину.

Итого вся программа у нас будет выглядеть уже так:

#include <SFML/Graphics.hpp> //подключение библиотеки

using namespace sf; // пространство имён этой библиотеки

int ground= 300; // координата пола

float offsetX=0, offsetY=0; // объявление смещений карты

 

// структура самой карты

const int H=12;

const int W=40;

String TileMap[H] = {

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",

"B                           B B",

"B                           B B",

"B                           B B",

"B                           B B",

"B    0000           BBBB B",

"B                           B B",

"BBB                         B B",

"B         BB           BB B",

"B         BB                 B",

"B B    BB    BB      B",

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",

};

 

// класс, в котором мы учитываем персонажа и как он взаимодействует с нашей картой

class player{

public:

           float dx,dy;

           FloatRect rect;

           bool onGround;

           Sprite sprite;

           float currentFrame;

               

           player(Texture &image) {

                           sprite.setTexture(image);

                           rect= FloatRect(7*32,9*32,40,50);

                           dx=dy=0.1;

                           currentFrame= 0;

           }

               

           void update(float time) {

                           rect.left+= dx*time;

                           Collision(0); // учёт возможного столкновения по иксу

                               

                           if(!onGround) dy+= 0.0005*time; // падение вниз с ускорением 0.0005

                           rect.top+= dy*time;

                           onGround= false;

                           Collision(1); // учёт возможного столкновения по игреку

                               

                           currentFrame+= 0.005*time; // перебор рамок-микрокартинок (анимация)

                           if(currentFrame>6) currentFrame-=6; // зацикливание анимации

                           if(dx>0) sprite.setTextureRect(IntRect(40*(int)currentFrame,244,40,50));           

                           if(dx<0) sprite.setTextureRect(IntRect(40*(int)currentFrame+40,244,-40,50));  

                           sprite.setPosition(rect.left-offsetX,rect.top-offsetY); // помещение персонажа в нужную координату

                           dx= 0;

           }

               

           // функция обработки возможного столкновения

           void Collision(int dir) {

                           for(int i=rect.top/32; i<(rect.top+rect.height)/32; i++) {

                                           for(int j= rect.left/32; j<(rect.left+rect.width)/32; j++) {

                                                           // чёрные квадратики (стенки)

                                                           if(TileMap[i][j]=='B') {

                                                                           if((dx>0)&&(dir==0)) rect.left= j*32 - rect.width;

                                                                           if((dx<0)&&(dir==0)) rect.left= j*32+32;

                                                                           if((dy>0)&&(dir==1)) { rect.top= i*32 - rect.height; dy=0; onGround=true; }

                                                                           if((dy<0)&&(dir==1)) { rect.top= i*32+32; dy=0; }

                                                           }

                                                           if(TileMap[i][j]=='0') TileMap[i][j]= ' '; // зелёные квадратики

                                           }

                           }

           }

               

};

 

int main() {

  RenderWindow window(sf::VideoMode(600, 450), "Test!"); // создание окна

           Texture t;

           t.loadFromFile("fang.png"); // загрузка всей большой картинки

           float currentFrame= 0;

           player p(t);

           Clock clock;

 

           RectangleShape rectangle(Vector2f(32,32)); // создание прямоугольника 32 на 32

  

           // главный цикл игры (каждая итерация происходит при каждом новом тике процессора, т.е. быстро-быстро всё перерисовывается за доли секунд)

           while(window.isOpen()) {

                    Event event;                             

                    float time= clock.getElapsedTime().asMicroseconds();

                    clock.restart();

                    time/= 1000; // чтобы персонаж был сдержанным, а не улетал со сверхскоростью из-за мощности нашего компьютера

                        

                           // обработка закрытия окна

           while(window.pollEvent(event)) {

                                    if(event.type == Event::Closed) window.close();

                           }

                               

                      // если нажаты те или иные клавиши, задаём персонажу скорость

                           if(Keyboard::isKeyPressed(Keyboard::Left)) p.dx= -0.1;

                           if(Keyboard::isKeyPressed(Keyboard::Right)) p.dx= 0.1;

 

                           // если нажали вверх, то оторвались от земли (onGround false) и дали скорость противоположную оси игрек (она направлена сверху вниз)

                           if(Keyboard::isKeyPressed(Keyboard::Up)) {

                                           if(p.onGround) { p.dy= -0.35; p.onGround= false;}

                           }

                               

                           // обновить местонахождение персонажа в соответствие с прошедшим временем

                           p.update(time);

                               

                           // смещения карты (как бы камеры на персонаже) чтобы персонаж оставался в центре окна

                           offsetX= p.rect.left - 600/2;

                           offsetY= p.rect.top - 450/2;

                               

                           // очистка экрана в белый цвет и последующая отрисовка карты с учётом смещения

                           window.clear(Color::White);

                           for(int i=0; i<H; i++) {

                                           for(int j=0; j<W; j++) {

                                                           if (TileMap[i][j]=='B') rectangle.setFillColor(Color::Black);

                                             if (TileMap[i][j]=='0') rectangle.setFillColor(Color::Green);

                                                           if (TileMap[i][j]==' ') continue;

                                                           rectangle.setPosition(j*32-offsetX,i*32-offsetY);

                                                           window.draw(rectangle);

                                           }

                           }

                           // нарисовать теперь самого персонажа на карте

                           window.draw(p.sprite);

                               

                           // вывести всё на экран

                           window.display();

           }

  

return 0;

}

Доработаем теперь- если персонаж подходит к краю карты, у нас тоже происходит смещение как обычно- обычно же в играх такого не происходит. Поэтому в таких случаях мы смещение уберём.

if(p.rect.left>300) offsetX= p.rect.left - 300;

if(p.rect.top>225) offsetY= p.rect.top - 225;

Ну и далее можно только поменять высоту окна, сделать какие-то иные доработки- но ключевые вещи для подобных игр мы уже разобрали. Игровой цикл, изменение координат, обработка столкновений и т.п.

...

Ниже я представлю уже конечный код игры. Тут я убрал смещение карты по игреку и изменил размеры окна.

#include <SFML/Graphics.hpp>

using namespace sf;

int ground= 300; float offsetX=0;

 

const int H=12, W=40;

String TileMap[H] = {

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",

"B                           B B",

"B                           B B",

"B                           B B",

"B                           B B",

"B    0000           BBBB B",

"B                           B B",

"BBB                         B B",

"B         BB           BB B",

"B         BB                 B",

"B B    BB    BB      B",

"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",

};

 

class player{

public:

           float dx,dy; FloatRect rect; bool onGround; Sprite sprite; float currentFrame;

               

           player(Texture &image) {

                           sprite.setTexture(image);

                           rect= FloatRect(7*32,9*32,40,50);

                           dx=dy=0.1; currentFrame= 0;

           }

               

           void Collision(int dir) {

                           for(int i=rect.top/32; i<(rect.top+rect.height)/32; i++) {

                                           for(int j= rect.left/32; j<(rect.left+rect.width)/32; j++) {

                                                           if(TileMap[i][j]=='B') {

                                                                           if((dx>0)&&(dir==0)) rect.left= j*32 - rect.width;

                                                                           if((dx<0)&&(dir==0)) rect.left= j*32+32;

                                                                           if((dy>0)&&(dir==1)) { rect.top= i*32 - rect.height; dy=0; onGround=true; }

                                                                      if((dy<0)&&(dir==1)) { rect.top= i*32+32; dy=0; }

                                                           }

                                                           if(TileMap[i][j]=='0') TileMap[i][j]= ' ';

                                           }

                           }

           }

               

           void update(float time) {

                           rect.left+= dx*time;

                           Collision(0);

                               

                           if(!onGround) dy+= 0.0005*time;

                           rect.top+= dy*time;

                           onGround= false;

                           Collision(1);

                               

                           currentFrame+= 0.005*time;

                           if(currentFrame>6) currentFrame-=6;

                           if(dx>0) sprite.setTextureRect(IntRect(40*(int)currentFrame,244,40,50));           

                           if(dx<0) sprite.setTextureRect(IntRect(40*(int)currentFrame+40,244,-40,50));

               

                           sprite.setPosition(rect.left-offsetX,rect.top);

                           dx= 0;

           }

};

 

int main() {

           const int wh= 380, ww= 600;

  RenderWindow window(sf::VideoMode(ww, wh), "Test!");

           Texture t;  t.loadFromFile("fang.png");

           float currentFrame= 0;

           player p(t);

           Clock clock;

           RectangleShape rectangle(Vector2f(32,32));

  

           while(window.isOpen()) {

                    Event event;                             

                    float time= clock.getElapsedTime().asMicroseconds();

                    clock.restart();

                           time/= 1000;

                        

           while(window.pollEvent(event)) {

                                    if(event.type == Event::Closed) window.close();

                           }

                               

                           if(Keyboard::isKeyPressed(Keyboard::Left)) p.dx= -0.1;

                           if(Keyboard::isKeyPressed(Keyboard::Right)) p.dx= 0.1;

                           if(Keyboard::isKeyPressed(Keyboard::Up)) {

                                           if(p.onGround) { p.dy= -0.35; p.onGround= false;}

                           }

                           p.update(time);

                           if(p.rect.left>ww/2) offsetX= p.rect.left - ww/2;

                               

                           window.clear(Color::White);

                           for(int i=0; i<H; i++) {

                                           for(int j=0; j<W; j++) {

                                                           if (TileMap[i][j]=='B') rectangle.setFillColor(Color::Black);

                                             if (TileMap[i][j]=='0') rectangle.setFillColor(Color::Green);

                                                           if (TileMap[i][j]==' ') continue;

                                                           rectangle.setPosition(j*32-offsetX,i*32);

                                                           window.draw(rectangle);

                                           }

                           }

                           window.draw(p.sprite);

                           window.display();

           }

}

 


Дата добавления: 2018-09-22; просмотров: 2497; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!