К сожалению, даже на официальной вики почти не возможно найти каких либо примеров использования SDL2.x, что уж говорить о рунете. Пытаясь разобраться, я нашел всего лишь пару статей, которые не покрыли и трети моих вопросов.
SDL 2.x существенно отличается от 1.x и даже, если в прошлом вам приходилось с ним работать — теперь вы рискуете ничего не понять.
Сегодня мы напишем простенькую программу выводящую на экран фон и зумируемый спрайт персонажа перемещающегося с помощью WASD и стрелок. + разберемся как в SDL работать с мышкой.
Для работы нам понадобится:
- SDL2.h
- SDL2_image.h>
- SDL2_mixer.h>
Все это можно легко найти на просторах интернета.
Начнем с самого начала:
Инициализация SDL
Начнем, пожалуй, с создания объекта класса SDL_DisplayMode.
Он нам очень пригодится, если мы хотим иметь приложение на весь экран.
Этот объект нужно создать до инициализации самого SDL.
SDL_DisplayMode displayMode;
После этого нужно проинициализировать сам SDL:
if (SDL_Init(SDL_INIT_EVERYTHING) != 0){
std::cout << "SDL_Init Error: " << SDL_GetError() << std::endl;
return 1;
}
Флаг SDL_INIT_EVERYTHING
инициализирует все подсистемы SDL. Если вам нужно только что то конкретное, то на вики можно найти их полный перечень.
Теперь нам нужно получить параметры монитора с которым мы работаем.
Для этого мы создаем интовую переменную, в которую будет возвращен 0, если все прошло успешно и приравниваем ее функции SDL_GetDesktopDisplayMode(*int displayIndex, SDL_DisplayMode* mode)
.
Если в первый аргумент записать 0, то функция обратиться к главному монитору. Все полученные параметры мы сможем считать с объекта displayMode
.
int request = SDL_GetDesktopDisplayMode(0,&displayMode);
Пришло время заняться нашим окном!
Тут все предельно просто, создаем указатель на объект класса SDL_Window
и вызываем функцию
SDL_Window* SDL_CreateWindow(const char* title, int x, int y, int w, int h, Uint32 flags)
Тут все конечно и так ясно, но на всякий случай объясню что к чему.
- title — имя окна.
- x,y — координаты окна. Если хотим открыть на весь экран, то нужно ставить
0,0
- w,h — размеры окна. Что бы открыть на весть экран обращаемся к объекту
displayMode
. - flags — тут выставляем флаги инициализации окна. Вы можете сказать, что я
тупойне прав и существует флагSDL_WINDOW_FULLSCREEN
, и я тут изобретаю велосипед своимDisplayMode
, но нет!
Путемнаучного тыкапродуктивных экспериментов, я заметил что такой способ гораздо быстрее и на него не реагируют антивирусники. На тот жеSDL_WINDOW_FULLSCREEN
аваст кричал, что меня пытаются взломать.
SDL_WINDOW_SHOWN — делает окно видимым.
В итоге на выходе получаем такой код:
SDL_Window *win = SDL_CreateWindow("Hello World!", 0, 0, displayMode.w, displayMode.h, SDL_WINDOW_SHOWN);
if (win == nullptr){
std::cout << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
return 1;
}
Теперь нам нужно создать рендер:
SDL_Renderer* SDL_CreateRenderer(SDL_Window* window,
int index,
Uint32 flags)
- window — окно в котором мы будем работать.
- index — индекс драйвера который будет использовать рендер. Если поставить
-1
, то рендер будет использовать первый подходящий драйвер. - flags — флаги рендера. Полный список как всегда на вики.
Я буду использоватьSDL_RENDERER_ACCELERATED
отвечающий за аппаратное ускорение иSDL_RENDERER_PRESENTVSYNC
отвечающий за вертикальную синхронизацию.
Собираем все вместе и получаем:
SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (ren == nullptr){
std::cout << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
return 1;
}
Теперь SDL готов с нами сотрудничать!
Вывод на экран
Пришло время заняться изображениями.
Для начала нам нужно создать 2 объекта класса SDL_Rect
.
Этот объект будет содержать физические параметры наших текстур, таких как ширину, высоту и положение в окне.
SDL_Rect player_RECT;
player_RECT.x = 0; //Смещение полотна по Х
player_RECT.y = 0; //Смещение полотна по Y
player_RECT.w = 333; //Ширина полотна
player_RECT.h = 227; //Высота полотна
SDL_Rect background_RECT;
background_RECT.x = 0;
background_RECT.y = 0;
background_RECT.w = displayMode.w;
background_RECT.h = displayMode.h;
И еще пару строк, чтобы чуть позже мы смогли зумировать нашего персонажа:
const int player_WIGHT = 333; //Ширина исходнго изображения
const int player_HEIGH = 227; //Высота исходного изображения
double TESTtexture_SCALE = 1.0; //Множетель для зумирования
И вот мы добрались до загрузки текстур.
Я покажу 2 способа:
Но для начала небольшое отступление!
Думаю те кто раньше работали с SDL1.x в объяснениях не нуждаются, но я все же расскажу от том как устроен SDL, вдруг (ну мало ли) тут кто то с ним не знаком.
В SDL есть 4 основных класса/структуры участвующих в выводе изображения на экран: SDL_Texture
, SDL_Surface
, SDL_Rect
, SDL_Render
.
Про последние 2 мы уже поговорили, давайте теперь вкратце обсудим оставшиеся.
SDL_Surface
— работая сSDL_mixer.h
о нем вы можете забыть. Но глупо с чем то работать не имея ни малейшего представления о том как оно устроено.ПодробностиОбъект этой структуры позволяет загрузить в себя любое BMP изображение, для дальнейшего перевода в текстуру. Однако ее немодифицируемое использование влечет за собой ограничения.Как пример: чистый SDL работает только с BMP, которое поддерживает альфа-канал, только в 32-битном цвете, а он поддерживается для этого формата далеко не на каждой ОС. А тут уже теряется вся польза от кроссплатформенности SDL.
SDL_Texture
— создав объект структурыSDL_Surface
, мы должны превратить его в текстуру, что бы рендер смог ей заняться.
После этого этот объект отправляется в рендер:
SDL_RenderCopy(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect)
SDL_RenderPresent(SDL_Renderer* renderer)
Ну вот, с теорией разобрались, пора к практике!
Вариант номер РАЗ:
Этот метод завязан на библиотеках SDL_mixer.h
и SDL_Image.h
, так что не орите на меня удивляйтесь, когда на вас польются ошибки подключив только SDL2.
Его особенность в том, что он без велосипедов передает альфа-канал.
SDL_Texture *player = IMG_LoadTexture(ren,"..\res\player.png");
Теперь у нас есть текстура персонажа готовая показаться на экране. Но перед этим нам нужно еще создать фон.
Вариант номер ДВА:
Фон мы создадим с использованием чистого SDL. Просто потому, что мы можем!
SDL_Surface *BMP_background = SDL_LoadBMP("..\res\background.bmp");
if (BMP_background == nullptr){
std::cout << "SDL_LoadBMP Error: " << SDL_GetError() << std::endl;
return 1;
}
SDL_Texture *background = SDL_CreateTextureFromSurface(ren, BMP_background);
SDL_FreeSurface(BMP_background); //Очищение памяти поверхности
if (player == nullptr){
std::cout << "SDL_CreateTextureFromSurface Error: " << SDL_GetError() << std::endl;
return 1;
}
Работая с чистым SDL никогда нельзя забывать делать проверки на ошибки!
И вот наконец то, УРА, пришло время вывести это на экран!
SDL_RenderClear(ren); //Очистка рендера
SDL_RenderCopy(ren,background,NULL,&background_RECT); //Копируем в рендер фон
SDL_RenderCopy(ren, player, NULL, &player_RECT); //Копируем в рендер персонажа
SDL_RenderPresent(ren); //Погнали!!
События
Думаю не стоит объяснять что из 2-х картинок игры не выйдет.
Время добавить немного динамики к нашему чуду!
Для начало нужно создать парочку бесконечных циклов, которые работают пока есть события и нет выхода:
SDL_Event event;
bool quit = false;
while(!quit)
while(SDL_PollEvent(&event))
{
SDL_PumpEvents(); // обработчик событий.
}
Пора заняться непосредственно событиями.
В SDL есть 2 способа считывать события с контроллеров:
- Первым способом мы реализуем работу с мышкой.
Но для начала добавим пару строк перед циклами:SDL_Texture* ARRAY_textures[2] = {background, player}; SDL_Rect* ARRAY_rect[2] = {&background_RECT, &player_RECT}; int ARRAY_texturesState[2] = {1,1};
Это нам пригодится чтобы иметь возможность отображать или не отображать ту или иную текстуру.
А теперь вставляем этот код во внутренний цикл.Код не ТОРТ, он правда тут!if(event.type == SDL_QUIT) quit=true; if(event.type == SDL_MOUSEBUTTONDOWN) { if(event.button.button == SDL_BUTTON_LEFT && event.button.x <=10 && event.button.y <=10) quit = true; if(event.button.button == SDL_BUTTON_RIGHT) ARRAY_texturesState[1] = 1; if((event.button.button == SDL_BUTTON_LEFT) && (event.button.x >= player_RECT.x) && (event.button.y >= player_RECT.y) && (event.button.x <= player_RECT.w + player_RECT.x) && (event.button.y <= player_RECT.h + player_RECT.y)) ARRAY_texturesState[1] = 0; }
Думаю это не требует пояснений, если вы внимательно читали и занимаетесь программированием больше 21 дня, но все же уточню, что
event.button.button
ждет специальный флаг SDLя, который вы сможете легко найти на вики, аevent.type
ждет флага о типе события, полный список которых находится все там же!Мы уже можем закрыть окно кликом по левому верхнему углу экрана! И даже более того, мы можем убрать и вернуть персонажа когда захотим просто кликнув по нему!
Да, я тоже чувствую, как ощущение власти начинает нас захлестывать, но не время останавливаться, впереди еще клавиатура! - Второй способ:
Для начала перед циклом нам нужно создать одну константу:const Uint8 *keyboardState = SDL_GetKeyboardState(NULL);
Она нужна что бы отслеживать состояния кнопок.
Еще вне нашей главной функции надо добавить много-много кода, который сделает нашу программу более структурированной.
(Вижу кто то уже начал писать о том, что нужно пользоваться классами и библиотеками, но я хочу напомнить, что это туториал и будет не хорошо, если человек запутается собирая код, поэтому будем писать все максимально просто. Приношу свои извинения тем, чьи чувства я задел!)Много-много кодаvoid move_UP (SDL_Renderer* render, SDL_Texture* texture, SDL_Rect &destrect, int offset = 5) { destrect.y -= offset; SDL_RenderClear(render); SDL_RenderCopy(render, texture,NULL,&destrect); } void move_DOWN (SDL_Renderer* render, SDL_Texture* texture, SDL_Rect &destrect, int offset = 5) { destrect.y += offset; SDL_RenderClear(render); SDL_RenderCopy(render, texture,NULL,&destrect); } void move_LEFT (SDL_Renderer* render, SDL_Texture* texture, SDL_Rect &destrect, int offset = 5) { destrect.x -= offset; SDL_RenderClear(render); SDL_RenderCopy(render, texture,NULL,&destrect); } void move_RIGHT(SDL_Renderer* render, SDL_Texture* texture, SDL_Rect &destrect, int offset = 5) { destrect.x += offset; SDL_RenderClear(render); SDL_RenderCopy(render, texture,NULL,&destrect); } void render_UPDATE(SDL_Renderer* render, SDL_Texture* texture[], SDL_Rect* destrect[], int states[]) { SDL_RenderClear(render); if(states[0]) SDL_RenderCopy(render, texture[0],NULL,destrect[0]); if(states[1]) SDL_RenderCopy(render, texture[1],NULL,destrect[1]); }
И теперь возвращаемся во внутренний цикл и добавляем еще много-много кода:
Много-много кодаif((keyboardState[SDL_SCANCODE_UP])||(keyboardState[SDL_SCANCODE_W])) move_UP(ren,player,player_RECT); if((keyboardState[SDL_SCANCODE_DOWN])||(keyboardState[SDL_SCANCODE_S])) move_DOWN(ren,player,player_RECT); if((keyboardState[SDL_SCANCODE_LEFT])||(keyboardState[SDL_SCANCODE_A])) move_LEFT(ren,player,player_RECT); if((keyboardState[SDL_SCANCODE_RIGHT])||(keyboardState[SDL_SCANCODE_D])) move_RIGHT(ren,player,player_RECT); //ZOOM---------------------------------------------------------------- if(keyboardState[SDL_SCANCODE_KP_PLUS]) { TESTtexture_SCALE += 0.02; player_RECT.h = player_HEIGH * TESTtexture_SCALE; player_RECT.w = player_WIGHT * TESTtexture_SCALE; } if(keyboardState[SDL_SCANCODE_KP_MINUS]) { TESTtexture_SCALE -= 0.02; player_RECT.h = player_HEIGH * TESTtexture_SCALE; player_RECT.w = player_WIGHT * TESTtexture_SCALE; }
Особо нового тут ничего нет. Единственное что добавилось это конструкция
keyboardState[flag]
.
Такая конструкция возвращаетtrue
в случае, если кнопка нажата иfalse
в обратом.
Список флагов… ТЫ УЖЕ ГОВОРИЛ МНОГО РАЗ!
Осталось вывести полученный результат на экран. Для этого добавляем в цикл:
render_UPDATE(ren, ARRAY_textures, ARRAY_rect, ARRAY_texturesState); //Написанная нами функция обновления рендера
SDL_RenderPresent(ren);
Закрываем цикл!
И в итоге нам осталось только завершить нашу программу.
Занавес!
Перед тем как все закончить нам нужно удалить наши текстуры из памяти.
SDL_DestroyTexture(player);
SDL_DestroyTexture(background);
И теперь можно смело завершать работу SDL и программы:
SDL_DestroyRenderer(ren);
SDL_DestroyWindow(win);
SDL_Quit();
return 1;
Финал, овации! Мы написали первую программу на SDL2! С чем я нас поздравляю!
P.S.
Что бы создать такую элементарную программу у меня ушло 2 дня. В интернете настолько мало мануалов по SDL2, что проще застрелиться чем что то найти.
Очень надеюсь, что Вам эта статья была полезна и этот монстр не отберет у вас так много времени, как у меня.
Автор: Cripos
void move_LEFT (SDL_Renderer* render, SDL_Texture* texture, SDL_Rect &destrect, int offset = 5)
{
destrect.x -= offset;
SDL_RenderClear(render);
SDL_RenderCopy(render, texture,NULL,&destrect);
Ты одно и тоже сто раз применяешь – это супертормоза.
Кусок
SDL_RenderClear(render);
SDL_RenderCopy(render, texture,NULL,&destrect);
вынеси из функций и применяй его 1 раз в самом низу кода.