SDL 2: Основы

от автора

К сожалению, даже на официальной вики почти не возможно найти каких либо примеров использования 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, что проще застрелиться чем что то найти.
Очень надеюсь, что Вам эта статья была полезна и этот монстр не отберет у вас так много времени, как у меня.

ссылка на оригинал статьи http://habrahabr.ru/post/201392/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *