Текст и кнопки в PyGame: как вывести надпись, сделать кнопку и читать клавиши

Текст в PyGame выводится в три шага: SysFont создаёт шрифт, render рисует строку на отдельной поверхности, blit кладёт её на экран.

Кнопка — это Rect, надпись на нём и проверка collidepoint в событии MOUSEBUTTONDOWN.

В статье код каждого шага, класс кнопки с подсветкой при наведении, два способа читать клавиши и полная программа с меню и счётом, проверенная на pygame 2.6.1.

Статью написал:
Ваня Буявец, продюсер, основатель Checkroi
Ваня Буявец
Основатель Checkroi, продюсер, эксперт в выборе онлайн-курсов
Все 3048 статей автора Подписаться на Телеграм-канал
Одобрено экспертом:
Наташа Буявец, основатель Checkroi, эксперт по онлайн-курсам
Наташа Буявец
Основательница Checkroi, продюсер Youtube-каналов, эксперт по онлайн-курсам
Все 3702 экспертных мнения Подписаться на Телеграм-канал
Обложка: Текст и кнопки в PyGame: как вывести надпись, сделать кнопку и читать клавиши

Чтобы вывести текст в PyGame, нужны три строки: создать шрифт font = pygame.font.SysFont("arial", 36), превратить строку в картинку surface = font.render("Счёт: 10", True, (255, 255, 255)) и нарисовать её на экране screen.blit(surface, (20, 20)). Кнопка делается из pygame.Rect: прямоугольник рисуется через draw.rect, а нажатие ловится в событии MOUSEBUTTONDOWN проверкой rect.collidepoint(event.pos).

Ниже разбираю шрифты, позиционирование, клики, клавиши и собираю всё в меню со счётом. Код снят на pygame 2.6.1, окно и игровой цикл беру из инструкции про первое окно в PyGame; если нужен контекст целой игры, есть разбор, как сделать игру на PyGame с нуля.

Как вывести текст на экран: pygame font и render

Рой-разработчик игр выводит первую надпись со счётом на экран своей игры

В PyGame нельзя «напечатать» строку прямо на экране. Модуль pygame.font сначала рисует текст на отдельной поверхности (Surface), и уже её вы кладёте на экран как любую картинку. Отсюда три шага, которые повторяются в каждой игре.

  1. Инициализировать библиотеку: pygame.init() запускает и модуль шрифтов. Без этого SysFont падает с ошибкой pygame.error: font not initialized.
  2. Создать объект шрифта один раз, до игрового цикла: font = pygame.font.SysFont("arial", 36).
  3. В цикле вызвать font.render() и нарисовать результат через screen.blit().
import pygame

pygame.init()
screen = pygame.display.set_mode((640, 480))
font = pygame.font.SysFont("arial", 36)

text = font.render("Счёт: 10", True, (255, 255, 255))
print(text.get_size())        # (140, 41) — размер поверхности с текстом

screen.blit(text, (20, 20))
pygame.display.flip()

У render() четыре параметра, и путают обычно второй.

Параметр Что делает Пример
text Строка. Только одна строка: символ \n не переносит текст, а рисуется пустым квадратом "Пауза", f"Счёт: {score}"
antialias Сглаживание краёв. True для обычного текста, False для пиксельного стиля True
color Цвет текста: кортеж RGB или имя цвета латиницей (255, 255, 255), "white"
background Цвет подложки. Без него фон прозрачный (0, 0, 0)

Результат render() — обычная поверхность, поэтому к тексту применимо всё, что умеет Surface: get_rect() для позиционирования, pygame.transform.rotate() для поворота, set_alpha() для прозрачности. Официальное описание модуля — в документации pygame.font.

SysFont, Font и файл ttf: какой шрифт выбрать

Шрифт в PyGame создаётся тремя способами, и они по-разному ведут себя на чужом компьютере. Это важно, если игру будут запускать не только вы.

