Клавиатура: события и состояние

Игра оживает, когда герой слушается клавиш. Разберём два способа читать клавиатуру и поймём, когда какой нужен.
Суть: есть два способа ввода — события KEYDOWN (момент нажатия) и pygame.key.get_pressed() (зажата ли клавиша прямо сейчас). Для движения нужен второй.

Управление — это то, что превращает картинку в игру. В Pygame клавиатуру можно читать двумя разными способами, и новички часто путают, какой когда нужен. Разберёмся раз и навсегда.

Первый способ — события KEYDOWN и KEYUP. Они срабатывают ОДИН раз: в момент, когда клавишу нажали, и в момент, когда отпустили. Это идеально для действий-однократников: прыжок, выстрел, открыть меню, поставить паузу. Ты не хочешь, чтобы герой прыгал 60 раз в секунду, пока зажат пробел.

Второй способ — pygame.key.get_pressed(). Он возвращает «снимок» всей клавиатуры: какие клавиши зажаты ПРЯМО СЕЙЧАС, в этом кадре. Это идеально для непрерывного движения: пока зажата стрелка вправо — герой едет вправо. Каждый кадр проверяем и двигаем.

Как работает под капотом

События живут в очереди и обрабатываются в цикле for event. А get_pressed возвращает список булевых значений, по одному на каждую клавишу. Вот разница:

   СОБЫТИЕ KEYDOWN          get_pressed()
   (момент нажатия)         (состояние сейчас)

   зажал ----+              зажал ------------+
             |  один раз                      |  каждый
   держу     |              держу [v][v][v][v]|  кадр
             |                                |  True
   отпустил  v              отпустил ---------+

   -> прыжок, выстрел       -> ходьба, бег

Оба способа в pygame (читаем):

while running:
    keys = pygame.key.get_pressed()       # снимок клавиатуры

    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                jump()                     # прыжок — один раз

    if keys[pygame.K_LEFT]:
        player.x -= SPEED * dt             # ходьба — пока зажато
    if keys[pygame.K_RIGHT]:
        player.x += SPEED * dt

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

SPEED = 200

def movement(keys, dt):
    dx = dy = 0
    if "LEFT" in keys:  dx -= SPEED * dt
    if "RIGHT" in keys: dx += SPEED * dt
    if "UP" in keys:    dy -= SPEED * dt   # вверх = минус Y!
    if "DOWN" in keys:  dy += SPEED * dt
    return dx, dy

dt = 0.1
print("вправо:", movement({"RIGHT"}, dt))
print("вправо+вверх:", movement({"RIGHT", "UP"}, dt))
print("влево+вправо:", movement({"LEFT", "RIGHT"}, dt))  # гасят друг друга

Биндинги: отвязываем действия от клавиш

Жёстко прописывать K_LEFT по всему коду — плохая идея: захочешь добавить настройку управления или поддержать WASD, и придётся править десятки мест. Профессиональный приём — словарь биндингов: actions = {"left": [K_LEFT, K_a], "jump": [K_SPACE]}. Дальше код спрашивает не «нажата ли стрелка влево», а «активно ли действие move_left», и сам сверяется со словарём. Поменять раскладку теперь — поменять одну строку, а не всю игру.

Такое разделение «физическая клавиша против игрового действия» — стандарт индустрии. Оно же позволяет легко добавить геймпад: просто допиши в действие ещё и кнопки джойстика, а остальной код не заметит разницы. Привыкай отделять «что игрок хочет сделать» от «чем он это сделал». Это делает управление гибким, настраиваемым и дружелюбным к игрокам с разными привычками — а ведь именно удобное управление часто решает, понравится ли игра с первых секунд.

Не забывай про удобство игрока в мелочах. Поддержи сразу две раскладки движения — стрелки и WASD, — просто проверяя обе клавиши на каждое направление: левшам и правшам удобно по-разному. Добавь паузу на P или Esc и выход на тот же Esc из меню — игрок не должен чувствовать себя в ловушке. И помни главное различие этого урока: непрерывное действие читается через состояние клавиш каждый кадр, а разовое — через событие нажатия. Если однажды управление «залипает» или прыжок срабатывает слишком часто, первым делом проверь, не перепутал ли ты эти два способа. Это настолько частая ошибка, что стоит сделать её проверку рефлексом.

Частые ошибки

  • Двигать героя в KEYDOWN — он дёрнется один раз и встанет, а не поедет плавно.
  • Прыгать через get_pressed — прыжок сработает каждый кадр, герой «зависнет в воздухе».
  • Звать get_pressed внутри цикла событий — зови его один раз за кадр, до или после.

Best practices

  • Однократные действия — через события KEYDOWN. Непрерывные — через get_pressed.
  • Складывай WASD и стрелки вместе, чтобы игроку было удобно.
  • Все движения умножай на dt, даже из get_pressed.

Итог: KEYDOWN — для «щелчков» (прыжок, выстрел), get_pressed — для «удержаний» (ходьба, бег). Перепутаешь — и управление сломается.

Проверьте себя
1. Каким способом читать клавиатуру для НЕПРЕРЫВНОГО движения героя?
AСобытие KEYDOWN
Bpygame.key.get_pressed()
CСобытие QUIT
Dpygame.mouse.get_pos()
2. Для прыжка по пробелу лучше использовать...
Aget_pressed — иначе не сработает
Bсобытие KEYDOWN — один раз на нажатие
Cсобытие QUIT
Dтаймер