- PVSM.RU - https://www.pvsm.ru -
К сожалению, даже на официальной вики почти не возможно найти каких либо примеров использования SDL2.x, что уж говорить о рунете. Пытаясь разобраться, я нашел всего лишь пару статей, которые не покрыли и трети моих вопросов.
SDL 2.x существенно отличается от 1.x и даже, если в прошлом вам приходилось с ним работать — теперь вы рискуете ничего не понять.
Сегодня мы напишем простенькую программу выводящую на экран фон и зумируемый спрайт персонажа перемещающегося с помощью WASD и стрелок. + разберемся как в 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)
Тут все конечно и так ясно, но на всякий случай объясню что к чему.
0,0
displayMode
.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)
-1
, то рендер будет использовать первый подходящий драйвер.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
о нем вы можете забыть. Но глупо с чем то работать не имея ни малейшего представления о том как оно устроено.
Как пример: чистый 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! С чем я нас поздравляю!
Что бы создать такую элементарную программу у меня ушло 2 дня. В интернете настолько мало мануалов по SDL2, что проще застрелиться чем что то найти.
Очень надеюсь, что Вам эта статья была полезна и этот монстр не отберет у вас так много времени, как у меня.
Автор: Cripos
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/tutorial/47878
Ссылки в тексте:
[1] Источник: http://habrahabr.ru/post/201392/
Нажмите здесь для печати.