Способ Код Когда подходит
Системный шрифт pygame.font.SysFont("arial", 36, bold=True) Быстрые прототипы. На машине без Arial PyGame молча подставит шрифт по умолчанию
Встроенный шрифт pygame.font.Font(None, 48) Всегда работает: None означает файл freesansbold.ttf, который идёт в комплекте с библиотекой. Кириллицу поддерживает
Свой файл pygame.font.Font("fonts/PressStart2P.ttf", 24) Готовая игра: шрифт лежит рядом с кодом и выглядит одинаково у всех

Проверить, какие шрифты видит PyGame на вашей системе, можно двумя функциями. pygame.font.get_fonts() возвращает список имён в нижнем регистре без пробелов (у меня на macOS их 555, среди них arialunicode, menlo, helvetica), а pygame.font.match_font("arial") отдаёт путь к файлу или None, если шрифта нет.

print(pygame.font.get_default_font())   # freesansbold.ttf
print(pygame.font.match_font("arial"))  # //selcdn.checkroi.ru/System/Library/Fonts/Supplemental/Arial.ttf
print(pygame.font.match_font("nosuch")) # None

Начертание меняется двумя путями. У SysFont есть аргументы bold и italic, а у любого объекта шрифта — методы set_bold(), set_italic() и set_underline(). Если библиотека у вас ещё не установлена, начните с короткой инструкции, как установить PyGame через pip.

Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединиться

Позиция текста, счёт и несколько строк

Координаты в blit() задают левый верхний угол поверхности. Чтобы поставить надпись по центру экрана или прижать к нижнему краю, удобнее взять прямоугольник текста и сдвинуть его через именованные атрибуты. Как устроен Rect и его атрибуты вроде center и midbottom, я подробно разбирал в статье про Rect и столкновения в PyGame.

title = font.render("Моя игра", True, (255, 255, 255))
title_rect = title.get_rect(center=(320, 120))     # центр надписи в точке (320, 120)
screen.blit(title, title_rect)

hint = font.render("Пробел: +1, Esc: в меню", True, (160, 160, 160))
screen.blit(hint, hint.get_rect(midbottom=(320, 460)))   # по центру у нижнего края

Счёт, который меняется, рендерится заново в каждом кадре: font.render(f"Счёт: {score}", True, WHITE). Сам вызов render() дешёвый, дорого только создание шрифта, поэтому объект font держите вне цикла, а строку обновляйте внутри.

Несколько строк выводятся циклом: разбиваете текст по \n, рендерите каждую строку отдельно и сдвигаете y на высоту строки. Высоту берите из font.get_linesize(), а не подбирайте вручную.

lines = "Первая строка\nВторая строка\nТретья".split("\n")
y = 20
for line in lines:
    surface = font.render(line, True, (255, 255, 255))
    screen.blit(surface, (20, y))
    y += font.get_linesize()      # для arial 28 это 33 пикселя

Как сделать кнопку в PyGame: Rect, collidepoint и MOUSEBUTTONDOWN

Экран игры с меню из двух кнопок и клавиатура с подсвеченным пробелом

Готового виджета «кнопка» в PyGame нет, и это нормально: кнопка — это прямоугольник, надпись на нём и проверка, попал ли клик внутрь. Всё это уже есть в библиотеке, остаётся собрать в класс. Ниже класс, который умеет подсвечиваться при наведении и вызывать функцию по клику.

class Button:
    def __init__(self, text, x, y, w, h, on_click):
        self.rect = pygame.Rect(x, y, w, h)
        self.text = text
        self.on_click = on_click
        self.hover = False

    def handle_event(self, event):
        if event.type == pygame.MOUSEMOTION:
            self.hover = self.rect.collidepoint(event.pos)
        elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            if self.rect.collidepoint(event.pos):
                self.on_click()

    def draw(self, surface):
        color = (90, 150, 210) if self.hover else (60, 100, 150)
        pygame.draw.rect(surface, color, self.rect, border_radius=8)
        label = ui_font.render(self.text, True, (255, 255, 255))
        surface.blit(label, label.get_rect(center=self.rect.center))

