“Я знаю, чем ты занимаешься… почему ты плохо спишь, почему живешь один и почему ночь за ночью сидишь за своим компьютером. Ты ищешь его. Я знаю, потому что я сама искала. И когда он нашел меня, он сказал, что на самом деле я искала не его. Я искала ответ. Этот вопрос ведет нас. Он сводит нас с ума! Этот вопрос привел тебя сюда. Ты знаешь этот вопрос точно так же, как и я… СПРАЙТЫ И ТЕКСТУРЫ… КАК SFML РАБОТАЕТ С НИМИ?”
Спрайты и текстуры
Итак, что же такое текстура, спрайт и чем они отличаются друг от друга.
Текстура — это обычная картинка, которая накладывается на двумерный объект.
Спрайт — это любая геометрическая фигура с нанесенной поверх нее текстурой.
Ниже приведена картинка, демонстрирующая эти концепции, которую я нагло спёр из официальной документации по SFML.
Сложного здесь ничего нет, поэтому двигаемся дальше.
Загрузка текстуры
Прежде чем создавать какой-нибудь спрайт, нам нужна текстура. Класс SFML, который предоставляет возможность работать с текстурами, называется внезапно Texture
. Так как единственной целью текстуры является её загрузка из источника и нанесение на объект, то назначение практически всех функций данного класса сводится в большинстве своем к двум задачам: к загрузке текстуры и к её отображению в программе.
Основным способом загрузки текстуры является считывание информации о ней из графического файла, расположенного на компьютере. Ниже я привел простейший пример создания спрайта и наложения на него текстуры:
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 |
#include <SFML/Graphics.hpp> using namespace sf; int main() { // Объект, который, собственно, является главным окном приложения RenderWindow window(VideoMode(150, 100), "SFML Works!"); // Главный цикл приложения: выполняется, пока открыто окно while (window.isOpen()) { // Обрабатываем события в цикле Event event; while (window.pollEvent(event)) { // Пользователь нажал на «крестик» и хочет закрыть окно? if (event.type == Event::Closed) // тогда закрываем его window.close(); } // Установка цвета фона - белый window.clear(Color::White); // Создаем переменную текстуры Texture texture; // Подгружаем нашу текстуру из файла texture.png texture.loadFromFile("C:\\dev\\SFML_Tutorial\\Debug\\texture.png"); // Создаем спрайт и устанавливаем ему нашу текстуру Sprite sprite(texture); // Отрисовка спрайта window.draw(sprite); // Отрисовка окна window.display(); } return 0; } |
Результат выполнения программы:
Стоит отметить, что библиотека SFML поддерживает работу со всеми основными форматами графических файлов.
Есть еще несколько способов загрузки текстуры:
загрузка файла текстуры из памяти с помощью метода loadFromMemory();
загрузка из потока с помощью метода loadFromStream();
загрузка из файла, который уже был загружен, с помощью метода loadFromImage().
Все эти функции имеют необязательный аргумент, который можно использовать, если вы хотите загрузить лишь часть изображения. Например, следующий код загружает небольшой кусочек текстуры размером 32×32 пикселя, начинающийся с координат (10; 10)
:
1 |
texture.loadFromFile("image.png", IntRect(10, 10, 32, 32)); |
Помимо этого, у текстуры есть два интересных свойства, которые влияют на то, как она будет отображаться на экране. Первое свойство — это сглаживание, которое делает границы пикселей менее видимыми (правда из-за этого изображение становится немного размытым). Например:
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 |
#include <SFML/Graphics.hpp> using namespace sf; int main() { // Объект, который, собственно, является главным окном приложения RenderWindow window(VideoMode(150, 100), "SFML Works!"); // Главный цикл приложения: выполняется, пока открыто окно while (window.isOpen()) { // Обрабатываем события в цикле Event event; while (window.pollEvent(event)) { // Пользователь нажал на «крестик» и хочет закрыть окно? if (event.type == Event::Closed) // тогда закрываем его window.close(); } // Установка цвета фона - белый window.clear(Color::White); // Создаем переменную текстуры Texture texture; // Подгружаем нашу текстуру из файла texture.png texture.loadFromFile("C:\\dev\\SFML_Tutorial\\Debug\\texture.png"); // Включаем режим сглаживания для нашей текстуры texture.setSmooth(true); // Создаем спрайт и устанавливаем ему нашу текстуру Sprite sprite(texture); // Отрисовка спрайта window.draw(sprite); // Отрисовка окна window.display(); } return 0; } |
Особенно полезным это свойство является при масштабировании текстуры. Ниже в прикрепленной гифке вы можете увидеть разницу между вариантом со сглаживанием и без:
Второе интересное свойство позволяет многократно повторять текстуру в пределах одного спрайта. Например:
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 |
#include <SFML/Graphics.hpp> using namespace sf; int main() { // Объект, который, собственно, является главным окном приложения RenderWindow window(VideoMode(500, 300), "SFML Works!"); // Главный цикл приложения: выполняется, пока открыто окно while (window.isOpen()) { // Обрабатываем события в цикле Event event; while (window.pollEvent(event)) { // Пользователь нажал на «крестик» и хочет закрыть окно? if (event.type == Event::Closed) // тогда закрываем его window.close(); } // Установка цвета фона - белый window.clear(Color::White); // Создаем переменную текстуры Texture texture; // Подгружаем нашу текстуру из файла texture.png texture.loadFromFile("C:\\dev\\SFML_Tutorial\\Debug\\texture.png"); // Включаем режим повторения для текстуры texture.setRepeated(true); // Создаем спрайт размером 400х198 и присваиваем ему нашу текстуру (текстура имеет размер 100х100) Sprite sprite(texture, IntRect(0, 0, 400, 198)); // Отрисовка спрайта window.draw(sprite); // Отрисовка окна window.display(); } return 0; } |
Результат выполнения программы:
Стоит отметить, что данный способ работает только тогда, когда размеры спрайта больше размеров текстуры, в противном случае — ничего не получится.
Также можно изменить и цвет самого спрайта, который будет мультипликативно складываться с текущей текстурой. Благодаря этому свойству удобно изменять значение прозрачности спрайта. Например:
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 |
#include <SFML/Graphics.hpp> using namespace sf; int main() { // Объект, который, собственно, является главным окном приложения RenderWindow window(VideoMode(750, 300), "SFML Works!"); // Главный цикл приложения: выполняется, пока открыто окно while (window.isOpen()) { // Обрабатываем события в цикле Event event; while (window.pollEvent(event)) { // Пользователь нажал на «крестик» и хочет закрыть окно? if (event.type == Event::Closed) // тогда закрываем его window.close(); } // Установка цвета фона - белый window.clear(Color::White); // Создаем переменную текстуры Texture texture; // Подгружаем нашу текстуру из файла texture.png texture.loadFromFile("C:\\dev\\SFML_Tutorial\\Debug\\texture.png"); // Создаем 7 спрайтов для примера Sprite sprite1(texture); Sprite sprite2(texture); Sprite sprite3(texture); Sprite sprite4(texture); Sprite sprite5(texture); Sprite sprite6(texture); Sprite sprite7(texture); // Первый спрайт остается на месте в качестве образца // Сдвигаем второй спрайт вправо на 105 пикселей sprite2.move(105, 0); // Устанавливаем ему цвет - зеленый sprite2.setColor(Color::Green); // Сдвигаем третий спрайт вправо на 210 пикселей sprite3.move(210, 0); // Устанавливаем ему цвет - красный sprite3.setColor(Color::Red); // Сдвигаем четвертый спрайт вправо на 315 пикселей sprite4.move(315, 0); // Устанавливаем ему цвет - желтый sprite4.setColor(Color::Yellow); // Сдвигаем пятый спрайт вправо на 420 пикселей sprite5.move(420, 0); // Устанавливаем ему цвет – белый + прозрачность 128 sprite5.setColor(Color(255, 255, 255, 128)); // Сдвигаем шестой спрайт вправо на 525 пикселей sprite6.move(525, 0); // Устанавливаем ему цвет – белый + прозрачность 64 sprite6.setColor(Color(255, 255, 255, 64)); // Сдвигаем седьмой спрайт вправо на 630 пикселей sprite7.move(630, 0); // Устанавливаем ему цвет – белый + прозрачность 32 sprite7.setColor(Color(255, 255, 255, 32)); // Отрисовка всех спрайтов window.draw(sprite1); window.draw(sprite2); window.draw(sprite3); window.draw(sprite4); window.draw(sprite5); window.draw(sprite6); window.draw(sprite7); // Отрисовка окна window.display(); } return 0; } |
Результат выполнения программы:
Трансформации
К спрайтам могут быть применены различные варианты трансформаций: изменение позиции, изменение ориентации в пространстве и масштабирование. Все они выполняются с помощью методов setPosition()/move(), setRotation()/rotate() и setScale()/scale(), соответственно. Вы можете спросить: «А почему данные методы написаны через слэш?». А дело в том, что первые методы в каждой паре производят абсолютные преобразования (преобразования относительно начала координат). Вторые же методы в каждой паре производят преобразования относительно текущих значений координат/угла/масштаба. Например:
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 |
#include <SFML/Graphics.hpp> using namespace sf; int main() { // Объект, который, собственно, является главным окном приложения RenderWindow window(VideoMode(600, 300), "SFML Works!"); // Главный цикл приложения: выполняется, пока открыто окно while (window.isOpen()) { // Обрабатываем события в цикле Event event; while (window.pollEvent(event)) { // Пользователь нажал на «крестик» и хочет закрыть окно? if (event.type == Event::Closed) // тогда закрываем его window.close(); } // Установка цвета фона - белый window.clear(Color::White); // Создаем переменную текстуры Texture texture; // Подгружаем нашу текстуру из файла texture_bricks.jpg (текстура кирпича) texture.loadFromFile("C:\\dev\\SFML_Tutorial\\Debug\\texture_bricks.jpg"); // Создаем 5 спрайтов для примера Sprite sprite1(texture); Sprite sprite2(texture); Sprite sprite3(texture); Sprite sprite4(texture); Sprite sprite5(texture); // Позиция sprite1.setPosition(10.f, 30.f); // абсолютное значение sprite2.move(170.f, 10.f); // смещение относительно текущей позиции // Вращение sprite3.setPosition(450.f, 30.f); // сначала сдвинем наш спрайт в сторону sprite3.setRotation(45.f); // абсолютное значение в 45° // sprite.rotate(15.f); // можно задать смещение относительно текущего значения угла (+15°) // Масштаб sprite4.setPosition(10, 150); // сначала сдвинем наш спрайт вниз sprite4.setScale(0.3f, 0.9f); // абсолютное значение масштаба sprite5.setPosition(100, 170); // сначала сдвинем наш спрайт в сторону sprite5.scale(1.5f, 0.2); // смещение параметров масштабирования относительно текущих значений // Отрисовка всех спрайтов window.draw(sprite1); window.draw(sprite2); window.draw(sprite3); window.draw(sprite4); window.draw(sprite5); // Отрисовка окна window.display(); } return 0; } |
Результат выполнения программы:
Точкой (началом координат), относительно которой происходят эти преобразования, является по умолчанию верхний левый угол спрайта. Если нам нужно установить начало координат в другую точку (например, в центр спрайта), то можно использовать метод setOrigin():
1 |
sprite.setOrigin(25.f, 25.f); |
Проблема белого квадрата
Вы успешно загрузили текстуру, правильно построили спрайт и… всё, что вы видите на экране сейчас — это белый квадрат. Что случилось?
Это распространенная ошибка. Когда вы устанавливаете текстуру для спрайта, то «под капотом» сохраняется указатель на экземпляр текстуры. Поэтому, если текстура уничтожается или перемещается в другое место в памяти, спрайт получает некорректный указатель на текстуру.
Данная проблема возникает при написании следующего рода функций:
1 2 3 4 5 6 7 |
Sprite loadSprite(std::string filename) { Texture texture; texture.loadFromFile(filename); return Sprite(texture); } // ошибка: В данном месте происходит уничтожение текстуры |
Поэтому всегда нужно перепроверять время жизни ваших текстур.
Использование большого количества текстур
Хорошей тактикой является использовать как можно меньше текстур. Причиной этому является то, что изменение текущей текстуры — это дорогостоящая операция для видеокарты. Рисование множества спрайтов, использующих одну и ту же текстуру, даст лучшую производительность.
Кроме того, использование одной текстуры позволяет группировать статическую геометрию в одну сущность, что будет намного быстрее отрисовываться, чем набор из множества сущностей. Постарайтесь помнить об этом и по возможности используйте как можно меньше текстур.
Заключение
На этом уроке мы разобрались со спрайтами и текстурами, научились накладывать текстуру на спрайт, изменять цвет. Поработали с различными типами трансформации спрайта: изменение положения, вращение, масштабирование (как в абсолютных, так и в относительных величинах). И даже рассмотрели проблему белого квадрата. На этом мы закончим знакомство с базовыми возможностями библиотеки SFML и будем двигаться дальше. Надеюсь, что вам понравилось, ведь впереди нас ждет нечто грандиозное…
«Ты можешь лучше.
Предела нет. Знай, ты можешь.
Будь уверен… Хватит попыток! Действуй!»
Здравствуйте! Я создал спрайт и попытался запустить программу, у меня вылезает код ошибки :"0x800700002".
Код программы:
Попробуйте заменить в своём коде строчку:
на:
Вот я новичок в этом и хотел бы спросить про объект window, который создаётся в классе RenderWindow . Насколько я знаю когда мы создаём объект класса, то можно дать ему любое имя. Получается вместо window можно написать что угодно? например вместо
можно написать
?
Или window это объект уже существующий в памяти библиотеки, к которому добавляются параметры?
>>Получается вместо window можно написать что угодно?
Да, всё верно. 🙂
Добрый день. Подскажите пожалуйста, из-за чего может при создании текстур возникать при отладке сообщение "вызвано исключение по адресу 0x78813697(vcruntime 140.dll) в Project1.exe: 0хС0000005: нарушение прав доступа при чтении по адресу 0х00700000" и текстура не загружается, хотя изображение в той же папке, что и Project1.exe, и может ли это быть из-за того, что у меня Visual Studio 2019, а не 2017 года?
Добрый день.
Благодарю за уроки. Очень помогают разобраться с графическими библиотеками.
Но при попытке повторить данные программы столкнулся с одной проблемой. Debug все работало идеально. Но при попытке скомпилировать программу в режиме Release, в строке со ссылкой на файл текстуры возникло исключение компилятора «нарушение прав доступа».
И к сожалению ни где в Ваших уроках описание решения подобной проблемы не нашел.
Поясните пути ее решения?
Помните, как в самом начале уроков по SFML мы прописывали в свойствах проекта дополнительные файлы библиотек? Ну там всякие:
sfml-system-d.lib;
sfml-window-d.lib;
sfml-graphics-d.lib
…
и пр.?
Так вот, буква "-d" в их имени — это сокращение от "debug". Что как бы тонко намекает на их использование только с debug-версией проекта 🙂
Для компиляции release-версии, необходимо добавить (аналогичным образом, но только для release-конфигурации) некоторые дополнительные настройки:
в окне свойств проекта переключить конфигурацию с "Debug" на "Release", далее в левой части окна выбираем "Компоновщик"->"Ввод"->"Дополнительные зависимости" и прописываем новые файлы библиотек (заметьте, уже без "-d"):
sfml-system.lib;
sfml-window.lib;
sfml-graphics.lib
Осталось только проверить, чтобы в папке с exe-файлом (если вы запускаете через него) или в папке проекта (если вы запускаете через Visual Studio) были необходимые *.dll:
openal32.dll
sfml-audio-2.dll
sfml-graphics-2.dll
sfml-network-2.dll
sfml-system-2.dll
sfml-window-2.dll
Как-то так 🙂
Админ в чем смысл удалять мой комент или я необоснованно критикую? начинка кода не совпадает с текстом нормального объяснения не в коментах нету не в тексте хотя ранее Евгений
8 января 2020 в 08:23 писал практически тоже самое и внятного ответа по поводу прямоугольников ему не дали. Если я так сильно туплю и не понимаю как сделать круг будь добр ну напиши на имеил как это сделать, если по какой то причине есть мнение что критика необоснованная и вредит сайту или автору . а так помойму это очевидная проблема урока
Очень редко, практически в 5% случаев, пользователи могут обнаружить мелкую неточность (в 1% случаев — серьезную ошибку), все остальные 95% случаев — это когда читатель прочитал, не понял, не захотел понять, невнимательно прочитал или просто не разобрался и т.д. и пишет комментарий, что "вот мол непонятен этот момент, объясните подробнее" или "могу ошибаться, но вот данный момент кажется мне неочевидным, можете объяснить" — такие комментарии (в большинстве случаев) публикуются.
Комментарии, где "не хочется хейтить", "не имеет смысла", "это очевидная проблема урока" и т.д., которые несут в себе «агрессивную окраску», человека, который не захотел разобраться сначала сам, а обвиняет сразу же автора или урок — удаляются.
Читайте уроки внимательнее и думайте, что пишите.
Не хочется хейтить ведь уроки то нормальные но почему текст с картинкой не сходится? на картинках мы делаем круг с текстурой но если сделать Sprite sprite(texture); мы получим квадрат! и тогда не о каком антиалиасинге речь не идет я пытался сделать преобразование в круг этого спрайта но так и не понял как , решил юзать сразу круг , и вот то что я достиг читая мануал на
офф сайте
пожалуйста обновите уроки что бы картинка с текстом сходилась ибо то что есть не имеет смысла и ломает голову.
>>на картинках мы делаем круг с текстурой но если сделать Sprite sprite(texture); мы получим квадрат!
Используемая текстура круга — прямоугольник с прозрачным(!) фоном, в центре которого изображен наш круг. Вы можете сами убедиться в этом, открыв текстуру в каком-нибудь графическом редакторе по типу paint.net или Photoshop. Поэтому, не смотря на то, что она воспринимается как круг, по факту это — прямоугольник.
>>и тогда не о каком антиалиасинге речь не идет
Почему? Где связь?
Большое спасибо за ответ. Просто в гайде не написано что надо качать именно эту текстуру для получения данного эффекта. И вот я который только второй день изучает эту библиотеку впадаю в диссонанс из за того что мой код выдает иной результат в 100% случаев, а попытки починить код ведут к синтаксическим ошибкам. И даже натянув текстуру на круг и преодолев это я уперся в тупик того что по уроку мы красим спрайт, а в случае фигуры это перекрывает текстуру. Со стороны новичка данные вещи крайне не понятны прошу это понять и возможно даже добавить небольшое пояснение этого момента в гайд для того что бы помочь разобраться другим новичкам.
По поводу антиалиасинга, я имел в виду что он становится не нужен в виду того что на экране ровный прямоугольник с текстурой не нуждающийся в сглаживании в случае текстуры как в уроке это имеет смысл.
Спасибо что обратили внимание и спасибо за уроки!
PS. если есть возможность объясните вкратце почему circle.setTexture принимает именно указатель на текстуру и пришлось создавать этот указатель.
>>PS. если есть возможность объясните вкратце почему circle.setTexture принимает именно указатель на текстуру и пришлось создавать этот указатель.
Потому что такой её создали разработчики. Ниже представлен прототип данной функции:
void CircleShape::setTexture (const Texture *texture, bool resetRect=false)
Конкретное место — "const Texture *texture".
Не обязательно создавать указатель на текстуру "auto *tex = &texture", чтобы затем передать его в функцию circle.setTexture(tex). Можно просто передать адрес уже существующей переменной texture, т.е.: "circle.setTexture(&texture)". Более подробно об этом написано здесь:
Урок №80. Указатели
https://ravesli.com/urok-80-ukazateli-vvedenie/
Урок №99. Передача по адресу
https://ravesli.com/urok-99-peredacha-argumentov-po-adresu/
Здравствуйте, у меня вопрос. А может даже просьба.
Подскажите мне. Может есть статья или тут мне подскажите.
Вот есть готовая игра , а мне нужно стереть некоторые текстуры чтобы добиться багов в игре. Например пол дома в игре будет прозрачная. Мне чисто интересно для себя поиграть и поделать баги своими руками.
Может подскажите как стирать текстуры ? Может статья есть какая ? Что да как и где так сказать.
Заранее благодарен за ответ.
>>Вот есть готовая игра , а мне нужно стереть некоторые текстуры чтобы добиться багов в игре.
Никогда не задавался таким вопросом. Обычно приходится решать обратную задачу — как заставить всё это дело работать без багов :). Поэтому даже и не знаю что подсказать.
Скажите, я сейчас делаю функцию, которая загружает текстуры один раз и потом эти текстуры могут быть использованы неограниченное кол-во раз. Я её реализовал, но она не отдаёт текстуры. Можете помочь?
>>Можете помочь?
Для этого нужно увидеть исходники вашей программы… 🙂
> sf::Sprite — это прямоугольник, наложенный на текстуру
> На изображениях — sf::CircleShape, наложенный на текстуру. Молодец, автор, бездумно копипастить…
Стоит упомянуть, что операции с трансформациями весьма ресурсозатратны, но выполняются строго при вызове методов отрисовки.
Также, забыт sf::Sprite::setTextureRect(). Это неотъемлемая часть спрайта, что в корне отличает его от других фигур в sfml и позволяет создавать анимированные элементы / сущности.
Про методы проверки коллизий тоже стоило бы написать отдельную статью. Это всё про спрайт, да — да, материал выше, в основном, про sf::Texture и sf::RectangleShape
Автор, вот вы сами хоть понимаете, что пишете? Как по мне — это просто какой-то сплошной поток мыслей.
>"Стоит упомянуть…"
Да есть много чего, про что стоит упомянуть. Но тогда объем материала уже будет тянуть не на статью, а на небольшую книгу.
Вообще говоря, когда я начинал писать данные статьи, то цель состояла в том, чтобы показать пользователям ravesli.com, освоившим раздел "Уроки С++", дальнейшие возможные пути продвижения в освоении языка C++.
Вдаваться в какие бы то ни было тонкости и нюансы работы SFML в данном материале считаю абсолютно нецелесообразным.
>"Про методы проверки коллизий тоже стоило бы написать отдельную статью…."
Так напишите, кто ж вам мешает. Напишите и отправьте ее на admin@ravesli.com. Если всё будет в порядке, то администратор опубликует её на сайте (с указанием авторства), и вы получите благодарность в свой адрес от довольных читателей. 🙂
Ждемс!
Есть еще просьба. Вот мы рассмотрели довольно подробно возможности языка C++. Теперь на очереди, как я понимаю, разные фреймворки.
Можно ли увидеть какую-то статью, где будет кратко расписано, какой фреймворк для какой категории задач предназначен?
-фреймворк "А" для разработки 3D-игр;
-фреймворк "B" для клиент-серверных приложений;
-фреймворк "C" для разработки 2D-игр;
-фреймвор для разработки VR-контента.
>>Можно ли увидеть какую-то статью, где будет кратко расписано, какой фреймворк для какой категории задач предназначен?
Как я уже писал, сейчас готовится очень интересный материал по SFML ;). Дальше запланирована статья-продолжение туториалов по Qt. А за ней, я думаю, можно будет выпустить то, о чем вы просите, т.е. обзорную статью по различным C++-фреймворкам. 🙂
А продолжение будет?! =)
Большое спасибо за материал!
В данный момент готовится новая статья по SFML. Так что следите за анонсами 🙂