- PVSM.RU - https://www.pvsm.ru -

Цикл уроков по SDL 2.0: урок 5 — нарезка листа спрайтов

image

От переводчика:
Это продолжение серии переводов туториалов от Twinklebear, в оригинале доступных тут [1]. Перевод отчасти вольный и может содержать незначительные поправки или дополнения от переводчика. Перевод первых двух уроков — за авторством InvalidPointer [2], а третьего и четвертого — за k1-801 [3].

Список уроков:


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

В этом уроке мы увидим, как выбирать части текстуры, используя SDL_RenderCopy [8], а также немного о том, как обнаружить определенные события нажатия клавиш, которые будем использовать, чтобы выбрать, какой участок текстуры рисовать. На листе спрайтов будут четыре разноцветных круга:

image

В данном уроке лист спрайтов состоит из множества спрайтов одинакового размера, в таком случае нарезка не представляет трудностей. В противном же случае для спрайтов разного размера, нам бы понадобился файл с метаданными, в котором была бы информация о расположении частей. Для этого урока мы будем использовать 4 спрайта размера 100x100. Код этого урока основан на уроке 4, если у вас еще нет кода, на основании которого вы будете писать, можно взять его с Github [9].

Выбор части изображения

С помощью SDL очень легко выбрать часть текстуры, которую хотим нарисовать. В уроке 4 оставшиеся параметры SDL_RenderCopy [8] со значением NULL означают координаты прямоугольника, который определяет, какую часть текстуры мы хотим отрисовать. При передаче значения NULL указываем, что нам нужна вся текстура, но мы можем легко добавить параметры прямоугольника и рисовать только часть текстуры. Чтобы это сделать, внесем изменения в функцию renderTexture так, чтобы она могла брать прямоугольную область, но все еще сохраним короткую версию синтаксиса из старой версии для удобства.

Изменяем renderTexture

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

/**
* Отрисовать SDL_Texture в SDL_Renderer в целевом прямоугольнике.
* По желанию передаем параметр выреза
* @param tex Исходная текстура, которую хотим отрисовать
* @param ren Рендерер, в который хотим отрисовать
* @param dst Целевой прямоугольник для рендеринга текстуры
* @param clip Часть текстуры для рисования (вырезаемый прямоугольник)
*               По умолчанию nullptr отрисовывает всю текстуру
*/
void renderTexture(SDL_Texture *tex, SDL_Renderer *ren, SDL_Rect dst,
        SDL_Rect *clip = nullptr)
{
        SDL_RenderCopy(ren, tex, clip, &dst);
}

Для удобства напишем другую функцию, где нам не нужно было бы создавать SDL_Rect для расположения, а лишь предоставлять x и y и позволить нашей функции отображения заполнить ширину и высоту текстуры. Мы создадим перегруженную версию renderTexture, которая это сделает с некоторыми настройками для обработки отсечения. Добавим прямоугольник вырезания, как параметр со значением по умолчанию nullptr и в случае, если вырез был передан, будем использовать ширину и высоту выреза вместо ширины и высоты текстуры. Таким образом, мы не будем растягивать маленький спрайт до размера его потенциально очень большого листа спрайтов, когда он отрисовывается. Эта функция является модификацией оригинальной функции renderTexture и выглядит весьма похоже.

/**
* Отрисовать SDL_Texture в SDL_Renderer в точке x, y, сохраняя
* ширину и высоту текстуры и передаем параметр выреза по желанию
* Если вырез был передан, ширина и высота выреза будут использованы вместо
*       ширины и высоты текстуры
* @param tex Исходная текстура, которую хотим отрисовать
* @param ren Рендерер, в который хотим отрисовать
* @param x координата x, в которой нужно рисовать
* @param y координата y, в которой нужно рисовать
* @param clip Часть текстуры для рисования (вырезаемый прямоугольник)
*               По умолчанию nullptr отрисовывает всю текстуру
*/
void renderTexture(SDL_Texture *tex, SDL_Renderer *ren, int x, int y,
        SDL_Rect *clip = nullptr)
{
        SDL_Rect dst;
        dst.x = x;
        dst.y = y;
        if (clip != nullptr){
                dst.w = clip->w;
                dst.h = clip->h;
        }
        else {
                SDL_QueryTexture(tex, NULL, NULL, &dst.w, &dst.h);
        }
        renderTexture(tex, ren, dst, clip);
}