Три вещи, на которых держится этот класс.

  • Событие, а не опрос. Клик ловится через pygame.MOUSEBUTTONDOWN из очереди событий. Если вместо этого проверять pygame.mouse.get_pressed() в цикле, одно нажатие сработает на каждом кадре, пока кнопка мыши зажата, и обработчик вызовется десятки раз.
  • event.button говорит, какая кнопка нажата: 1 левая, 2 колесо как кнопка, 3 правая. Прокрутка колеса в pygame 2 приходит отдельным событием pygame.MOUSEWHEEL с полем event.y (-1 вниз, 1 вверх).
  • collidepoint(event.pos) возвращает True, если точка внутри прямоугольника. Тот же метод отвечает за подсветку при наведении через MOUSEMOTION.

Текущее положение курсора вне событий даёт pygame.mouse.get_pos(), а смещение с прошлого вызова — pygame.mouse.get_rel(). Полный список полей событий мыши и клавиатуры описан в справочнике по pygame.event.

CheckroiCheckroiПодборка курсов по Python569 курсов • 51 школаСравните цены, школы, программу и найдите выгодные предложения по обучениюСравнить→

Клавиши в PyGame: KEYDOWN, get_pressed и ввод текста

Клавиатуру в PyGame читают двумя способами, и у каждого своя задача. Событие KEYDOWN срабатывает один раз в момент нажатия, поэтому оно подходит для прыжка, паузы, выстрела и выхода по Esc. Функция pygame.key.get_pressed() возвращает состояние всех клавиш прямо сейчас и нужна для движения: пока стрелка зажата, персонаж едет.

Задача Как читать Код
Прыжок, пауза, выход Событие KEYDOWN if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
Движение, пока клавиша зажата key.get_pressed() каждый кадр keys = pygame.key.get_pressed(); if keys[pygame.K_LEFT]: player.x -= 5
Ввод имени, чата event.unicode из KEYDOWN name += event.unicode
Отпускание клавиши Событие KEYUP if event.type == pygame.KEYUP:

Коды клавиш лежат в константах pygame.K_*: K_SPACE, K_ESCAPE, K_RETURN, K_LEFT, K_a. Имя клавиши по коду отдаёт pygame.key.name(event.key). Для текстового поля используйте не event.key, а event.unicode: там уже лежит символ с учётом раскладки и Shift, включая кириллицу.

name = ""
for event in pygame.event.get():
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_BACKSPACE:
            name = name[:-1]
        elif event.key == pygame.K_RETURN:
            print("Введено:", name)
        elif event.unicode.isprintable():
            name += event.unicode

Проверка isprintable() отсекает служебные символы: у Esc и стрелок unicode пустой или непечатный, и без неё в строку попадёт мусор. Подробнее о том, как игровой цикл раз за разом опрашивает события, я писал в разборе цикла while в Python. Справочник по константам клавиш — в документации pygame.key. Если условия, классы и словари в этом коде пока читаются с трудом, базу быстрее добрать на курсах по Python, а к PyGame вернуться через неделю.

Меню, кнопки и счёт в одной программе

Рой радуется: его игра запустилась, меню работает и счёт растёт

Ниже полный код: стартовое меню с двумя кнопками, игровое состояние с квадратом, который двигается стрелками, счётчик по пробелу и возврат в меню по Esc. Переключение экранов сделано через переменную state, а не через два вложенных цикла: так проще добавлять экраны паузы и проигрыша, а потом и экран «Игра окончена» с рестартом, как в разборе игры на PyGame от пустого окна до exe.

import pygame

pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Меню и счёт")
clock = pygame.time.Clock()

title_font = pygame.font.SysFont("arial", 48, bold=True)
ui_font = pygame.font.SysFont("arial", 28)

WHITE = (255, 255, 255)
BG = (30, 30, 40)


