Pull to refresh

Игра на SDL — легко

Reading time4 min
Views26K
Наверняка, каждому да приходила идея написать какую-нибудь графическую игру или программу. Но не всегда есть время или желание осилить графику в программировании. Надеюсь, это статья поможет вам научиться писать(или просто написать) простую, но очень забавную игру.
Finished project


Это будет немалоизвестная Flood-It. Идея крайне проста:
Требуется закрасить все поле в один цвет, при том, что красить можно только всю часть одного цвета, прилегающую к левому верхнему квадрату.

Инициализация



Каждая программа на SDL должна начинаться с очень важного дела — инициализации нужных подсистем и задания основных параметров. В нашем примере нам понадобится проинициализировать только видео.
Сей процесс проходит с помощью команды SDL_Init
которая принимает в качестве параметра константы(разделенные логическим «или»), отвечающие за ту или иную часть. Так, для видео-подсистемы нужно написать
SDL_Init(SDL_INIT_VIDEO)



Если вам нужные другие системы можно воспользоваться этим списком:

  • SDL_INIT_AUDIO
  • SDL_INIT_VIDEO
  • SDL_INIT_TIMER
  • SDL_INIT_CDROM
  • SDL_INIT_JOYSTICK
  • SDL_INIT_EVERYTHING — инициализация всех подсистем
  • SDL_INIT_NOPARACHUTE — не ловить фатальные сигналы
  • SDL_INIT_EVENTTHREAD — запускать менеджер событий в отдельном потоке


Ох, если б и тут все было так просто. На самом деле не всегда это может завершиться успешно, и нужно аварийно завершить выполнение в случае неудачи. И для этого нужно всего-лишь следить за значением, возвращаемым SDL_Init — в случае неудачи всегда возвращается отрицательное число.

if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        printf( "Unable to init SDL");
        return 1;
    }



Не слишком информативно. Пользователям(и нам) полезно знать, что же привело к проблеме. SDL_GetError() всегда вернет текст проблемы. Воспользовавшись этой функцией сделаем сообщение об ошибках приятнее

if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
{
        printf( "Unable to init SDL: %s", SDL_GetError());
        return 1;
}



Поверхности



