Нажатия клавиш


Этот урок рассказывает о том, как обнаруживать нажатия клавиш. Мы будем писать простую программу, показывающую какая из клавиш-стрелок была нажата. Мы уже делали простую обработку событий (SDL_QUIT). Сегодня мы разберемся с тем, как определять что была нажата клавиша, и что это была за клавиша.

    //Сгенерировать поверхности с сообщениями
    upMessage = TTF_RenderText_Solid( font, "Up was pressed.", textColor );
    downMessage = TTF_RenderText_Solid( font, "Down was pressed.", textColor );
    leftMessage = TTF_RenderText_Solid( font, "Left was pressed", textColor );
    rightMessage = TTF_RenderText_Solid( font, "Right was pressed", textColor );

После того, как все инициализировано и загружено, мы генерируем 4 поверхности с сообщениями.

Мне вероятно следовало бы проверять на ошибки при рендеринге текста, но … хм, так меньше печатать.

    //Если есть событие для обработки
    if( SDL_PollEvent( &event ) ) {
        //Если была нажата клавиша
        if( event.type == SDL_KEYDOWN ) {

Теперь, когда мы хотим проверить была ли нажата клавиша, мы проверяем равен ли тип события SDL_KEYDOWN.

            //Выбрать правильное сообщение
            switch( event.key.keysym.sym ) {
                case SDLK_UP: message = upMessage; break;
                case SDLK_DOWN: message = downMessage; break;
                case SDLK_LEFT: message = leftMessage; break;
                case SDLK_RIGHT: message = rightMessage; break;
            }
        }
        //Если пользователь хочет выйти
        else if( event.type == SDL_QUIT ) {
            //Выходим из программы
            quit = true;
        }
    }

Теперь, если была нажата клавиша, мы должны проверить что это была за клавиша.
SDL_PollEvent() кладет данные типа SDL_KEYDOWN в структуру события как SDL_KeyboardEvent по имени key:

и внутри key находится структура keysym:

а внутри keysym SDL_Key под названием sym, который хранит информацию о том какая кнопка была нажата.

Если была нажата стрелка вверх, sym будет равен SDLK_UP и мы выберем сообщение «вверх», если sym будет равен SDLK_DOWN мы выберем сообщение «вниз» и т.д.

Посмотреть все определения SDL_Key вы можете в документации по SDL.

Так же мы проверяем не хочет ли пользователь выйти и соответствующим образом обрабатываем это событие.

Замечание: Некоторые IDE, типа Code::Blocks, включают флаг -Wall по умолчанию. Из-за этого компилятор может жаловаться на то что у вас нет выражений case для всех возможных значений. Чтобы компилятор перестал жаловаться просто добавьте в конец блока switch:

default : ;
    //Если есть сообщение для отображения
    if( message != NULL ) {
        //Скопировать сообщение на экран
        apply_surface( 0, 0, background, screen );
        apply_surface(
            ( SCREEN_WIDTH - message->w ) / 2,
            ( SCREEN_HEIGHT - message->h ) / 2,
            message,
            screen
        );
        //Обнулить указатель на поверхность сообщения
        message = NULL;
    }
    //Обновить экран
    if( SDL_Flip( screen ) == -1 ) {
        return 1;
    }

Если поверхность сообщения никуда не указывает, ее значение будет NULL и ничего не будет скопировано. В обратном случае мы копируем фон и затем помещаем сообщение по центру экрана.
Способ центрирования заключается в вычитании ширины/высоты копируемой поверхности из ширины/высоты поверхности на которую вы копируете. А так как поверхность должна быть отцентрирована, отступ с обеих сторон должен быть одинаковым, поэтому мы делим оставшееся расстояние пополам.

После этого мы сбрасываем сообщение в NULL и обновляем экран.


Скачать исходники и материалы

Нажатия клавиш: 6 комментариев

  1. Никита

    А почему не работает, если фон задать заранее, а message удалять через SDL_FreeSurface(message)??
    Не слишком ли большая нагрузка, если обновлять его каждый раз?

    1. admin Автор записи
      SDL_FreeSurface()

      освобождает память, занятую структурой поверхности.
      Для того, чтобы «удалить» изображение с экрана, нужно заменить содержимое экранного буфера в нужных местах. Самый простой способ это сделать заключается в том, чтобы очистить все и нарисовать весь кадр заново. Конечно, существуют различные техники оптимизации, например, перерисовывать только изменившиеся куски изображения.

  2. dima

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

    while (SDL_PollEvent(&event))
    {
    //Если нажали клавишу
    if (event.type == SDL_KEYDOWN)
    {
    switch (event.key.keysym.sym)
    {
    case SDLK_UP: draqon4ik.move(UP); break;
    case SDLK_RIGHT: draqon4ik.move(RIGHT); break;
    case SDLK_DOWN: draqon4ik.move(DOWN); break;
    case SDLK_LEFT: draqon4ik.move(LEFT); break;
    }
    }

    1. admin Автор записи

      Скорее всего дело в повторении нажатий.

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

        1. admin Автор записи
          Player player;
          while (SDL_PollEvent(&event))
          {
              //Если нажали клавишу
              if (event.type == SDL_KEYDOWN)
              {
              switch (event.key.keysym.sym)
              {
                  case SDLK_UP: player.dy = 10; player.dx = 0; break;
                 ...
              }
          }
          player.x += player.dx;
          player.y += player.dy;

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