Познакомьтесь, SDL.

Урок 2. "Загружаем и отображаем картинки".

Урок 1 Содержание Урок 3

Приветствую вас во втором уроке. Если вы внимательно изучили первый, то это очень хорошо. Сегодня мы научимся отображать картинки в ваших программах. Этот урок очень простой и я постараюсь объяснить каждую строчку нашей следующей программы. Мы нарисуем приятный бэкграунд, нарисуем забавное существо и будем передвигать его с помощью клавиатуры.
Начнем с включения заголовочных файлов. Их должно быть три: stdio.h, stdlib.h и SDL.h. Файл stdlib.h нужен для функции atexit().

#include <stdio.h> 
#include <stdlib.h>  
#include "SDL.h" 

После этого мы объявим три глобальных поверхности SDL_Surface. Как вы знаете, глобальные означают, что они могут использоваться всеми функциями внутри файла. Чтобы объявить что-либо глобально, просто поместите код объявления в верху файла, перед кодом функций. Также нам понадобятся две переменных типа int: xpos и ypos. Они будут хранить координаты положения нашего существа.
SDL_Surface *back;
SDL_Surface *image;
SDL_Surface *screen;
int xpos=0, ypos=0;
Функция InitImages() нужна, чтобы загружать изображения из файлов (BMP) на наши поверхности SDL_Surface. Позже мы вызовем эту функцию из функции main(). Внутри нашей функции мы используем SDL_LoadBMP. Эта функция принимает имя файла изображения в качестве параметра и возвращает указатель на структуру SDL_Surface. Мы загрузим две картинки: одну в SDL_Surface back, которая будет отображаться в качестве фона, а другую в SDL_Surface image, которая будет нашим существом.
/* ------------------------------------------- */
void InitImages(){

 back=SDL_LoadBMP("bg.bmp");
 image=SDL_LoadBMP("image.bmp"); 

}

А сейчас небольшое отступление. Вы узнаете о поддержке графических изображений в SDL.
 
Библиотекой SDL поддерживается только формат BMP. Вся эта поддержка состоит только из двух функций SDL_LoadBMP, которую мы использовали и SDL_SaveBMP, которая позволяет сохранять поверхность в файл BMP. Для этой функции передавайте в качестве параметра поверхность, которую надо сохранить и имя файла, куда сохранять. Вот ее прототип:
int SDL_SaveBMP(SDL_Surface *surface, const char *filename);
Функция возвращает 0 при успехе и -1 при ошибке. Но иногда неудобно использовать формат BMP. Такие файлы занимают много места. Существует библиотека SDL Image, которая позволяет очень просто работать с изображениями очень многих форматов. Загрузить эту библиотеку можно все на том же сайте www.libsdl.org

 
Следующие две функции выводят изображение на экран. Обе функции называются DrawIMG. Первая из них принимает в качестве параметра поверхность, которую нужно отобразить и координаты в которых это изображение появится на экране. Для этого нам пригодится функция SDL_BlitSurface(), с помощью которой одну поверхность можно отобразить на другой. Вот прототип функции, взятый из документации SDL:
int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect);
Здесь src это поверхность, которую мы хотим отобразить, а dst это поверхность на которой отобразится src. Еще два параметра - это указатели на структуру SDL_Rect: srcrect и dstrect. Эта структура содержит 4 16-битных целых числа: x, y, w(ширина) и h(высота). srcrect указывает какую часть исходной поверхности отобразить, а dstrect задает координаты принимающей поверхности, в которых отобразится исходная. Если в качестве второго параметра NULL, то все исходное изображение будет отображено целиком. В структуре dstrect элементы ширина (w) и высота (h) не обрабатываются функцией и не имеют никакого значения. Вот код первой функции DrawIMG
/* ------------------------------------------- */
void DrawIMG(SDL_Surface *img, int x, int y){

 SDL_Rect dest;
 dest.x = x;
 dest.y = y;

 SDL_BlitSurface(img, NULL, screen, &dest);

}

Вторая функция использует параметр srcrect. Элементы x и y структуры srcrect задают координату откуда начинать копирование изображения, а ширина и высота определяют размеры исходного копируемого изображения. То есть, мы рисуем только часть изображения. Взгляните на код функции и постарайтесь понять, что происходит:
/* ------------------------------------------- */
void DrawIMG(SDL_Surface *img, int x, int y, int w, int h, int sx, int sy){

 SDL_Rect dest;
 dest.x = x;
 dest.y = y;

 SDL_Rect src;
 src.x = sx;
 src.y = sy;
 src.w = w;
 src.h = h;
 
 SDL_BlitSurface(img, &src, screen, &dest);

}

