Работа с библиотекой на примере 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; просмотров: 258; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!