FullStack JS Developer
0,0
рейтинг
27 января 2013 в 06:50

Разработка → Перевод SDL Game Framework Series. Часть 2 — SDL Coordinates and Bliting перевод tutorial

Взяв за основу первый урок, мы будем углубляться в мир поверхностей SDL. Как я уже говорил, SDL поверхности, в основном, это изображения, сохраненные в памяти. Представьте себе, что у нас есть пустое окно размером 320x240 пикселей. В системе координат 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 и в итоге получаем отображаемый регион в виде квадрата 50x50 пикселей.



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 пикселей от верха и левого края экрана:



Ссылки на исходный код:


Ссылки на все уроки:
Перевод: Tim Jones
Артем Комаров @m0sk1t
карма
25,5
рейтинг 0,0
FullStack JS Developer
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (4)

  • 0
    Серия статей хороша, жалко, что это перевод, дал бы пару советов.
    Главная проблемам данного фреймворка его привязка к sdl, а значит у него имеются все недостатки SDL, например отсутствие поддержки OpenGL Core Profile, А ведь все что нужно, ввести свою систему событий и сделать перенаправление с событие SDL.
    В дальнейшем можно без больших затрат использовать ваш фреймворк например с GLFW или темже GLUT (freeGLUT), ну или на winapi руками написать что-то свое.
    • 0
      Спасибо! По сути вашего вопроса:
      1. Фрэймворк не мой, а автора серии;
      2. Как видно из названия цикла статей — затрагивается SDL, и только SDL. OpenGL это отдельный, большой разговор;
      3. Ничто не мешает читателю после прочтения этого цикла в дальнейшем думать в направлении связки SDL&OpenGL, что многие разработчики игр и делают;
      4. От советов не откажусь никогда! Но мне пока рановато пробовать OpenGL…
      Вопрос к вам: что за OpenGL Core Profile? Быстрым гуглением нашел это.
      • 0
        Core Profile — это новый стандарт OpenGL у которого отрезали все лишнее, скажем так, и там теперь все на шейдерах (микропрограммах для видеокарты). Версии 3.0, 4.0, ES2. С выходом стандарта немного изменился порядок создания окна и поэтому SDL не может создать нужное нам окно, а так как она кросс платформенная, то не дает нам доступ к управлению окном, а значит и сами мы ничего не можем сделать. Таже учесть постигла и GLUT (теперь надо freeGLUT использовать)
        В теории новый SDL2 поддерживает openGL 3,4 но у него нет релиза
        Note: These are not official releases and may be unstable!

        Так что вот такие дела.

        А статья да хорошая и очень для начинающих поучительная.
        • 0
          SDL 2.0 таки вышел =) Ура, товарищи! И да, я готов продолжить переводить, и также писать что-то свое, так что буду ждать ваших советов.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.