Определение прямоугольников отсечения

В нашем случае очень легко посчитать прямоугольники отсечения, используя метод во многом похожий на метод тайлинга из урока 3 [6], однако вместо того, чтобы идти строка за строкой, мы пойдем столбец за столбцом. Таким образом, первый кусок будет зеленым, второй — красным, третий — синим и четвертый — желтым. Идея вычислений такая же, как в уроке 3, но только вместо строк пробегаем столбцы. Так наши координаты по y вычисляются получением остатка при делении индекса тайла на количество тайлов (2), а координата по x делением индекса на количество тайлов. Эти координаты x и y являются индексами x и y, так что мы переводим их в реальные координаты пикселей умножением на ширину и высоту выреза, который для всех тайлов одинаковый (100x100). Наконец, выбираем кусок, чтобы рисовать, в данном случае первый.

Мы также хотели бы нарисовать наши куски в центре экрана, поэтому вычисляем эти координаты x и y, используя ширину и высоту тайла вместо ширины и высоты текстуры.

//iW и iH ширина и высота выреза
//Будем рисовать только части, так что получим координаты центра, учитывая высоту и ширину частей
int iW = 100, iH = 100;
int x = SCREEN_WIDTH / 2 - iW / 2;
int y = SCREEN_HEIGHT / 2 - iH / 2;

//Настраиваем части для нашего изображения
SDL_Rect clips[4];
for (int i = 0; i < 4; ++i){
        clips[i].x = i / 2 * iW;
        clips[i].y = i % 2 * iH;
        clips[i].w = iW;
        clips[i].h = iH;
}
//Для начала укажем часть по умолчанию
int useClip = 0;

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

Изменение изображений на основе ввода

Чтобы проверить все созданные нами части изображения, добавим обработку ввода с клавиатуры в цикл обработки событий и сделаем выбор отображаемой части с помощью клавиш 1-4. Чтобы определить, произошло ли нажатие клавиши, можно проверить имеет ли событие тип SDL_KEYDOWN и если это так, то мы можем узнать, какая клавиша была нажата, проверяя код клавиши внутри события, используя e.key.keysym.sym. Полный список типов событий [10], кодов клавиш [10] и остальной информации по SDL_Event [11] доступен на вики.
Когда клавиша нажата, нам нужно поменять значение useClip на номер части изображения, которую мы хотим рисовать. С этими изменениями цикл обработки событий выглядит следующим образом:

while (SDL_PollEvent(&e)){
        if (e.type == SDL_QUIT)
                quit = true;
        //Используем ввод чисел для выбора куска для отрисовки
        if (e.type == SDL_KEYDOWN){
                switch (e.key.keysym.sym){
                        case SDLK_1:
                                useClip = 0;
                                *break;
                        case SDLK_2:
                                useClip = 1;
                                *break;
                        case SDLK_3:
                                useClip = 2;
                                *break;
                        case SDLK_4:
                                useClip = 3;
                                *break;
                        case SDLK_ESCAPE:
                                quit = true;
                                *break;
                        default:
                                *break;
                }
        }
}

Рисуем вырезанное изображение

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

SDL_RenderClear(renderer);
renderTexture(image, renderer, x, y, &clips[useClip]);
SDL_RenderPresent(renderer);

Конец урока 5

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

Автор: Андрей Черняков

Источник [12]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/351013

Ссылки в тексте:

[1] тут: https://www.willusher.io/pages/sdl2/

[2] InvalidPointer: https://habr.com/ru/users/invalidpointer/

[3] k1-801: https://habr.com/ru/users/k1-801/

[4] Урок 1. Hello World!: https://habr.com/ru/post/198600/

[5] Урок 2. Не запихивайте все в main: https://habr.com/ru/post/200730/

[6] Урок 3. Библиотеки-расширения SDL: https://habr.com/ru/post/437252/

[7] Урок 4. Обработка событий: https://habr.com/ru/post/437308/

[8] SDL_RenderCopy: http://wiki.libsdl.org/SDL_RenderCopy

[9] Github: https://github.com/Twinklebear/TwinklebearDev-Lessons/tree/master/Lesson4

[10] типов событий: http://wiki.libsdl.org/SDL_EventType

[11] SDL_Event: http://wiki.libsdl.org/SDL_Event

[12] Источник: https://habr.com/ru/post/494478/?utm_source=habrahabr&utm_medium=rss&utm_campaign=494478