Теперь разберем функцию DrawBG(), с помощью которой будем рисовать бэкграунд (задний фон). Впоследствии, в функции main перед началом главного цикла мы вызовем ее. Как вы помните с предыдущего урока, нам не надо блокировать экран для отображения картинок. В этой функции мы просто копируем фоновое изображение на экранную поверхность SDL_Surface *screen. Нам не понадобится функция обновления экрана навроде SDL_Flip(), потому что бэкграунд нужно нарисовать только один раз. Заметьте, что копировать изображение поверхности можно на любую поверхность, а не только на экран. Вот код для рисования заднего фона:
/* ------------------------------------------- */
void DrawBG(){

 DrawIMG(back, 0, 0);

}

Далее следует функция рисования сцены. Сначала мы копируем на экран часть бэкграунда, на котором находится наше существо (по сути это прямоугольник). Затем в этом же положении копируем на экран изображение существа. А затем обновляем экран с помощью SDL_Flip() (так как все рисование происходит в невидимом буфере screen, который мы выводим в итоге на экран). С этого места подробнее. Стандартный алгоритм для анимации движения таков:
И так продолжается пока не пердусмотрено иное действие. Мы будем придерживаться этого алгоритма. Изображение нашего существа имеет размеры 128х128. Координаты текущего положения буудт изменяться только на 1 точку, т.е. при одном нажтии на клавишу изменяем на 1 точку. Участок фона, которым мы будем затирать изображение будет немного больше в размерах и это здорово облегчит нашу задачу. То есть если наше изображение размерами 128х128, то участок фона должен быть (2+128+2)х(2+128+2) или 132х132. Вот что должно в итоге получиться:
/* ------------------------------------------- */
void DrawScene(){

 DrawIMG(back, xpos-2, ypos-2, 132, 132, xpos-2, ypos-2);
 DrawIMG(image, xpos, ypos);
 
 SDL_Flip(screen);

}

Все, что осталось, это функция main. Создадим для начала указатель на переменную типа Uint8, который будем использовать для определения нажатия клавиш клавиатуры. После этого происходит инициализация, о которой вы читали в первом уроке.
int main(int argc, char *argv[]){ 
 
 Uint8* keys;
 
 if ( SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0 ){ 
   printf("Unable to init SDL: %s\n", SDL_GetError()); 
   exit(1); 
 } 
 
 atexit(SDL_Quit); 

 SDL_WM_SetCaption("SDL Gfx Example #2","ex2");
 SDL_WM_SetIcon(SDL_LoadBMP("icon.bmp"), NULL);
 
 screen=SDL_SetVideoMode(640,480,32,SDL_HWSURFACE|SDL_DOUBLEBUF); 
 if ( screen == NULL ){ 
   printf("Unable to set 640x480 video: %s\n", SDL_GetError()); 
   exit(1); 
 }

Затем загружаем изображения и рисуем фон
 
 InitImages();
 DrawBG();


Затем начинаем главный цикл и проверяем на нажатие клавишу ESCAPE. Все как в первом уроке:
 int done=0; 
 while(done == 0){ 
   
   SDL_Event event; 
   
   while ( SDL_PollEvent(&event) ){ 
     if ( event.type == SDL_QUIT ){ done = 1; } 
     if ( event.type == SDL_KEYDOWN ){
       if ( event.key.keysym.sym == SDLK_ESCAPE ){ done = 1; } 
     } 
   }

Теперь надо получить состояние клавиш клавиатуры. Помните нашу переменную Uint8 *keys? Так вот, функция SDL_GetKeyState() возвращает указатель на массив Uint8. Каждый элемент массива содержит состояние определенной клавиши клавиатуры (нажата или нет). Мы не проверяем все клавиши в event-цикле (там где проверка на нажатие ESCAPE), потому что SDL_PollEvent реагирует только на события (например KEYDOWN), но не будет реагировать на удерживание клавиши. После этого рисуем сцену:
   keys = SDL_GetKeyState(NULL);
   if(keys[SDLK_UP]){ ypos -= 1; }
   if(keys[SDLK_DOWN]){ ypos += 1; }
   if(keys[SDLK_LEFT]){ xpos -= 1; }
   if(keys[SDLK_RIGHT]){ xpos += 1; }
   
   DrawScene();
 }
Когда выходим из цикла, то программа завершает работу:
 return 0;
  
}

Вот и все! Вы конечно еще не профессионалы, но по крайней мере постигли основы и знаете как это делать. Исходники для этого урока можно скачать здесь.


Урок 1 Содержание Урок 3

©opyleft PLG, 2003.