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

Термины

Вероятно вы уже знакомы с этими терминами, но всё же давайте кратко их определим.

Текстура — это изображение. Мы же называем изображение «текстурой» из-за его специфической роли: их наносят на 2D объекты.

Спрайт — это текстурированный прямоугольник.

graphics-sprites-definition

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

Перед созданием любого спрайта, нам нужна готовая текстура. В SFML класс для работы с текстурами называется sf::Texture. Поскольку единственная роль текстуры после её загрузки заключается в нанесении её на графический объект, то почти все методы этого класса связаны с загрузкой и обновлением.

Наиболее распространённый способ загрузки текстуры — это загрузка из файла изображения лежащего на диске. Осуществляется это функцией loadFromFile():

sf::Texture texture;
if (!texture.loadFromFile("image.png"))
{
 // ошибка...
}

Иногда функция loadFromFile() терпит неудачу по не очевидной причин. Сначала проверьте сообщение об ошибке выводимое в стандартный вывод (проверьте консоль). Если сообщение гласит unable to open file (не удалось открыть файл), убедитесь что рабочий каталог (это каталог относительно которого и будут интерпретироваться все пути к файлам) именно там где вы предполагаете: когда вы запускаете приложение из проводника, рабочим каталогом является папка с приложением, но когда вы запускаете вашу программу через IDE (Visual Studio, Code :: Blocks, …), то рабочий каталог иногда устанавливается в папку с проектом, как правило это легко изменить в настройках проекта.

Аналогично вы можете загрузить некоторый файл изображение из памяти (loadFromMemory()), из пользовательского входного потока (loadFromStream())  или из уже загруженного изображения (loadFromImage()). Последняя из этих функций загружает текстуру из объекта класса sf::Image. Этот класс является служебным и помогает управлять изображениями (изменять пиксели, создавать альфа-канал, и т.д.) Пиксели изображения класса  sf::Image находятся в системной памяти, это гарантирует, что операции над ними будут производится быстрее, чем над пикселями текстуры, которые находятся в видеопамяти и которые долго получать и обновлять, но очень быстро можно нарисовать.

SFML поддерживает большинство из распространённых форматов файлов изображений. Полный перечень указан в API документации.

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

// загружаем прямоугольник 32x32 начало которого в точке (10, 10)
if (!texture.loadFromFile("image.png", sf::IntRect(10, 10, 32, 32)))
{
 // ошибка...
}

Класс sf::IntRect это простой и удобный тип, олицетворяющий прямоугольник. Конструктор класса принимает левый верхний угол и размеры прямоугольника.

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

// создаём пустую текстуру 200x200
if (!texture.create(200, 200))
{
 // ошибка...
}

Заметьте, в этот момент содержимое текстуры не определено.

Для обновления пикселей существующей текстуры, вы должны использовать функцию update(). Она может принимать различные ресурсы в качестве аргументов.

// обновление текстуры из массива пикселей
sf::Uint8* pixels = new sf::Uint8[width * height * 4]; // * 4 т.к. пиксели имеют четыре компоненты (RGBA)
...
texture.update(pixels);

// обновление текстуры из sf::Image
sf::Image image;
...
texture.update(image);

// обновление текстуры из текущего содержимого окна
sf::RenderWindow window;
...
texture.update(window);

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

Кроме того, текстура имеет два свойства, которые влияют на визуализацию.

Первое из них — это сглаживание текстуры. Сглаживание текстуры делает её пиксели менее видимыми (более размытыми), что может быть важно при масштабировании.

texture.setSmooth(true);

graphics-sprites-smooth

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

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

texture.setRepeated(true);

graphics-sprites-repeated

Это будет работать только если прямоугольник спрайта больше чем текстура, в противном случае эффекта не будет.

Хорошо, теперь то я уже могу нарисовать свой спрайт?

Да, теперь можно приступить к рисованию спрайтов.

Создаём спрайт:

sf::Sprite sprite;
sprite.setTexture(texture);

Рисуем его:

// внутри главного цикла, между window.clear() и window.display()
window.draw(sprite);

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

