Настало время перебраться к концепции ООП, потому что городить дальше кучи строк кода станет утомительным занятием На этом уроке перепишем код под ООП, уберем движение персонажа по диагонали, персонажа льва сделаем прозрачным в одном его “синеватом” месте.
Видеоверсия: sfml урок 8
Итак, представьте, что у нас есть враги, пули, бонусы, игрок и все это взаимодействует между собой. Чокнемся , если это будет структурно описано.
Сначала сделаем класс Игрока, поскольку он у нас в игре и существует пока что.
До функции int main пишем
1 2 3 4 5 6 7 8 9 |
////////////////////////////////////////////////////КЛАСС ИГРОКА//////////////////////// class Player { // класс Игрока public: float x, y, w, h, dx, dy, speed ; //координаты игрока х и у, высота ширина, ускорение (по х и по у), сама скорость int dir ; //направление (direction) движения игрока String File; //файл с расширением Image image;//сфмл изображение Texture texture;//сфмл текстура Sprite sprite;//сфмл спрайт |
В данном случае создали класс Player и задали ему свойства. Они публичны (public) потому, что этот класс мы потом преобразуем в родительский и будем его наследовать, а так же обращаться оператором точка в функции main. У всех – врагов, пулей, игрока и тд есть много общего – текстура, спрайт, изображение, жизни, скорость и чего только вы не пожелаете, поэтому лучше будет создать главный класс, обладающий общими свойствами и методами и наследовать эти же свойства и методы для других классов, которым эти свойства и методы тоже нужны. Это поможет избежать лишних строк в коде и добавит удобства в работе. Такой класс и наследование мы потом когда-нибудь запилим.
После свойств идет конструктор. В нем задаем значения всем нашим переменным. В нашем случае он с параметрами, которые мы будем передавать при создании объекта:
1 2 3 4 5 6 7 8 9 10 11 |
Player(String F, float X, float Y, float W, float H){ //Конструктор с параметрами(формальными) для класса Player. При создании объекта класса мы будем задавать имя файла, координату Х и У, ширину и высоту dx=0;dy=0;speed=0;dir=0; File = F;//имя файла+расширение w = W; h = H;//высота и ширина image.loadFromFile("images/" + File);//запихиваем в image наше изображение вместо File мы передадим то, что пропишем при создании объекта. В нашем случае "hero.png" и получится запись идентичная image.loadFromFile("images/hero/png"); image.createMaskFromColor(Color(41, 33, 59));//убираем ненужный темно-синий цвет, эта тень мне показалась не красивой. texture.loadFromImage(image);//закидываем наше изображение в текстуру sprite.setTexture(texture);//заливаем спрайт текстурой x = X; y = Y;//координата появления спрайта sprite.setTextureRect(IntRect(0, 0, w, h)); //Задаем спрайту один прямоугольник для вывода одного льва, а не кучи львов сразу. IntRect - приведение типов } |
Давайте представим ситуацию и разберем как это работает. В ф-ции main написано (пока не пишите):
Player p(“hero.png”,250,250,96.0, 96.0);
Получается , что так мы создали объект класса Player, картинка для него берется по адресу images/hero.png , координаты x и y у нас 250,а ширина и высота изображения 96. Всё примерно то же самое, что и в прошлых уроках, только другими способами
Далее для тех у кого картинка тоже лев – объяснение строки image.createMaskFromColor(Color(41, 33, 59)) в конце урока, пока сосредоточьтесь на другом
После конструктора создадим метод update, который оживит нашего игрока с помощью времени SFML. в конце закроем скобку класса и на этом с классом пока всё.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void update(float time) //функция "оживления" объекта класса. update - обновление. принимает в себя время SFML , вследствие чего работает бесконечно, давая персонажу движение. { switch (dir)//реализуем поведение в зависимости от направления. (каждая цифра соответствует направлению) { case 0: dx = speed; dy = 0; break;//по иксу задаем положительную скорость, по игреку зануляем. получаем, что персонаж идет только вправо case 1: dx = -speed; dy = 0; break;//по иксу задаем отрицательную скорость, по игреку зануляем. получается, что персонаж идет только влево case 2: dx = 0; dy = speed; break;//по иксу задаем нулевое значение, по игреку положительное. получается, что персонаж идет только вниз case 3: dx = 0; dy = -speed; break;//по иксу задаем нулевое значение, по игреку отрицательное. получается, что персонаж идет только вверх } x += dx*time;//то движение из прошлого урока. наше ускорение на время получаем смещение координат и как следствие движение y += dy*time;//аналогично по игреку speed = 0;//зануляем скорость, чтобы персонаж остановился. sprite.setPosition(x,y); //выводим спрайт в позицию x y , посередине. бесконечно выводим в этой функции, иначе бы наш спрайт стоял на месте. } }; |
Чуть подробнее о листинге выше. У нас четыре направления движения. Когда мы нажимаем клавишу мы изменяем dir и таким образом изменяем направление. Если идем влево, то dir=1, по иксу получаем отрицательное значение ( как видим speed в этом случае <0), по игреку 0, таким образом не сможем идти по диагонали. Аналогично другим случаям. Ну а в конце мы пишем x+=dx*time – оживляем это движение с помощью времени sfml, которое мы приняли в качестве параметра для этого метода update. То же самое делаем с игреком – y+=…
Чуть ниже обнуляем скорость. Если вы этого не сделаете – ваш персонаж будет двигаться как старая добрая Змейка. )) попробуйте потом в конце урока для интереса не обнулять. Если вам нужен именно такой персонаж “без тормозов” – оставляйте
Итак, чтобы все это заработало – нам необходимо создать объект класса игрока в функции main. давайте сделаем это:
1 |
Player p("hero.png", 250, 250, 96.0, 96.0);//создаем объект p класса player,задаем "hero.png" как имя файла+расширение, далее координата Х,У, ширина, высота. |
Уже сейчас мы можем, (но не будем:-)) сделать двух игроков. Нам нужно два объекта, можем задать им разные картинки, задать разные координаты появления. Пока что они могут пересекаться.
Как видите мы подгружаем файл в процессе создания объекта. Это не прокатит, если у вас объект пуля, который постоянно надо удалять и создавать заново. В последствии переделаем на другой лад, когда будем делать класс анимации и другие объекты (особенно динамические).
Заменим старое управление персонажем на новое:
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 |
///////////////////////////////////////////Управление персонажем с анимацией//////////////////////////////////////////////////////////////////////// if ((Keyboard::isKeyPressed(Keyboard::Left) || (Keyboard::isKeyPressed(Keyboard::A)))) { p.dir = 1; p.speed = 0.1;//dir =1 - направление вверх, speed =0.1 - скорость движения. Заметьте - время мы уже здесь ни на что не умножаем и нигде не используем каждый раз CurrentFrame += 0.005*time; if (CurrentFrame > 3) CurrentFrame -= 3; p.sprite.setTextureRect(IntRect(96 * int(CurrentFrame), 96, 96, 96)); //через объект p класса player меняем спрайт, делая анимацию (используя оператор точку) } if ((Keyboard::isKeyPressed(Keyboard::Right) || (Keyboard::isKeyPressed(Keyboard::D)))) { p.dir = 0; p.speed = 0.1;//направление вправо, см выше CurrentFrame += 0.005*time; if (CurrentFrame > 3) CurrentFrame -= 3; p.sprite.setTextureRect(IntRect(96 * int(CurrentFrame), 192, 96, 96)); //через объект p класса player меняем спрайт, делая анимацию (используя оператор точку) } if ((Keyboard::isKeyPressed(Keyboard::Up) || (Keyboard::isKeyPressed(Keyboard::W)))) { p.dir = 3; p.speed = 0.1;//направление вниз, см выше CurrentFrame += 0.005*time; if (CurrentFrame > 3) CurrentFrame -= 3; p.sprite.setTextureRect(IntRect(96 * int(CurrentFrame), 288, 96, 96)); //через объект p класса player меняем спрайт, делая анимацию (используя оператор точку) } if ((Keyboard::isKeyPressed(Keyboard::Down) || (Keyboard::isKeyPressed(Keyboard::S)))) { //если нажата клавиша стрелка влево или англ буква А p.dir = 2; p.speed = 0.1;//направление вверх, см выше CurrentFrame += 0.005*time; //служит для прохождения по "кадрам". переменная доходит до трех суммируя произведение времени и скорости. изменив 0.005 можно изменить скорость анимации if (CurrentFrame > 3) CurrentFrame -= 3; //проходимся по кадрам с первого по третий включительно. если пришли к третьему кадру - откидываемся назад. p.sprite.setTextureRect(IntRect(96 * int(CurrentFrame), 0, 96, 96)); //проходимся по координатам Х. получается 96,96*2,96*3 и опять 96 } |
Обращаемся к свойствам объекта – скорости и направлению, тем самым реализуя движение. Движется герой в четырех направлениях будто на него смотрят сверху (как в танчиках на денди, например). Для реализации в стиле марио нам нужно будет убрать одно направление (вниз) и реализовать силу притяжения. Будет урок, в котором я расскажу как это сделать.
Ну а пока ниже пишем:
1 |
p.update(time);//оживляем объект p класса Player с помощью времени sfml, передавая время в качестве параметра функции update. благодаря этому персонаж может двигаться |
И последнее – меняем draw и (кстати удаляем старое spritehero и вобще все, что с ним связано):
1 |
window.draw(p.sprite);//рисуем спрайт объекта p класса player |
Мы перешли на ООП , осталась анимация и отдельный её класс. Это тема для отдельной статьи, поэтому анимацию здесь я не трогал. С анимацией не все так просто, поэтому пока поживем с тем, что имеем и затронем другие темы.
Так же стоит отметить публичный модификатор public в нашем классе и обращение к свойствам класса в функции main. Хорошим тоном является как можно меньше публичных модификаторов доступа (который public) к полям (в нашем случае можем называть их свойствами) . Обращаться напрямую извне класса к свойству объекта (полю) мы не должны, для этого необходимо описывать метод, способствующий изменению какого-то свойства класса, тем самым мы реализуем инкапсуляцию (гуглить ООП) . В нашем случае мы обратились – меняли скорость (p.speed=0.1), направление движения (p.dir=1). Далее я буду постепенно уходить от этого, но без фанатизма, поскольку наша главная задача – понять суть работы с sfml. В конце концов у нас не большой проект и команда разработчиков – мы только учимся. Всякие там красивости и правильные вещи вы наведете (или не наведете) по своему желанию, когда будете делать свою полноценную игру.
На следующем уроке загрузим карту, а то бегать в темноте уже становится страшно
А теперь объяснение строки createMaskFromColor из начала статьи, где мы писали свойства объекта класса. Мне не понравился вывод игрока с его тенью, как – то не очень выглядит и я решил убрать эту тень. Как узнать код цвета в paint’e (паинте) ? Открыл картинку паинтом, выбрал пипеткой этот цвет тени под его пузом, потом нажал “изменение цветов” и справа смотрел код цвета (красн 41, зел 33, син 59) , добавил этот код как вы видели выше в маску цветов спрайта: “image.createMaskFromColor(Color(41, 33, 59));”
Ниже картинка как я это сделал по шагам куда тыкал:
Код урока:
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 |
///////////////////////Урок 8//////////////////////////////////// #include <iostream> #include <SFML/Graphics.hpp> using namespace sf; ////////////////////////////////////////////////////КЛАСС ИГРОКА//////////////////////// class Player { // класс Игрока public: float x, y, w, h, dx, dy, speed = 0; //координаты игрока х и у, высота ширина, ускорение (по х и по у), сама скорость int dir = 0; //направление (direction) движения игрока String File; //файл с расширением Image image;//сфмл изображение Texture texture;//сфмл текстура Sprite sprite;//сфмл спрайт Player(String F, float X, float Y, float W, float H){ //Конструктор с параметрами(формальными) для класса Player. При создании объекта класса мы будем задавать имя файла, координату Х и У, ширину и высоту File = F;//имя файла+расширение w = W; h = H;//высота и ширина image.loadFromFile("images/" + File);//запихиваем в image наше изображение вместо File мы передадим то, что пропишем при создании объекта. В нашем случае "hero.png" и получится запись идентичная image.loadFromFile("images/hero/png"); image.createMaskFromColor(Color(41, 33, 59));//убираем ненужный темно-синий цвет, эта тень мне показалась не красивой. texture.loadFromImage(image);//закидываем наше изображение в текстуру sprite.setTexture(texture);//заливаем спрайт текстурой x = X; y = Y;//координата появления спрайта sprite.setTextureRect(IntRect(0, 0, w, h)); //Задаем спрайту один прямоугольник для вывода одного льва, а не кучи львов сразу. IntRect - приведение типов } void update(float time) //функция "оживления" объекта класса. update - обновление. принимает в себя время SFML , вследствие чего работает бесконечно, давая персонажу движение. { switch (dir)//реализуем поведение в зависимости от направления. (каждая цифра соответствует направлению) { case 0: dx = speed; dy = 0; break;//по иксу задаем положительную скорость, по игреку зануляем. получаем, что персонаж идет только вправо case 1: dx = -speed; dy = 0; break;//по иксу задаем отрицательную скорость, по игреку зануляем. получается, что персонаж идет только влево case 2: dx = 0; dy = speed; break;//по иксу задаем нулевое значение, по игреку положительное. получается, что персонаж идет только вниз case 3: dx = 0; dy = -speed; break;//по иксу задаем нулевое значение, по игреку отрицательное. получается, что персонаж идет только вверх } x += dx*time;//то движение из прошлого урока. наше ускорение на время получаем смещение координат и как следствие движение y += dy*time;//аналогично по игреку speed = 0;//зануляем скорость, чтобы персонаж остановился. sprite.setPosition(x,y); //выводим спрайт в позицию x y , посередине. бесконечно выводим в этой функции, иначе бы наш спрайт стоял на месте. } }; int main() { RenderWindow window(sf::VideoMode(640, 480), "Lesson 8. kychka-pc.ru"); float CurrentFrame = 0;//хранит текущий кадр Clock clock; Player p("hero.png",250,250,96.0, 96.0);//создаем объект p класса player,задаем "hero.png" как имя файла+расширение, далее координата Х,У, ширина, высота. while (window.isOpen()) { float time = clock.getElapsedTime().asMicroseconds(); clock.restart(); time = time / 800; sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); } ///////////////////////////////////////////Управление персонажем с анимацией//////////////////////////////////////////////////////////////////////// if ((Keyboard::isKeyPressed(Keyboard::Left) || (Keyboard::isKeyPressed(Keyboard::A)))) { p.dir = 1; p.speed = 0.1;//dir =1 - направление вверх, speed =0.1 - скорость движения. Заметьте - время мы уже здесь ни на что не умножаем и нигде не используем каждый раз CurrentFrame += 0.005*time; if (CurrentFrame > 3) CurrentFrame -= 3; p.sprite.setTextureRect(IntRect(96 * int(CurrentFrame), 96, 96, 96)); //через объект p класса player меняем спрайт, делая анимацию (используя оператор точку) } if ((Keyboard::isKeyPressed(Keyboard::Right) || (Keyboard::isKeyPressed(Keyboard::D)))) { p.dir = 0; p.speed = 0.1;//направление вправо, см выше CurrentFrame += 0.005*time; if (CurrentFrame > 3) CurrentFrame -= 3; p.sprite.setTextureRect(IntRect(96 * int(CurrentFrame), 192, 96, 96)); //через объект p класса player меняем спрайт, делая анимацию (используя оператор точку) } if ((Keyboard::isKeyPressed(Keyboard::Up) || (Keyboard::isKeyPressed(Keyboard::W)))) { p.dir = 3; p.speed = 0.1;//направление вниз, см выше CurrentFrame += 0.005*time; if (CurrentFrame > 3) CurrentFrame -= 3; p.sprite.setTextureRect(IntRect(96 * int(CurrentFrame), 288, 96, 96)); //через объект p класса player меняем спрайт, делая анимацию (используя оператор точку) } if ((Keyboard::isKeyPressed(Keyboard::Down) || (Keyboard::isKeyPressed(Keyboard::S)))) { //если нажата клавиша стрелка влево или англ буква А p.dir = 2; p.speed = 0.1;//направление вверх, см выше CurrentFrame += 0.005*time; //служит для прохождения по "кадрам". переменная доходит до трех суммируя произведение времени и скорости. изменив 0.005 можно изменить скорость анимации if (CurrentFrame > 3) CurrentFrame -= 3; //проходимся по кадрам с первого по третий включительно. если пришли к третьему кадру - откидываемся назад. p.sprite.setTextureRect(IntRect(96 * int(CurrentFrame), 0, 96, 96)); //проходимся по координатам Х. получается 96,96*2,96*3 и опять 96 } p.update(time);//оживляем объект p класса Player с помощью времени sfml, передавая время в качестве параметра функции update. благодаря этому персонаж может двигаться window.clear(); window.draw(p.sprite);//рисуем спрайт объекта p класса player window.display(); } return 0; } |
Автор спасибо!
Здравствуйте, подскажите почему происходит эта ошибка и как ее исправить? (использование инициализатора элемента данных не допускается)
Привет, отправьте сообщение на форум в раздел разное сфмл(другие вопросы) и туда приложите код в тег code. И скрин не работает =(
вот скрин
вставил код программы из урока, героя почему то не выводит
я почитал комментарий который вы уже писали к одному из пользователю с такой же проблемой, но не понял что надо поменять
вставил код следующего урока все сработало. Проблем нет так что)
Выдает такую ошибку и выводит вместо льва белый квадрат
До этого это решалось указанием полного пути, но теперь и это не помогает
Может, подскажете, как решить?
Вложение:
До этого решалось, а что изменилось?) Код настоялся?
Попробуйте загрузить картинку в текстуру без параметра конструктора, то есть в самом конструкторе напрямую написать путь до картинки. И еще: ” Что за ошибка такая – неверный размер текстуры?”
Попробовал, ничего не изменилось. Сам удивляюсь, не может же ни с того ни с сего такое быть. А ошибка наверное из-за того, что файл не открылся, потому и размер 0х0
Обычно показывает лишь то , что файл не открылся. Приложите код и создайте тему на форуме, попробую у себя ваш код запустить.
Я идиот. Я просто переименовал картинку и забыл. Извините, что потратил ваше время своей невнимательностью
Здравствуйте!
возникла проблема с определением констант..
прилагаю скрин.. заранее спасибо!
Вложение:
Твой компилятор не поддерживает инициализация в свойствах класса , скорее всего. Инициализируй в конструкторе, а тут инициализацию убери. И в следующий раз пиши на форуме
Спасибо, решил проблему, убрав инициализацию дир и спид
хорошо
У меня такая ошибка…помогите!
я переменные speed и dir в конструкторе ввел = 0.
Но теперь такая фигня! – Спрайт карты мне показывает а спрайт игрока – нет! (p.sprite)
я не знаю что делать….
Почему-то у меня персонаж влево и наверх стал быстрее ходить чем вправо и вниз. Почему?