Спрайты и текстуры в C++/SFML

  Дмитрий Бушуев  | 

  Обновл. 4 Сен 2021  | 

 30305

 ǀ   23 

“Я знаю, чем ты занимаешься… почему ты плохо спишь, почему живешь один и почему ночь за ночью сидишь за своим компьютером. Ты ищешь его. Я знаю, потому что я сама искала. И когда он нашел меня, он сказал, что на самом деле я искала не его. Я искала ответ. Этот вопрос ведет нас. Он сводит нас с ума! Этот вопрос привел тебя сюда. Ты знаешь этот вопрос точно так же, как и я… СПРАЙТЫ И ТЕКСТУРЫ… КАК SFML РАБОТАЕТ С НИМИ?”

Спрайты и текстуры

Итак, что же такое текстура, спрайт и чем они отличаются друг от друга.

   Текстура — это обычная картинка, которая накладывается на двумерный объект.

   Спрайт — это любая геометрическая фигура с нанесенной поверх нее текстурой.

Ниже приведена картинка, демонстрирующая эти концепции, которую я нагло спёр из официальной документации по SFML.

Сложного здесь ничего нет, поэтому двигаемся дальше.

Загрузка текстуры


Прежде чем создавать какой-нибудь спрайт, нам нужна текстура. Класс SFML, который предоставляет возможность работать с текстурами, называется внезапно Texture. Так как единственной целью текстуры является её загрузка из источника и нанесение на объект, то назначение практически всех функций данного класса сводится в большинстве своем к двум задачам: к загрузке текстуры и к её отображению в программе.

Основным способом загрузки текстуры является считывание информации о ней из графического файла, расположенного на компьютере. Ниже я привел простейший пример создания спрайта и наложения на него текстуры:

Результат выполнения программы:

Стоит отметить, что библиотека SFML поддерживает работу со всеми основными форматами графических файлов.

Есть еще несколько способов загрузки текстуры:

   загрузка файла текстуры из памяти с помощью метода loadFromMemory();

   загрузка из потока с помощью метода loadFromStream();

   загрузка из файла, который уже был загружен, с помощью метода loadFromImage().

Все эти функции имеют необязательный аргумент, который можно использовать, если вы хотите загрузить лишь часть изображения. Например, следующий код загружает небольшой кусочек текстуры размером 32×32 пикселя, начинающийся с координат (10; 10):

Помимо этого, у текстуры есть два интересных свойства, которые влияют на то, как она будет отображаться на экране. Первое свойство — это сглаживание, которое делает границы пикселей менее видимыми (правда из-за этого изображение становится немного размытым). Например:

Особенно полезным это свойство является при масштабировании текстуры. Ниже в прикрепленной гифке вы можете увидеть разницу между вариантом со сглаживанием и без:

Второе интересное свойство позволяет многократно повторять текстуру в пределах одного спрайта. Например:

Результат выполнения программы:

Стоит отметить, что данный способ работает только тогда, когда размеры спрайта больше размеров текстуры, в противном случае — ничего не получится.

Также можно изменить и цвет самого спрайта, который будет мультипликативно складываться с текущей текстурой. Благодаря этому свойству удобно изменять значение прозрачности спрайта. Например:

Результат выполнения программы:

Трансформации

К спрайтам могут быть применены различные варианты трансформаций: изменение позиции, изменение ориентации в пространстве и масштабирование. Все они выполняются с помощью методов setPosition()/move(), setRotation()/rotate() и setScale()/scale(), соответственно. Вы можете спросить: «А почему данные методы написаны через слэш?». А дело в том, что первые методы в каждой паре производят абсолютные преобразования (преобразования относительно начала координат). Вторые же методы в каждой паре производят преобразования относительно текущих значений координат/угла/масштаба. Например:

Результат выполнения программы:

Точкой (началом координат), относительно которой происходят эти преобразования, является по умолчанию верхний левый угол спрайта. Если нам нужно установить начало координат в другую точку (например, в центр спрайта), то можно использовать метод setOrigin():

Проблема белого квадрата


Вы успешно загрузили текстуру, правильно построили спрайт и… всё, что вы видите на экране сейчас — это белый квадрат. Что случилось?

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

Данная проблема возникает при написании следующего рода функций:

Поэтому всегда нужно перепроверять время жизни ваших текстур.

Использование большого количества текстур

Хорошей тактикой является использовать как можно меньше текстур. Причиной этому является то, что изменение текущей текстуры — это дорогостоящая операция для видеокарты. Рисование множества спрайтов, использующих одну и ту же текстуру, даст лучшую производительность.

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

Заключение


