От переводчика:
Я продолжаю заброшенную серию переводов туториалов от Twinklebear, в оригинале доступных тут, с разрешения переводчика предыдущих уроков серии InvalidPointer. Первые два урока серии переводов в списке — за его авторством. Перевод отчасти вольный и может содержать незначительные поправки или дополнения от переводчика.
Список уроков:
- Урок 1. Hello World!
- Урок 2. Не запихивайте все в main.
- Урок 3. Библиотеки-расширения SDL
- Уроки 4-6 — To be continued
Библиотеки-расширения SDL
До этого момента мы использовали только изображения в формате BMP, поскольку это единственный тип изображений, поддерживаемый основной библиотекой SDL 2, и это не очень-то удобно. К счастью, существует множество библиотек-расширений для SDL, добавляющих полезные возможности, например, SDL_image позволяет загружать многие типы изображений, SDL_ttf добавляет поддержку отрисовки текста с помощью шрифтов в формате TTF, SDL_net — низкоуровневую поддержку сети, а SDL_mixer — вывод многоканального аудио.
Установка расширения
В этом уроке мы будем использовать только SDL_image, однако процесс установки для остальных расширений не отличается, и, в целом, почти совпадает с таковым для установки самой SDL2.
- Windows (MinGW или Visual Studio): Поместите файлы расширения, загруженные со страницы проекта расширения, в папку с SDL2. Также, вам потребуется скопировать SDL2_image, zlib и другие файлы .dll (например, libpng) в папку с вашим исполняемым файлом (или в C:Windowssystem32 — прим. пер.), чтобы они загрузились при запуске приложения.
- Linux: Установите расширение с помощью вашего штатного менеджера пакетов, либо соберите из исходников. Если у вас Linux — скорее всего, вы уже знаете, как это сделать ©.
- Mac: скачайте .dmg с официального сайта и следуйте инструкциям в Readme.
Также, чтобы использовать расширение, потребуется обновить список используемых заголовочных файлов и подключаемых библиотек, точно так же, как это было сделано для самого SDL2.
Инициализация SDL_image (не обязательно)
При первой загрузке изображения каждого типа 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_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