Ну вот, теперь, когда мы готовы начать творить, надо разобраться, как это происходит. Изображения хранятся на поверхностях(surface'ах), с которыми можно делать много полезных вещей — накладывать друг на друга, рисовать на них и так далее.
Для видимой на экране части также потребуется поверхность. Задать эту поверхность нетрудно, для этого есть команда SDL_SetVideoMode, которая установит размер
этой поверхности(а значит, и экрана программы), количество бит на цвет, а так же всякие важные параметры, включающие аппаратное/программное ускорение и многое другое.

Так же есть один полезный параметр SDL_DOUBLEBUF, добавляющий этой поверхности буфер. Все действия не будут отображаться на экране до необходимого момента. Это неплохо ускорит нашу программу(хоть она и совершенно не требовательна, но так приятнее и культурнее).

Итак, создав поверхность следующим образом

SDL_Surface * screen = SDL_SetVideoMode(550, 420, 16, SDL_HWSURFACE | SDL_DOUBLEBUF); /// 550x420 — таким будет размер нашего окна. А цвет будет 16-битным. Видео с аппаратным ускорением из-за параметра SDL_HWSURFACE
// Если не поддерживается аппаратное ускорение, то может SDL_SWSURFACE(программное ускорение)



мы опять можем не получить желаемый результат. Снова выведем сообщение об ошибке и завершим выполнение, если поверхности нет.

If (!screen)
{
    printf("Can't set videomode: %s", SDL_GetError());
    return 1;
}



Изображения



Теперь, когда мы учли все возможные проблемы, ничего плохо не должно произойти.
Игровое поле 14x14 будет состоять из блоков 6 цветов. Каждый блок — ни что иное, как простая картинка. Расстраивает, но SDL может работать только с изображениями формата BMP. Впрочем, это проблема решается подключением библиотеки SDL_Image, но в нашем примере у нас нет колоссальных объемов изображений, и нам хватит BMP.
Загрузить изображение на поверхность можно подобным образом:

int InitImages()
{
    for (int i = 0; i < 6; i++) // У нас будет 6 цветов
    {
        char path[20];
        for (int j = 0; j < 20; j++)
            path[j] = 0;
        sprintf(path, "Block%d.bmp", i);
        block[i] = SDL_LoadBMP(path); // Загружаем BMP по заданному пути в block[i]
        if (!block[i])
            return -1;
    }
     return 1;
}



И если в процессе загрузки изображений что-то пойдет не так, то возвращено будет отрицательное значение(а дальше-то мы знаем, что делать).
Изображение после загрузки представлено уже известным нам типом — surface. Поэтому хранить блоки мы будет в массиве поверхностей block. Похоже, у нас есть есть все, чтобы вывести игровое поле. Кроме одного… Есть поверхность с изображением, есть поверхность экрана, но как перенести на screen другую поверхность(или часть)?

Для этих целей есть SDL_BlitSurface, который может переписать с одной поверхности прямоугольник с координатами x1, y1 и размерами w, h на другую поверхность в место x2, y2. Как-то многовато параметров, да к тому же координаты передаются не простыми числами. Раз выводить мы будем все только на экранную поверхность, то напишем небольшую процедуру

void DrawImage(SDL_Surface * img, int x, int y, int w, int h, int sx, int sy)
{
    SDL_Rect desc; // координаты, куда нужно наложить часть.
    desc.x = x;
    desc.y = y;
    SDL_Rect src; // накладываемый прямоугольник.
    src.x = sx;
    src.y = sy;
    src.w = w;
    src.h = h;
    SDL_BlitSurface(img, &src, screen, &desc);
}



Разберем передаваемые параметры:
img — поверхность, часть которой мы будем добавлять. X, Y — координаты, на которые нужно вывести. SX, SY, W, H — параметры выводимого прямоугольника(координаты угла и размер). Волшебство передаваемых значений заключается в том, что координаты должны быть заданы в типе SDL_Rect(от rectangle), который имеет 4 значения — X, Y, W, H. Ну и после задания координат в новом типе выполняется накладывание.

Теперь точно самое время создать и, главное, нарисовать игровое поле. Создадим процедурку генерации поля GenMap, которая заполняет map случайными числами от 0 до 5(обозначающими цвет).

void GenMap()
{
    for (int i = 0; i < maps; i++)
        for (int j = 0; j < maps; j++)
            map[i][j] = rand() % 6;
}



Добавим ещё процедуру, выводящую по координатам блока изображение на законное место(для блока X, Y им будет являться прямоугольник с 30 * X, 30 * Y, по 30 * (X + 1), 30 * ( Y + 1), для блоков размером 30x30 пикселей).

void DrawBlock(int x, int y)
{
    DrawImage(block[map[x][y]], 30 * x, 30 * y, 30, 30, 0, 0); // блок в координаты 30*x, 30*y размером 30x30
}



А заодно сразу и все поле создадим

void DrawMap()
{
    for (int i = 0; i < maps; i++) // maps(map size) — размер игрового поля.
        for (int j = 0; j < maps; j++)
            DrawBlock(i, j);
}



Время проверить, что у нас вышло. Генерируем поле, выводим, и… Ничего! Как же так? Вроде все правильно, но… Буфер экрана, не стоит забывать его выводить. Это делается просто

    GenMap();
    DrawMap();
    SDL_Flip(screen);



Именно SDL_Flip выполнит нужное действие со screen. И теперь можно увидеть результат:
Результат

Отлично, все работает.
На этом, пожалуй, стоит остановиться в этой статье. Продолжение здесь.
Tags:
Hubs:
+24
Comments13

Articles

Change theme settings