Как обрабатывать события от клавиатуры

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

Вообще, за обработку событий отвечает модуль

pygame.event

и ранее мы уже познакомились с методом

pygame.event.get()

который возвращал коллекцию событий, произошедших с момента его последнего вызова. Причем, каждое конкретное событие – это объект, унаследованный от базового класса Event. Этот базовый класс определяет свойство type, в котором сохраняется тип события. Остальные свойства объектов событий зависят от их типа (для клавиатуры одни, для мыши другие, для джойстика – третьи и т.д.). Причем, свойство type принимает несколько разных значений для нажатия клавиш, например:

  • event.type == pygame.KEYDOWN – клавиша нажата;
  • event.type == pygame.KEYUP – клавиша отпущена.

То есть, тип – это не только тип события, но и его конкретизация – какое именно событие произошло. Давайте в качестве примера создадим программу, которая при нажатии на курсорные клавиши вправо-влево будет перемещать прямоугольник на экране:

import pygame
 
pygame.init()
 
W = 600
H = 400
 
sc = pygame.display.set_mode((W, H))
pygame.display.set_caption("События от клавиатуры")
pygame.display.set_icon(pygame.image.load("app.bmp"))
 
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
 
FPS = 60        # число кадров в секунду
clock = pygame.time.Clock()
 
x = W // 2
y = H // 2
speed = 5
 
while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                x -= speed
            elif event.key == pygame.K_RIGHT:
                x += speed
 
    sc.fill(WHITE)
    pygame.draw.rect(sc, BLUE, (x, y, 10, 20))
    pygame.display.update()
 
    clock.tick(FPS)

Смотрите, нажимая на курсорные клавиши, происходит изменение координаты x и прямоугольник перерисовывается в новой позиции. Но, если нажать и удерживать клавишу нажатой, то объект сместится только один раз. Постоянного перемещения не происходит, как можно было ожидать. Все дело в том, что при нажатии клавиши в PyGame формируется только одно событие pygame.KEYDOWN. После того, как мы его прочитали из очереди и обработали, повторного такого события там уже нет и, соответственно, условие event.type == pygame.KEYDOWN не срабатывает.

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

flLeft = flRight = False
 
while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                flLeft = True
            elif event.key == pygame.K_RIGHT:
                flRight = True
        elif event.type == pygame.KEYUP:
            if event.key in [pygame.K_LEFT, pygame.K_RIGHT]:
                flLeft = flRight = False
 
    if flLeft:
        x -= speed
    elif flRight:
        x += speed
 
    sc.fill(WHITE)
    pygame.draw.rect(sc, BLUE, (x, y, 10, 20))
    pygame.display.update()

Здесь мы по событию pygame.KEYDOWN изменяем состояния флагов flLeft или flRight в зависимости от нажатия на левую или правую курсорные клавиши. А в основном цикле по этим флагам осуществляем изменение координат прямоугольника. Теперь, он перемещается непрерывно, удерживая клавишу нажатой. При отпускании клавиши генерируется событие pygame.KEYUP и флаги flLeft, flRight устанавливаются в False, движение прекращается.

У вас может возникнуть вопрос: а зачем нам вторая проверка

if event.key in [pygame.K_LEFT, pygame.K_RIGHT]

Это защита от двойных нажатий. Например, удерживая нажатой курсорную клавишу, мы нажимаем, а потом отпускаем еще какую-нибудь. Тогда без этой второй проверки по событию pygame.KEYUP флаги flLeft, flRight станут равными False и движение остановится. Хотя, курсорная клавиша остается нажатой. Чтобы этого не происходило и делается эта дополнительная проверка.

Однако, тот же самый функционал можно реализовать проще, используя модуль работы с клавиатурой

pygame.key

В частности, в нем есть функция:

pygame.key.get_pressed()

которая возвращает информацию о состояниях клавиш в виде кортежа:

Если клавиша с определенным индексом нажата, то в этом кортеже ее значение будет 1, а если отжата, то 0. Используя эту функцию, наша программа может быть записана в таком виде:

while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
 
    keys = pygame.key.get_pressed()
 
    if keys[pygame.K_LEFT]:
        x -= speed
    elif keys[pygame.K_RIGHT]:
        x += speed
 
    sc.fill(WHITE)
    pygame.draw.rect(sc, BLUE, (x, y, 10, 20))
    pygame.display.update()
 
    clock.tick(FPS)

Как видите, все стало предельно простым. Фактически, функция get_pressed() дает маску нажатых на клавиатуре клавиш и никак не влияет на состояния событий, находящихся в очереди. То есть, мы здесь напрямую не работаем с событиями, зарегистрированные в PyGame, а просто используем информацию о том, какая клавиша нажата. И это имеет свои следствия. Например, если мы хотим перемещать прямоугольник при одновременном нажатии на клавишу Ctrl и курсорные клавиши вправо-влево, то с помощью функции get_pressed() нельзя отследить состояния клавиш-модификаторов:

Shift, Ctrl, Alt и др.

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

move = 0
 
while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT and event.mod == pygame.KMOD_LCTRL:
                move = -speed
            elif event.key == pygame.K_RIGHT and event.mod == pygame.KMOD_LCTRL:
                move = speed
        elif event.type == pygame.KEYUP:
            if event.key in [pygame.K_LEFT, pygame.K_RIGHT]:
                move = 0
 
    x += move
 
    sc.fill(WHITE)
    pygame.draw.rect(sc, BLUE, (x, y, 10, 20))
    pygame.display.update()
 
    clock.tick(FPS)

При этом важна последовательность нажатия на клавиши: сначала нужно нажать левый Ctrl, а потом курсорную клавишу вправо-влево. В другой последовательности работать уже не будет. Библиотека PyGame обрабатывает такие клавиши-модификаторы несколько иначе стандартных клавиш, отсюда и получаются такие особенности.

Вот так в целом выполняется обработка событий от клавиатуры. На следующем занятии мы рассмотрим обработку событий от мыши.