Как стать автором
Обновить

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

Время на прочтение5 мин
Количество просмотров4.8K
image

От переводчика:

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


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



Нарезка листа спрайтов


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

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

image


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

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


С помощью SDL очень легко выбрать часть текстуры, которую хотим нарисовать. В уроке 4 оставшиеся параметры SDL_RenderCopy со значением 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, однако вместо того, чтобы идти строка за строкой, мы пойдем столбец за столбцом. Таким образом, первый кусок будет зеленым, второй — красным, третий — синим и четвертый — желтым. Идея вычислений такая же, как в уроке 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. Полный список типов событий, кодов клавиш и остальной информации по SDL_Event доступен на вики.

Когда клавиша нажата, нам нужно поменять значение 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 (зеленый кружок) в центре экрана и иметь возможность выбирать различные части изображения с помощью цифровых клавиш. Если у вас возникнут какие-либо проблемы, дважды проверьте свой код и/или отправьте автору оригинала электронное письмо или твит.
Теги:
Хабы:
Всего голосов 8: ↑7 и ↓1+12
Комментарии0

Публикации

Истории

Работа

Программист C++
103 вакансии
QT разработчик
4 вакансии

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань