- PVSM.RU - https://www.pvsm.ru -
От переводчика:
Я продолжаю заброшенную серию переводов туториалов от Twinklebear, в оригинале доступных тут [1], с разрешения переводчика предыдущих уроков серии InvalidPointer [2]. Первые два урока серии переводов в списке — за его авторством. Перевод отчасти вольный и может содержать незначительные поправки или дополнения от переводчика.
Список уроков:
До этого момента мы использовали только изображения в формате BMP, поскольку это единственный тип изображений, поддерживаемый основной библиотекой SDL 2, и это не очень-то удобно. К счастью, существует множество библиотек-расширений для SDL, добавляющих полезные возможности, например, SDL_image позволяет загружать многие типы изображений, SDL_ttf добавляет поддержку отрисовки текста с помощью шрифтов в формате TTF, SDL_net — низкоуровневую поддержку сети, а SDL_mixer — вывод многоканального аудио.
В этом уроке мы будем использовать только SDL_image, однако процесс установки для остальных расширений не отличается, и, в целом, почти совпадает с таковым для установки самой SDL2.
Также, чтобы использовать расширение, потребуется обновить список используемых заголовочных файлов и подключаемых библиотек, точно так же, как это было сделано для самого SDL2.
При первой загрузке изображения каждого типа SDL_image автоматически инициализирует необходимую для этого типа подсистему, однако, это вызовет небольшую задержку. Чтобы этого избежать, можно заранее проинициализировать необходимые подсистемы с помощью функции IMG_Init. IMG_Init возвращает битовую маску со списком всех успешно проинициализированных на данный момент подсистем, поэтому для проверки успешности вызова необходимо проверить, что биты для всех указанных для инициализации подсистем были установлены, например, применив маску к результату побитовым И. Для этого урока нам хватит только одной подсистемы PNG. Важно проводить эту операцию после SDL_Init.
if ((IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG) != IMG_INIT_PNG)
{
logSDLError(std::cout, "IMG_Init");
SDL_Quit();
return 1;
}
В этом уроке мы рассмотрим, как загружать изображения с помощью SDL_image, как масштабировать текстуры при отрисовке и замостим фон плиткой более рациональным способом, нежели в предыдущем уроке — циклом, основывающемся на размерах окна и плиток.
Но для начала зададим константу для размера плиток, прямо под константами для размеров окна.
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
// Будем использовать квадратные плитки
const int TILE_SIZE = 40;
SDL_image позволяет загрузить несколько типов изображений, а так же сразу преобразовать их в SDL_Texture функцией IMG_LoadTexture. Эта функция заменяет почти весь код функции loadTexture из предыдущего урока, теперь достаточно просто вызвать IMG_LoadTexture, проверить, не возникло ли ошибок при загрузке, и выйти из функции. Поскольку определённая в SDL_image функция IMG_GetError — не более чем синоним для SDL_GetError, для вывода сообщений об ошибках мы можем использовать любую из них.
/**
* Загружает изображение в текстуру для рендерера
* @param file Путь к изображению
* @param ren Рендерер, на который эту текстуру можно будет отрисовать
* @return Возвращает текстуру, либо nullptr в случае ошибки.
*/
SDL_Texture* loadTexture(const std::string &file, SDL_Renderer *ren)
{
SDL_Texture *texture = IMG_LoadTexture(ren, file.c_str());
if (!texture)
{
std::cout << IMG_GetError(); // Можно заменить на SDL_GetError()
}
return texture;
}
Если при отрисовке текстуры на рендерер указать размер прямоугольника, отличный от размера самой текстуры, SDL2 отмасштабирует её соответствующим образом. Однако, если масштабирование не требуется, то каждый раз определять исходный размер текстуры может быть неудобно, поэтому мы реализуем две версии функции renderTexture, одна из которых будет отрисовывать текстуру с масштабированием, а вторая — без.
/**
* Отобразить SDL_Texture на SDL_Renderer на координатах x, y, с масштабированием
* @param tex Текстура для отображения
* @param ren Рендерер
* @param x Координаты
* @param y
* @param w Фактический размер при отрисовке
* @param h
*/
void renderTexture(SDL_Texture *tex, SDL_Renderer *ren, int x, int y, int w, int h)
{
SDL_Rect dst;
dst.x = x;
dst.y = y;
dst.w = w;
dst.h = h;
SDL_RenderCopy(ren, tex, NULL, &dst);
}
/**
* Отрисовать SDL_Texture на SDL_Renderer на координатах x, y, без масштабирования
* @param tex Текстура
* @param ren Рендерер
* @param x Коодринаты
* @param y
*/
void renderTexture(SDL_Texture *tex, SDL_Renderer *ren, int x, int y)
{
int w, h;
SDL_QueryTexture(tex, NULL, NULL, &w, &h);
renderTexture(tex, ren, x, y, w, h);
}
Поскольку основная цель этого урока — загрузка изображений PNG, мы воспользуемся новым набором изображений. Также, мы продемонстрируем сохранение прозрачности PNG при отрисовке изображения переднего плана (с прозрачным фоном) поверх замощёного плиткой фона.
Мы будем использовать вот эти картиночки:
Плитка для заполнения фона:
Изображение переднего плана (как на нём и написано, с прозрачным фоном, а так же снова со смайликами, нарушающими правила Хабра):
Загружаем изображения:
SDL_Texture *background = loadTexture("background.png", renderer);
SDL_Texture *image = loadTexture("image.png", renderer);
// Проверка
if (!background || !image)
{
// В оригинале, эта часть использовала шаблонную функцию cleanup(), рассмотренную в P.S. к первому уроку, добавленному уже после написания перевода, и посему, не попавшему в перевод.
SDL_DestroyTexture(background);
SDL_DestroyTexture(image);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
IMG_Quit();
SDL_Quit();
return 1;
}
Поскольку плитки стали заметно меньше, нам понадобится поставить больше четырёх штук, чтобы заполнить всё окно, и указывать позицию для каждой вручную будет довольно сложно. К счастью, можно заставить компьютер определять эти позиции самостоятельно.
Мы можем узнать, сколько нужно плиток в ширину, поделив ширину окна на размер плитки, и аналогично для высоты.
// Определение количества плиток, необходимого для покрытия всего окна
// От переводчика: я бы заменил на ceil(float(SCREEN_WIDTH) / TILE_SIZE), чтобы в том случае, кода размер окна не кратен размеру плитки, оставшаяся часть окна не оставалась пустой; однако, в данном примере размеры заданы константой и кратны, так что это не страшно.
int xTiles = SCREEN_WIDTH / TILE_SIZE;
int yTiles = SCREEN_HEIGHT / TILE_SIZE;
// Отрисовка фона
for (int i = 0; i < xTiles * yTiles; ++i)
{
int x = i % xTiles;
int y = i / xTiles;
renderTexture(background, renderer, x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
Как и прежде, изображение переднего плана помещается в середине окна.
int iW, iH;
SDL_QueryTexture(image, NULL, NULL, &iW, &iH);
int x = SCREEN_WIDTH / 2 - iW / 2;
int y = SCREEN_HEIGHT / 2 - iH / 2;
renderTexture(image, renderer, x, y);
Осталось только отобразить результат на окне и подождать пару секунд, так же, как и во втором уроке.
SDL_RenderPresent(renderer);
SDL_Delay(2000);
Освобождение ресурсов аналогично таковому в уроке 2 (и уже встречалось выше, при обработке ошибки загрузки изображения), за исключением добавившегося вызова IMG_Quit.
SDL_DestroyTexture(background);
SDL_DestroyTexture(image);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
IMG_Quit();
SDL_Quit();
После успешной компиляции и запуска, если вы всё сделали правильно, окно будет выглядеть примерно так:
Вот и подошел к концу очередной урок. Всем до встречи в уроке 4: Обработка событий.
Автор: k1-801
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/306272
Ссылки в тексте:
[1] тут: https://www.willusher.io/pages/sdl2/
[2] InvalidPointer: https://habr.com/ru/users/invalidpointer/
[3] Урок 1. Hello World!: https://habr.com/ru/post/200730/
[4] страницы проекта расширения: http://www.libsdl.org/projects/SDL_image/
[5] Источник: https://habr.com/ru/post/437252/?utm_campaign=437252
Нажмите здесь для печати.