class Button:
    def __init__(self, text, x, y, w, h, on_click):
        self.rect = pygame.Rect(x, y, w, h)
        self.text = text
        self.on_click = on_click
        self.hover = False

    def handle_event(self, event):
        if event.type == pygame.MOUSEMOTION:
            self.hover = self.rect.collidepoint(event.pos)
        elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            if self.rect.collidepoint(event.pos):
                self.on_click()

    def draw(self, surface):
        color = (90, 150, 210) if self.hover else (60, 100, 150)
        pygame.draw.rect(surface, color, self.rect, border_radius=8)
        label = ui_font.render(self.text, True, WHITE)
        surface.blit(label, label.get_rect(center=self.rect.center))


state = "menu"
score = 0
player = pygame.Rect(300, 220, 40, 40)
running = True


def start_game():
    global state, score
    state = "game"
    score = 0


def quit_game():
    global running
    running = False


buttons = [
    Button("Играть", 220, 200, 200, 50, start_game),
    Button("Выход", 220, 270, 200, 50, quit_game),
]

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif state == "menu":
            for button in buttons:
                button.handle_event(event)
        elif state == "game" and event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                score += 1
            elif event.key == pygame.K_ESCAPE:
                state = "menu"

    if state == "game":
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            player.x -= 5
        if keys[pygame.K_RIGHT]:
            player.x += 5
        player.clamp_ip(screen.get_rect())

    screen.fill(BG)
    if state == "menu":
        title = title_font.render("Моя игра", True, WHITE)
        screen.blit(title, title.get_rect(center=(320, 120)))
        for button in buttons:
            button.draw(screen)
    else:
        pygame.draw.rect(screen, (240, 200, 60), player)
        score_text = ui_font.render(f"Счёт: {score}", True, WHITE)
        screen.blit(score_text, (20, 20))
        hint = ui_font.render("Пробел: +1, Esc: в меню", True, (160, 160, 160))
        screen.blit(hint, hint.get_rect(midbottom=(320, 460)))

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Что происходит при запуске, по шагам.

  1. Открывается меню: заголовок по центру и две кнопки. При наведении кнопка светлеет.
  2. Клик по «Играть» вызывает start_game(): состояние меняется на game, счёт обнуляется.
  3. Стрелки двигают квадрат, clamp_ip не даёт ему уехать за экран, пробел прибавляет очко, счёт обновляется в левом верхнем углу.
  4. Esc возвращает в меню, «Выход» завершает цикл и закрывает окно.

Я прогнал этот код с заскриптованными событиями: наведение включает подсветку, клик переводит в игру, два пробела дают счёт 2, Esc возвращает в меню, клик по «Выход» останавливает цикл. Дальше сюда естественно ложатся спрайты и столкновения из статьи про Rect и спрайты, а если хочется понять, как такие механики устроены в больших проектах, посмотрите обзор, как программировать игры.

Ваня БуявецКанал основателя Checkroi Вани БуявцаЗабирайте промпты и обучение по нейросетям в моём Телеграм-каналеБольше 3 700 человек уже применяют Claude Code, ChatGPT и другие нейросети в работе, учёбе, бизнесе и жизниПерейти в канал

Частые ошибки с текстом и кнопками

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

  • Текст не появляется вовсе. Обычно blit() стоит до screen.fill(), и заливка фона закрашивает надпись в том же кадре. Порядок в цикле: сначала fill, потом всё остальное, в конце flip().
  • Игра тормозит, когда на экране появился счёт. Значит, SysFont() вызывается внутри цикла. У меня создание шрифта плюс рендер занимает 0,69 мс, а один рендер готовым шрифтом 0,005 мс: разница в сто с лишним раз. Шрифт создаётся один раз до while.
  • Вместо переноса строки квадрат. render() рисует одну строку, символ \n он не понимает. Разбивайте текст через split("\n") и рендерите построчно, как в разделе выше.
  • Одно нажатие срабатывает много раз. Клик или клавиша проверяются через get_pressed(), а не через событие. Для разовых действий (клик по кнопке, прыжок, пауза) используйте MOUSEBUTTONDOWN и KEYDOWN.
  • Кириллица рисуется квадратами. Выбранный системный шрифт не содержит русских глифов. Встроенный Font(None, размер) кириллицу знает; для своего ttf проверьте, что в нём есть нужные символы, через font.metrics("П"): для отсутствующего глифа вернётся None.