sprite.setTextureRect(sf::IntRect(10, 10, 32, 32));

Можно изменить цвет спрайта. Цвет, который вы установите модулируется (умножается) с текстурой спрайта. Это также может быть использовано для изменения глобальной прозрачности (альфа-канал) спрайта.

Все эти спрайты использовали одну и ту же текстуру, но имеют разные цвета:

graphics-sprites-color

Спрайты также могут быть преобразованы: они имеют положение, ориентацию и масштаб.

// позиция
sprite.setPosition(sf::Vector2f(10, 50)); // абсолютная позиция
sprite.move(sf::Vector2f(5, 10)); // смещение относительно текущей позиции

// поворот
sprite.setRotation(90); // абсолютный угол
sprite.rotate(15); // смещение относительно текущего угла

// масштаб
sprite.setScale(sf::Vector2f(0.5f, 2.f)); // абсолютный коэффициент масштабирования
sprite.scale(sf::Vector2f(1.5f, 3.f)); // коэффициент масштабирования относительно текущего масштаба

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

sprite.setOrigin(sf::Vector2f(25, 25));

Поскольку функции преобразования являются общими для всех объектов SFML, то они объясняются в отдельном уроке: Трансформация объектов.

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

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

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

Эта проблема возникает, когда вы пишете что-то наподобие этого:

sf::Sprite loadSprite(std::string filename)
{
 sf::Texture texture;
 texture.loadFromFile(filename);

 return sf::Sprite(texture);
} // ошибка: здесь уничтожается текстура

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

Важность использования такого малого числа текстур, какое только возможно

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

Кроме того, использование одной текстуры позволяет группировать неподвижные фигуры в единое целое (можно использовать только одну текстуру в вызове draw()), что позволит рисовать это целое намного быстрее, чем набор из множества отдельных объектов. Об этом подробнее будет рассказано в уроке Создание собственных объектов на основе массивов вершин(vertex arrays).

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

Использование sf::Texture вместе с OpenGL

Если вы используете OpenGL, а не графические объекты SFML, вы всё равно можете использовать sf::Texture как обертку над текстурами в OpenGL и тем самым взаимодействовать с вашими объектами OpenGL.

Что бы включить sf::Texture для рисования (эквивалент glBindTexture()), необходимо вызвать статическую функцию bind():

sf::Texture texture;
...

// привязка текстуры
sf::Texture::bind(&texture);

// рисуйте ваши текстурированные объекты OpenGL здесь...

// "отвязка" текстуры
sf::Texture::bind(NULL);

7 Комментарии

  1. Павел

    А как узнать позицию спрайта?

    1. samuel_unknown (Автор записи)
      float x, y, r;
      
      // Координаты x и y
      x = sprite.getPosition().x;
      y = sprite.getPosition().y;
      
      // Угол на который повёрнут спрайт
      r = sprite.getRotation();
      
  2. Павел

    Спасибо

  3. Павел

    Здравствуйте, появился такой вопрос. У меня есть функция, как мне из функции отключить показ спрайта, ну или как нибудь его изменить. Я так понял, что текстуры и спрайты объявляются только в главной функции main.

    1. samuel_unknown (Автор записи)

      Текстуры и спрайты не обязательно объявляются в главной функции main(). Что бы внутри какой-либо функции изменять спрайт, необходимо что бы внутри этой функции (в области видимости) существовал доступ к спрайту..если на более практичном уровне, то в функцию можно по ссылке или по указателю передать спрайт и творить с ним всё что душе угодно :) А можно например сделать так: допустим в некотором классе есть текстура и спрайт, у этого класса есть метод для изменения спрайта, в этом случае в метод передавать спрайт ни к чему, в нём и так будет этот спрайт «видно». По поводу прозрачности..не соврать бы :)) прозрачным спрайт можно сделать вызвав у спрайта метод выбора цвета: sprite.setColor(sf::Color::Transparent); вроде должно сработать. :)

  4. Милана

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

    1. samuel_unknown (Автор записи)

      Рад что вам пригодились мои статейки :)

Оставить комментарий

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

Капча * Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.