Эта система координат отличается от той к которой вы привыкли (я про декартову). Но основное отличие между этими системами в том, что координата Y «растет» вниз. Понимание системы SDL координат важно, чтобы правильно рисовать изображения на экране, так что уж вникните хорошенько.
Так как у нас уже есть подготовленная и настроенная поверхность (Surf_Display), нам осталось только найти способ отрисовки изображений. Этот способ называется блитированием (от англ. blitting — перемещение группы бит из одного места в другое, в нашем случае подразумевается перенос изображения (или его части) поверх другого), т.е. своего рода наложение. Но прежде чем мы сможем это сделать, мы должны найти ещё и способ загрузить эти изображения в память. SDL предлагает простую функцию, чтобы осуществить задуманное — SDL_LoadBMP (примечание: SDL_LoadBMP предоставляет возможность загрузки изображений только в формате *.BMP, как видно из её названия. Чтобы загружать изображения других форматов, к проекту нужно подключить библиотеку SDL_image, как справедливо заметил в комментариях товарищ alrusdi в первом уроке, и использовать функцию IMG_Load). Пример кода может выглядеть следующим образом:
SDL_Surface* Surf_Temp; if((Surf_Temp = SDL_LoadBMP("mypicture.bmp")) == NULL) { //Обшибка! }
Здесь всё довольно просто, SDL_LoadBMP принимает в качестве параметра всего один аргумент — путь до файла, который вы хотите загрузить, а возвращает она поверхность, содержащую указанное изображение. Если функция возвращает NULL, то либо файл не найден, либо поврежден, либо возникли другие, более сложные ошибки. К сожалению, в ущерб эффективности, этот метод не обеспечивает полное покрытие всевозможных ошибок загрузки. Очень часто загруженное изображение не соответствует попиксельному формату той поверхности, в которую мы его загружаем. Таким образом во время отображения возможна потеря производительности, цветов изображения, и т.д. (важно чтобы подготавливаемая поверхность и загружаемое изображение подходили друг другу по всем параметрам, т.е. (утрируя) размер коробки подходил бы размерам груза). К счастью в SDL существует быстрый и безболезненный обход этой проблемы — SDL_DisplayFormat. Эта функция настраивает уже загруженное изображение, и возвращает новую поверхность, подходящую под формат отображаемой.
Теперь вам необходимо открыть проект, созданный в предыдущем уроке и добавить два файла: CSurface.h и CSurface.cpp. Откройте CSurface.h и добавьте следующее:
#ifndef _CSURFACE_H_ #define _CSURFACE_H_ #include <SDL/SDL.h> class CSurface { public: CSurface(); public: static SDL_Surface* OnLoad(char* File); }; #endif
Тем самым мы создали простую функцию OnLoad, которая будет загружать для нас поверхность. Теперь откройте CSurface.cpp и добавьте:
#include "CSurface.h" CSurface::CSurface() { } SDL_Surface* CSurface::OnLoad(char* File) { SDL_Surface* Surf_Temp = NULL; SDL_Surface* Surf_Return = NULL; if((Surf_Temp = SDL_LoadBMP(File)) == NULL) { return NULL; } Surf_Return = SDL_DisplayFormat(Surf_Temp); SDL_FreeSurface(Surf_Temp); return Surf_Return; }
Итак, парочка вещей, на которые стоит обратить внимание:
1. Всегда обнуляйте свои указатели прежде чем как-либо их использовать (NULL или «0» неважно). Это поможет избежать туевой хучи самых разных проблем и ошибок;
2. Помните, что SDL_DisplayFormat возвращает новую поверхность на основе старой, поэтому не стоит забывать освободить ресурсы, занимаемые той старой поверхностью. В противном случае мы будем наблюдать поверхность «блуждающую» в памяти так как ей заблагорассудится.
Теперь у нас есть способ загрузки поверхностей в память, но нам также нужен способ, чтобы отобразить их на другие поверхности. Так же как и для загрузки изображений, у SDL есть функция и для этого: SDL_BlitSurface. Возможно её будет не так просто использовать как SDL_LoadBMP, но не стоит пугаться. Откройте CSurface.h и добавьте следующий прототип функции:
#ifndef _CSURFACE_H_ #define _CSURFACE_H_ #include <SDL/SDL.h> class CSurface { public: CSurface(); public: static SDL_Surface* OnLoad(char* File); static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y); }; #endif
Снова откройте CSurface.cpp и добавьте следующее:
#include "CSurface.h" CSurface::CSurface() { } SDL_Surface* CSurface::OnLoad(char* File) { SDL_Surface* Surf_Temp = NULL; SDL_Surface* Surf_Return = NULL; if((Surf_Temp = SDL_LoadBMP(File)) == NULL) { return NULL; } Surf_Return = SDL_DisplayFormat(Surf_Temp); SDL_FreeSurface(Surf_Temp); return Surf_Return; } bool CSurface::OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y) { if(Surf_Dest == NULL || Surf_Src == NULL) { return false; } SDL_Rect DestR; DestR.x = X; DestR.y = Y; SDL_BlitSurface(Surf_Src, NULL, Surf_Dest, &DestR); return true; }
Прежде всего, давайте взглянем на аргументы, которые передаются в функцию OnDraw. Мы видим две поверхности, и две переменные типа int. Первая поверхность берется в качестве базовой (помните доску в первом уроке?), т.е. той на которую мы и будем всё отображать в дальнейшем. Соответственно вторая поверхность — та, которую мы будем накладывать на базовую (а вот и наши стикеры). В принципе, мы просто размещаем Surf_Src поверх Surf_Dest, вот и весь секрет. X и Y — переменные, которые обозначают координаты места на поверхности Surf_Dest, в которое мы будем отображать Surf_Src.
В начале функции мы должны убедиться, что у нас есть поверхности, в противном случае мы возвращаем false. Далее, мы создаем переменную типа SDL_Rect. Это структура SDL, которая состоит из четырех свойств: X, Y, W, H. Вы уже конечно догадались что она то как раз и задает параметры отображаемого региона поверхности. Пока нас интересуют только координаты места в которое мы будем отображать прямоугольник, и нам наплевать на его размер. Итак, далее мы присваиваем переданные в функцию X, Y координаты структуре отображаемого региона. Если вам интересно, что же за параметр NULL затесался в нашей SDL_BlitSurface (да, автор, нам интересно!), это еще один параметр типа SDL_Rect. Мы вернемся к этому чуть позже.
Позже наступило! Думаю что никто не обидится если мы разберем сигнатуру SDL_BlitSurface чуть раньше. Вкратце поясню: нам не всегда нужно отображать всю поверхность поверх другой, есть много случаев, когда требуется выбрать какую-то часть изображения (например у нас есть тайлсет (от англ. tileset — набор изображений, попросту множество картинок, собранных в одном изображении) и нужно выбрать из него определённый квадратик с текстурой или персонажем, и т.д.). Так вот
int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect);
принимает в качестве параметров по порядку, слева направо:
- поверхность, которую будем накладывать;
- параметры региона отображения накладываемой поверхности (т.е. какую её часть мы будем отображать);
- поверхность, на которую будем накладывать;
- ну и, соответственно, параметры региона базовой поверхности, в который будем накладывать.
Думаю теперь всё стало более-менее прозрачно и понятно.
В завершении функции мы отрисовываем настроенные поверхности и возвращаем true.
Теперь, чтобы убедиться, что все работает, давайте создадим тестовую поверхность. Откройте CApp.h, и добавьте новую поверхность, и включите созданный нами заголовочный файл CSurface.h:
#ifndef _CAPP_H_ #define _CAPP_H_ #include <SDL/SDL.h> #include "CSurface.h" class CApp { private: bool Running; SDL_Surface* Surf_Display; SDL_Surface* Surf_Test; public: CApp(); int OnExecute(); public: bool OnInit(); void OnEvent(SDL_Event* Event); void OnLoop(); void OnRender(); void OnCleanup(); }; #endif
Также в конструкторе не забудьте сначала обнулить наши поверхности:
CApp::CApp() { Surf_Test = NULL; Surf_Display = NULL; Running = true; }
И помните об очистке!
#include "CApp.h" void CApp::OnCleanup() { SDL_FreeSurface(Surf_Test); SDL_FreeSurface(Surf_Display); SDL_Quit(); }
Настало время уже что-то загрузить. Откройте CApp_OnInit.cpp и приведите его к такому виду:
#include "CApp.h" bool CApp::OnInit() { if(SDL_Init(SDL_INIT_EVERYTHING) < 0) { return false; } if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) { return false; } if((Surf_Test = CSurface::OnLoad("myimage.bmp")) == NULL) { return false; } return true; }
Убедитесь в том что у вас действительно имеется файл myimage.bmp. Если нет — скачайте или нарисуйте сами и положите его в каталог с исполняемым файлом вашей игры. Откройте CApp_OnRender.cpp и добавьте следующее:
#include "CApp.h" void CApp::OnRender() { CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0); SDL_Flip(Surf_Display); }
Обратите внимание на новую функцию SDL_Flip. Она обновляет буфер и отображает Surf_Display на экран. Это называется двойной буферизацией. Она подготавливает созданные поверхности сначала в памяти, а затем отображает подготовленное на экран. Если бы мы не использовали её, то наблюдали бы мерцающий экран. Помните флаг SDL_DOUBLEBUF, который мы указывали при создании поверхности? Он-то как раз и включает режим двойной буферизации.
Теперь вы можете откомпилировать проект, и убедиться, что все работает правильно. Вы должны увидеть изображение в верхнем левом углу окна. Если да, то поздравляю, вы еще на один шаг ближе к реальной игре. Если нет, то убедитесь в том, что у вас myimage.bmp лежит в той же папке, что и исполняемый файл, а также в том, что он нормально открывается в просмотрщике графики. Вот что получилось у меня:
(И да, я немного схитрил видоизменил код и загрузил свой аватар в формате *.PNG, используя IMG_Load. Советую вам тоже поэкпериментировать с этой функцией, да и с другими тоже. Дерзайте и у вас всё получится!). Если у вас появляется сообщение deprecated conversion from string constant to ‘char*’ -wwrite-strings
необходимо изменить сигнатуру функции OnLoad(char* File) на OnLoad(const char* File) в CSurface.h и соответственно в CSurface.cpp.
Двинемся дальше! Мы потешили себя тем, что отобразили, наконец, в окошке наше первое изображение, но очень часто нам необходимо отобразить всего лишь его часть, как пример — тайлсеты, указанные ниже:
![](http://habrastorage.org/storage2/398/cce/ad1/398ccead19a3051e416c6d01c3e03a8c.png)
Т.е. имея всего одно изображение, нам нужно нарисовать только его часть. Откройте CSurface.h, и добавьте следующий код:
#ifndef _CSURFACE_H_ #define _CSURFACE_H_ #include <SDL/SDL.h> class CSurface { public: CSurface(); public: static SDL_Surface* OnLoad(char* File); static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y); static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H); }; #endif
Откройте CSurface.cpp, и добавьте следующую функцию (Важно, мы добавляем вторую функцию OnDraw, а не заменяем уже имеющуюся! Вы же в курсе про перегрузку функций?):
bool CSurface::OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H) { if(Surf_Dest == NULL || Surf_Src == NULL) { return false; } SDL_Rect DestR; DestR.x = X; DestR.y = Y; SDL_Rect SrcR; SrcR.x = X2; SrcR.y = Y2; SrcR.w = W; SrcR.h = H; SDL_BlitSurface(Surf_Src, &SrcR, Surf_Dest, &DestR); return true; }
Видите, это в основном та же функция, как и раньше, за исключением того, мы добавили еще один SDL_Rect. Этот регион позволяет указать, какие пиксели из накладываемой поверхности нужно скопировать на основную. Теперь вкупе с координатами мы указываем ещё и оставшиеся два параметра — ширину и высоту 0, 0, 50, 50 и в итоге получаем отображаемый регион в виде квадрата 50×50 пикселей.
#include "CApp.h" void CApp::OnRender() { CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0); CSurface::OnDraw(Surf_Display, Surf_Test, 100, 100, 0, 0, 50, 50); SDL_Flip(Surf_Display); }
А вот частичка моего аватара с отступом в 100 пикселей от верха и левого края экрана:
Ссылка на исходный код:
Ссылки на все уроки:
ссылка на оригинал статьи http://habrahabr.ru/post/167035/
Добавить комментарий