На этом уроке мы разобрались со спрайтами и текстурами, научились накладывать текстуру на спрайт, изменять цвет. Поработали с различными типами трансформации спрайта: изменение положения, вращение, масштабирование (как в абсолютных, так и в относительных величинах). И даже рассмотрели проблему белого квадрата. На этом мы закончим знакомство с базовыми возможностями библиотеки SFML и будем двигаться дальше. Надеюсь, что вам понравилось, ведь впереди нас ждет нечто грандиозное…

«Ты можешь лучше.
Предела нет. Знай, ты можешь.
Будь уверен… Хватит попыток! Действуй!»

  GitHub / Спрайты и текстуры в C++/SFML — Исходный код

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (60 оценок, среднее: 4,88 из 5)
Загрузка...

Комментариев: 23

  1. Антон:

    Здравствуйте! Я создал спрайт и попытался запустить программу, у меня вылезает код ошибки :"0x800700002".

    Код программы:

    1. Дмитрий Бушуев:

      Попробуйте заменить в своём коде строчку:

      на:

  2. Раф:

    Вот я новичок в этом и хотел бы спросить про объект window, который создаётся в классе RenderWindow . Насколько я знаю когда мы создаём объект класса, то можно дать ему любое имя. Получается вместо window можно написать что угодно? например вместо

    можно написать

    ?
    Или window это объект уже существующий в памяти библиотеки, к которому добавляются параметры?

    1. Дмитрий Бушуев:

      >>Получается вместо window можно написать что угодно?
      Да, всё верно. 🙂

  3. вероника:

    Добрый день. Подскажите пожалуйста, из-за чего может при создании текстур возникать при отладке сообщение "вызвано исключение по адресу 0x78813697(vcruntime 140.dll) в Project1.exe: 0хС0000005: нарушение прав доступа при чтении по адресу 0х00700000" и текстура не загружается, хотя изображение в той же папке, что и Project1.exe, и может ли это быть из-за того, что у меня Visual Studio 2019, а не 2017 года?

  4. Алексей:

    Добрый день.
    Благодарю за уроки. Очень помогают разобраться с графическими библиотеками.
    Но при попытке повторить данные программы столкнулся с одной проблемой. Debug все работало идеально. Но при попытке скомпилировать программу в режиме Release, в строке со ссылкой на файл текстуры возникло исключение компилятора «нарушение прав доступа».
    И к сожалению ни где в Ваших уроках описание решения подобной проблемы не нашел.
    Поясните пути ее решения?

    1. Дмитрий Бушуев:

      Помните, как в самом начале уроков по 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

      Как-то так 🙂

  5. Rock:

    Админ в чем смысл удалять мой комент или я необоснованно критикую? начинка кода не совпадает с текстом нормального объяснения не в коментах нету не в тексте хотя ранее Евгений
    8 января 2020 в 08:23 писал практически тоже самое и внятного ответа по поводу прямоугольников ему не дали. Если я так сильно туплю и не понимаю как сделать круг будь добр ну напиши на имеил как это сделать, если по какой то причине есть мнение что критика необоснованная и вредит сайту или автору . а так помойму это очевидная проблема урока

    1. Юрий:

      Очень редко, практически в 5% случаев, пользователи могут обнаружить мелкую неточность (в 1% случаев — серьезную ошибку), все остальные 95% случаев — это когда читатель прочитал, не понял, не захотел понять, невнимательно прочитал или просто не разобрался и т.д. и пишет комментарий, что "вот мол непонятен этот момент, объясните подробнее" или "могу ошибаться, но вот данный момент кажется мне неочевидным, можете объяснить" — такие комментарии (в большинстве случаев) публикуются.

      Комментарии, где "не хочется хейтить", "не имеет смысла", "это очевидная проблема урока" и т.д., которые несут в себе «агрессивную окраску», человека, который не захотел разобраться сначала сам, а обвиняет сразу же автора или урок — удаляются.

      Читайте уроки внимательнее и думайте, что пишите.

  6. Rock:

    Не хочется хейтить ведь уроки то нормальные но почему текст с картинкой не сходится? на картинках мы делаем круг с текстурой но если сделать Sprite sprite(texture); мы получим квадрат! и тогда не о каком антиалиасинге речь не идет я пытался сделать преобразование в круг этого спрайта но так и не понял как , решил юзать сразу круг , и вот то что я достиг читая мануал на
    офф сайте

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

    1. Дмитрий Бушуев:

      >>на картинках мы делаем круг с текстурой но если сделать Sprite sprite(texture); мы получим квадрат!

      Используемая текстура круга — прямоугольник с прозрачным(!) фоном, в центре которого изображен наш круг. Вы можете сами убедиться в этом, открыв текстуру в каком-нибудь графическом редакторе по типу paint.net или Photoshop. Поэтому, не смотря на то, что она воспринимается как круг, по факту это — прямоугольник.

      >>и тогда не о каком антиалиасинге речь не идет
      Почему? Где связь?

      1. Rock:

        Большое спасибо за ответ. Просто в гайде не написано что надо качать именно эту текстуру для получения данного эффекта. И вот я который только второй день изучает эту библиотеку впадаю в диссонанс из за того что мой код выдает иной результат в 100% случаев, а попытки починить код ведут к синтаксическим ошибкам. И даже натянув текстуру на круг и преодолев это я уперся в тупик того что по уроку мы красим спрайт, а в случае фигуры это перекрывает текстуру. Со стороны новичка данные вещи крайне не понятны прошу это понять и возможно даже добавить небольшое пояснение этого момента в гайд для того что бы помочь разобраться другим новичкам.

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

        Спасибо что обратили внимание и спасибо за уроки!

        PS. если есть возможность объясните вкратце почему circle.setTexture принимает именно указатель на текстуру и пришлось создавать этот указатель.

        1. Дмитрий Бушуев:

          >>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/

  7. Виталий:

    Здравствуйте, у меня вопрос. А может даже просьба.
    Подскажите мне. Может есть статья или тут мне подскажите.
    Вот есть готовая игра , а мне нужно стереть некоторые текстуры чтобы добиться багов в игре. Например пол дома в игре будет прозрачная. Мне чисто интересно для себя поиграть и поделать баги своими руками.
    Может подскажите как стирать текстуры ? Может статья есть какая ? Что да как и где так сказать.
    Заранее благодарен за ответ.

    1. Дмитрий Бушуев:

      >>Вот есть готовая игра , а мне нужно стереть некоторые текстуры чтобы добиться багов в игре.
      Никогда не задавался таким вопросом. Обычно приходится решать обратную задачу — как заставить всё это дело работать без багов :). Поэтому даже и не знаю что подсказать.

  8. Владимир:

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

    1. Дмитрий Бушуев:

      >>Можете помочь?
      Для этого нужно увидеть исходники вашей программы… 🙂

  9. Евгений:

    > sf::Sprite — это прямоугольник, наложенный на текстуру
    > На изображениях — sf::CircleShape, наложенный на текстуру. Молодец, автор, бездумно копипастить…
    Стоит упомянуть, что операции с трансформациями весьма ресурсозатратны, но выполняются строго при вызове методов отрисовки.

    Также, забыт sf::Sprite::setTextureRect(). Это неотъемлемая часть спрайта, что в корне отличает его от других фигур в sfml и позволяет создавать анимированные элементы / сущности.

    Про методы проверки коллизий тоже стоило бы написать отдельную статью. Это всё про спрайт, да — да, материал выше, в основном, про sf::Texture и sf::RectangleShape

    1. Дмитрий Бушуев:

      Автор, вот вы сами хоть понимаете, что пишете? Как по мне — это просто какой-то сплошной поток мыслей.

      >"Стоит упомянуть…"
      Да есть много чего, про что стоит упомянуть. Но тогда объем материала уже будет тянуть не на статью, а на небольшую книгу.

      Вообще говоря, когда я начинал писать данные статьи, то цель состояла в том, чтобы показать пользователям ravesli.com, освоившим раздел "Уроки С++", дальнейшие возможные пути продвижения в освоении языка C++.
      Вдаваться в какие бы то ни было тонкости и нюансы работы SFML в данном материале считаю абсолютно нецелесообразным.

      >"Про методы проверки коллизий тоже стоило бы написать отдельную статью…."
      Так напишите, кто ж вам мешает. Напишите и отправьте ее на admin@ravesli.com. Если всё будет в порядке, то администратор опубликует её на сайте (с указанием авторства), и вы получите благодарность в свой адрес от довольных читателей. 🙂

  10. Александр:

    Ждемс!
    Есть еще просьба. Вот мы рассмотрели довольно подробно возможности языка C++. Теперь на очереди, как я понимаю, разные фреймворки.
    Можно ли увидеть какую-то статью, где будет кратко расписано, какой фреймворк для какой категории задач предназначен?
    -фреймворк "А" для разработки 3D-игр;
    -фреймворк "B" для клиент-серверных приложений;
    -фреймворк "C" для разработки 2D-игр;
    -фреймвор для разработки VR-контента.

    1. Дмитрий Бушуев:

      >>Можно ли увидеть какую-то статью, где будет кратко расписано, какой фреймворк для какой категории задач предназначен?

      Как я уже писал, сейчас готовится очень интересный материал по SFML ;). Дальше запланирована статья-продолжение туториалов по Qt. А за ней, я думаю, можно будет выпустить то, о чем вы просите, т.е. обзорную статью по различным C++-фреймворкам. 🙂

  11. Александр:

    А продолжение будет?! =)
    Большое спасибо за материал!

    1. Дмитрий Бушуев:

      В данный момент готовится новая статья по SFML. Так что следите за анонсами 🙂

Добавить комментарий для Алексей Отменить ответ

Ваш E-mail не будет опубликован. Обязательные поля помечены *