Перевод SDL Game Framework Series. Часть 2 — SDL Coordinates and Bliting

от автора

Взяв за основу первый урок, мы будем углубляться в мир поверхностей SDL. Как я уже говорил, SDL поверхности, в основном, это изображения, сохраненные в памяти. Представьте себе, что у нас есть пустое окно размером 320×240 пикселей. В системе координат SDL, окно представлено следующим образом:



Эта система координат отличается от той к которой вы привыкли (я про декартову). Но основное отличие между этими системами в том, что координата 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 и добавьте следующее:

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 и добавьте:

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 и добавьте следующий прототип функции:

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 и добавьте следующее:

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:

CApp.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.cpp

CApp::CApp() {     Surf_Test = NULL;     Surf_Display = NULL;       Running = true; } 

И помните об очистке!

CApp_OnCleanup.cpp

#include "CApp.h"   void CApp::OnCleanup() {     SDL_FreeSurface(Surf_Test);     SDL_FreeSurface(Surf_Display);     SDL_Quit(); } 

Настало время уже что-то загрузить. Откройте CApp_OnInit.cpp и приведите его к такому виду:

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 и добавьте следующее:

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.

Двинемся дальше! Мы потешили себя тем, что отобразили, наконец, в окошке наше первое изображение, но очень часто нам необходимо отобразить всего лишь его часть, как пример — тайлсеты, указанные ниже:

Tilesets

Т.е. имея всего одно изображение, нам нужно нарисовать только его часть. Откройте CSurface.h, и добавьте следующий код:

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, а не заменяем уже имеющуюся! Вы же в курсе про перегрузку функций?):

CSurface.cpp

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 пикселей.

CApp_OnRender.cpp

#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/


Комментарии

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

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