Событийно-ориентированное программирование

Событийно-ориентированное программирование
До этого момента, вы, вероятно, писали программы в «командно-ориентированном» стиле, используя cin и cout. Эта руководство научит вас как проверять и обрабатывать события.


В оригинале автор использует термин «X out». Как я понял под этим подразумевается закрытие приложение, либо при помощи нажатия на значок крестика в заголовке окна, либо при помощи соответствующего клавиатурного сочетания, например, Alt + F4. Я буду называть это действие «закрытие приложения»


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

//Заголовки
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <string>
//Аттрибуты экрана
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;
//Поверхности, которые будут использоваться
SDL_Surface *image = NULL;
SDL_Surface *screen = NULL;

Здесь у нас все тоже самое что и раньше: подключение заголовочных файлов и объявление констант и структур для поверхностей.

//Структура для событий, которые будут использоваться
SDL_Event event;

А вот это уже кое-что новенькое. Структура SDL_Event хранит данные события, которые мы будем обрабатывать.

SDL_Surface *load_image( std::string filename ) {
    //Временное хранилище для загружаемого изображения
    SDL_Surface* loadedImage = NULL;
    //Оптимизированное изображение, которое и будет использоваться
    SDL_Surface* optimizedImage = NULL;
    //Загрузить изображение
    loadedImage = IMG_Load( filename.c_str() );
    //Если изображение загружено
    if( loadedImage != NULL ) {
        //Создать оптимизированное изображение
        optimizedImage = SDL_DisplayFormat( loadedImage );
        //Освободить ресурсы из-под старого изображения
        SDL_FreeSurface( loadedImage );
    }
    //Вернуть оптимизированное изображение
    return optimizedImage;
}
void apply_surface( int x, int y, SDL_Surface* source, SDL_Surface* destination ) {
    //Делаем прямоугольник, для временного хранения смещений
    SDL_Rect offset;
    //Кладем смещения в прямоугольник
    offset.x = x;
    offset.y = y;
    //Копировать поверхность
    SDL_BlitSurface( source, NULL, destination, &offset );
}

Это наши функции для загрузки и копирования поверхностей. По сравнению с прошлой частью руководства ничего не поменялось.

bool init() {
    //Инициализировать все подсистемы SDL
    if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 ) {
        return false;
    }
    //Подготовить экран
    screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT,
        SCREEN_BPP, SDL_SWSURFACE );
    //Если при подготовке экрана произошла ошибка
    if( screen == NULL ) {
        return false;
    }
    //Установить заголовок окна
    SDL_WM_SetCaption( "Event test", NULL );
    //Если все прошло хорошо
    return true;
}

Это наша функция инициализации. Она запускает SDL, подготавливает окно, устанавливает заголовок и возвращает false если происходит какая-нибудь ошибка.

bool load_files() {
    //Загрузить изображение
    image = load_image( "x.png" );
    //Если при загрузке произошла ошибка
    if( image == NULL ) {
        return false;
    }
    //Если все загрузилось
    return true;
}

Это функция загружающая файлы. Она загружает изображения и возвращает false если возникли какие-то проблемы.

void clean_up() {
    //Освободить поверхность
    SDL_FreeSurface( image );
    //Завершаем SDL
    SDL_Quit();
}

Здесь у нас функция очистки, вызываемая при завершении программы. Она освобождает нашу поверхность и завершает работу SDL.

int main( int argc, char* args[] ) {
    //Подтверждение того, что программа ожидает выхода
    bool quit = false;

Теперь мы дошли до нашей функции main

Здесь мы объявляем переменную quit, которая следит за тем, желает ли пользователь покинуть программу. Так как приложение только что запустилось, мы устанавливаем ее в false, иначе завершение работы произойдет немедленно.

    //Инициализация
    if( init() == false ) {
        return 1;
    }
    //Загрузить файлы
    if( load_files() == false ) {
        return 1;
    }

Теперь мы вызываем функции инициализации и загрузки файлов, сделанные ранее.

    //Скопировать поверхность на экран
    apply_surface( 0, 0, image, screen );
    //Обновить экран
    if( SDL_Flip( screen ) == -1 ) {
        return 1;
    }

Затем мы показываем изображение на экране.

//До тех пор, пока пользователь не захотел закрыть приложение
    while( quit == false ) {

Теперь мы запускаем главный цикл программы. Этот цикл выполняется до тех пор, пока пользователь не установит quit в true.

        //Пока есть события для обработки
        while( SDL_PollEvent( &event ) ) {

В SDL, когда происходит событие, она попадает в «очередь событий». Очередь событий хранит данные для каждого произошедшего события.

Поэтому если вы нажали кнопку мыши, передвинули мышь, а затем нажали на клавишу на клавиатуре, очередь событий будет выглядеть как-то так:
Очередь событий
SDL_PollEvent() занимается тем, что берет событие из очереди и помещает его данные в нашу структуру:
Опрос события
В результате этот код продолжает получать данные событий, пока очередь не опустеет.

             //Если пользователь попытался закрыть окно
            if( event.type == SDL_QUIT ) {
                //Закрываем приложение
                quit = true;
            }
        }
    }

Когда пользователь попытается закрыть приложение, тип полученного события будет SDL_QUIT.

Но когда пользователь делает это, программа не закрывается; это только сообщает нам, что пользователь хочет выйти из программы.

А поскольку мы хотим чтобы программа завершилась, когда пользователь попытается её закрыть, мы устанавливаем quit в true и это прерывает цикл, в котором мы находимся.

    //Освободить поверхность и завершить работу SDL
    clean_up();
    return 0;
}

В конце концов, мы выполняем функцию очистки.

Есть и другие способы обработки событий, например, SDL_WaitEvent() и SDL_PeepEvents(). Вы можете узнать о них в документации.


Скачать исходники


Примечание: теперь самое время узнать побольше о функциях SDL по обработке ошибок. У меня нет руководства по ним, но я затрагиваю их в статье 5 (англ.). В документация по SDL объясняется функция SDL_GetError(), а документация по SDL_image объясняет IMG_GetError(). SDL_ttf и SDL_mixer так же имеют свои функции обработки ошибок, так что не забудьте посмотреть и их.


Допольнительные материалы:
Cобытийно-ориентированное программирование (Event Driven Programming)

Событийно-ориентированное программирование: 6 комментариев

  1. Nezon

    Спасибо огромное! Благодаря Вашим руководствам я за сутки освоил элементарную 2д-графику в СИ!! =)

    У меня такой вопрос появился. Как очистить очередь этих событий?

    Пояснение:
    Я пишу игрушку «танчики». У меня есть функция, которая вызывается при нажатии клавиши пробел и моделирует выстрел танка. Соответственно, для визуализации полета снаряда на каждом шаге вызывается задержка (SDL_Delay(200)).
    В то время, пока снаряд летит, накапливается очередь событий, и, когда снаряд прилетает, они все начинают выполняться. Этого бы хотелось избежать. Чтобы по прилету снаряда собравшаяся очередь очищалась.