Ещё одна ловушка с цветом: render("x", True, "белый") падает с UnicodeEncodeError, потому что имена цветов PyGame понимает только латиницей. Пишите "white" или кортеж (255, 255, 255).

Где освоить Python и разработку игр системно

Текст, кнопки и обработка клавиш закрывают интерфейс маленькой игры, а дальше начинаются уровни, сохранения, звук и сборка в exe. Если PyGame для вас первый шаг к профессии, посмотрите, чем занимается разработчик игр и какие движки просят в вакансиях. Системно язык и логику игр дают курсы по Python и курсы по разработке игр: там PyGame проходят как учебный движок, а потом переводят на Unity или Godot.

КурсШколаСтоимость со скидкойВ рассрочкуДлитель­ностьОбзор курса от Checkroi
Профессия «Python-разработчик»
Перейти на сайт курса
SkillboxSkillbox157 335 ₽5987 ₽/мес.10 месяцевОбзор курса
Fullstack-разработчик на Python
Перейти на сайт курса
НетологияНетология137 000 ₽7125 ₽/мес.21 месяцОбзор курса
ДО Профессия Python-разработчик
Перейти на сайт курса
GeekBrainsGeekBrains149 001 ₽3167 ₽/мес.10 месяцевОбзор курса
Автоматизированное тестирование на Python
Перейти на сайт курса
SkillboxSkillbox122 226 ₽4108 ₽/мес.9 месяцевОбзор курса
Профессия Python-разработчик
Перейти на сайт курса
SkillFactorySkillFactory154 044 ₽4279 ₽/мес.10 месяцевОбзор курса

Больше программ — в полном каталоге курсов по Python

Часто задаваемые вопросы

Как вывести текст в PyGame

Создайте шрифт font = pygame.font.SysFont("arial", 36), отрисуйте строку surface = font.render("Счёт: 10", True, (255, 255, 255)) и положите её на экран screen.blit(surface, (20, 20)). Шрифт создаётся один раз до игрового цикла, render и blit вызываются в каждом кадре после screen.fill().

Чем pygame.font.SysFont отличается от pygame.font.Font

SysFont("arial", 36) ищет шрифт по имени среди установленных в системе, и на чужом компьютере может подставить другой. Font(None, 36) берёт встроенный freesansbold.ttf, который есть везде, а Font("путь.ttf", 36) грузит ваш файл рядом с игрой. Для готового проекта надёжнее свой файл шрифта.

Как сделать кнопку в PyGame

Кнопка собирается из pygame.Rect, надписи через font.render() и проверки клика: в событии pygame.MOUSEBUTTONDOWN с event.button == 1 вызовите rect.collidepoint(event.pos). Если точка внутри прямоугольника, запускайте нужную функцию. Подсветка при наведении делается так же, но по событию MOUSEMOTION.

Почему текст в PyGame не переносится на новую строку

render() рисует ровно одну строку, а символ \n выводит пустым квадратом. Разбейте текст через split("\n"), отрисуйте каждую строку отдельно и сдвигайте координату y на font.get_linesize().

Как обработать нажатие клавиши в PyGame

Для разового действия ловите событие pygame.KEYDOWN и сравнивайте event.key с константами вроде pygame.K_SPACE. Для движения, пока клавиша зажата, каждый кадр читайте keys = pygame.key.get_pressed() и проверяйте keys[pygame.K_LEFT]. Для ввода текста берите символ из event.unicode.

Почему кириллица в PyGame отображается квадратами

Выбранный шрифт не содержит русских глифов. Встроенный pygame.font.Font(None, размер) кириллицу поддерживает, а для своего ttf проверьте наличие символа через font.metrics("П"): для отсутствующего глифа метод вернёт None.

Читайте Checkroi первым в Google
Добавьте Checkroi в избранные источники — и наши разборы курсов и обзоры школ будут показываться выше в вашей выдаче Google.
Оставить комментарий
0 комментариев
Форма комментария

Оставьте комментарий

Напишите, что думаете. Нам важно ваше мнение!