Django-Admin.ru

Pygame: первая игра на Python

Python Pygame с нуля: установка, окно, игровой цикл, события клавиатуры и мыши, отрисовка, спрайты и столкновения, FPS через Clock и мини-игра целиком.

Редакция Django-Admin.ru
· обновлено · 10 мин чтения

Содержание · 11 разделов
  1. Установка Pygame
  2. Окно и игровой цикл
  3. FPS и pygame.time.Clock
  4. События: клавиатура и мышь
  5. Отрисовка: фигуры, картинки и текст
  6. Прямоугольники Rect и столкновения
  7. Спрайты и группы
  8. Мини-игра «Лови монеты»
  9. Частые ошибки
  10. Окно открывается и сразу закрывается или «не отвечает»
  11. Ничего не рисуется
  12. Объект оставляет за собой «шлейф»
  13. pygame.error: video system not initialized
  14. pygame.error: No video mode has been set
  15. FileNotFoundError при загрузке картинки
  16. Ошибка сборки при pip install pygame
  17. Частые вопросы
  18. Почему вместо русских букв выводятся прямоугольники?
  19. Как сделать задержку, не останавливая игру?
  20. Как проверять столкновения круглых объектов?
  21. Как добавить фоновую музыку?
  22. Как называются клавиши Shift, Ctrl и Tab?
  23. Итог

Pygame — библиотека Python для 2D-игр: она открывает окно, рисует фигуры и картинки, обрабатывает клавиатуру и мышь, проигрывает звук. Каркас любой игры на Python Pygame укладывается в десяток строк:

import pygame

pygame.init()
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # нажали крестик окна
            running = False
    screen.fill("black")
    pygame.display.flip()
    clock.tick(60)  # не больше 60 кадров в секунду

pygame.quit()

Ниже разберём каждую строку этого каркаса, научимся рисовать, двигать объекты и проверять столкновения, а в конце соберём мини-игру «Лови монеты». Код проверен на pygame 2.6 и Python 3.12.

Установка Pygame

Pygame ставится через pip, лучше в виртуальное окружение:

# Windows
py -m pip install pygame

# macOS / Linux
python3 -m pip install pygame

Проверка — запустите встроенный пример:

python -m pygame.examples.aliens

Если открылось окно с игрой, всё работает. При импорте pygame печатает в консоль строку с версиями pygame, SDL и Python — это нормально.

Важно: версия Python. У pygame 2.6.1 (последний выпуск на октябрь 2026 года) готовые сборки на PyPI есть для Python до 3.13 включительно. На Python 3.14 pip попытается собрать пакет из исходников, и на обычном компьютере это, скорее всего, закончится ошибкой. Выходы: создать окружение на Python 3.13 или поставить форк pygame-ce (pip install pygame-ce): в версии 2.5.8 у него есть готовые сборки для Python 3.10–3.15.

pygame-ce (Community Edition) — активно развиваемый форк с тем же API: импортируется тоже как import pygame, и все примеры из статьи в нём работают. Не ставьте pygame и pygame-ce в одно окружение — они конфликтуют. Общие сведения о pip — в руководстве по pip.

Окно и игровой цикл

Разберём каркас из начала статьи:

  • pygame.init() инициализирует модули pygame (графику, шрифты, звук).
  • pygame.display.set_mode((640, 480)) создаёт окно 640×480 и возвращает поверхность (Surface) — холст, на котором вы рисуете. Координата (0, 0) — левый верхний угол, ось Y направлена вниз.
  • while running: — игровой цикл. Одна итерация — один кадр. Каждый кадр делает три вещи: обрабатывает события, обновляет состояние игры, рисует.
  • pygame.display.flip() показывает нарисованный кадр на экране. Пока его не вызвать, изменений не видно.
  • pygame.quit() закрывает окно и освобождает ресурсы.

Заголовок окна задаётся так: pygame.display.set_caption("Моя игра"). Если в цикле не обрабатывать события, Windows через несколько секунд пометит окно как «не отвечает», поэтому pygame.event.get() вызывается в каждом кадре. Если вы ещё не уверенно чувствуете себя с циклами, загляните в статью про цикл while.

FPS и pygame.time.Clock

clock.tick(60) делает паузу так, чтобы цикл выполнялся не чаще 60 раз в секунду, и возвращает время в миллисекундах, прошедшее с прошлого вызова. Без tick() цикл крутится с максимальной скоростью и загружает процессор впустую.

Скорость движения лучше считать в пикселях в секунду, умножая на время кадра dt. Тогда объект движется одинаково быстро и при 30, и при 144 кадрах в секунду:

speed = 300  # пикселей в секунду
x = 0

while running:
    dt = clock.tick(60) / 1000  # секунды с прошлого кадра
    x += speed * dt

Текущую частоту кадров показывает clock.get_fps() — удобно для отладки.

События: клавиатура и мышь

Все действия пользователя приходят в очередь событий. У каждого события есть type, а у клавиатурных и мышиных — дополнительные атрибуты:

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        running = False
    elif event.type == pygame.KEYDOWN:          # клавиша нажата
        if event.key == pygame.K_ESCAPE:
            running = False
        elif event.key == pygame.K_SPACE:
            print("Прыжок!")
    elif event.type == pygame.MOUSEBUTTONDOWN:  # клик мышью
        print("Клик в точке", event.pos, "кнопкой", event.button)

События подходят для однократных действий: выстрел, прыжок, пауза. Для непрерывного движения, пока клавиша зажата, удобнее опрашивать состояние клавиатуры в каждом кадре:

keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
    x -= speed * dt
if keys[pygame.K_RIGHT]:
    x += speed * dt

Позицию курсора возвращает pygame.mouse.get_pos().

Отрисовка: фигуры, картинки и текст

Каждый кадр обычно рисуется с нуля: сначала заливка фона, затем объекты, затем flip(). Цвет задаётся кортежем RGB (255, 0, 0) или названием вроде "red".

screen.fill((20, 20, 30))                                    # фон
pygame.draw.rect(screen, (50, 120, 220), (100, 100, 80, 40))  # прямоугольник: x, y, ширина, высота
pygame.draw.circle(screen, "gold", (320, 240), 25)           # круг: центр и радиус
pygame.draw.line(screen, "white", (0, 0), (640, 480), 2)     # линия толщиной 2

Картинки загружаются из файла и выводятся методом blit():

player_img = pygame.image.load("player.png").convert_alpha()  # после set_mode()
screen.blit(player_img, (200, 300))  # левый верхний угол картинки

convert_alpha() переводит картинку в формат экрана с прозрачностью — отрисовка становится заметно быстрее. Вызывать его можно только после создания окна.

Текст рисуется через шрифт: сначала строка превращается в поверхность, затем выводится как картинка.

font = pygame.font.Font(None, 36)  # None — встроенный шрифт, 36 — размер
text = font.render("Счёт: 10", True, "white")  # True — сглаживание
screen.blit(text, (10, 10))

Встроенный шрифт pygame содержит кириллицу. Для своего шрифта передайте путь к файлу .ttf вместо None.

Прямоугольники Rect и столкновения

Положение и размеры объектов в pygame удобно хранить в pygame.Rect. У него есть координаты (x, y, center, bottom и другие) и готовые проверки пересечений:

player = pygame.Rect(0, 0, 10, 10)
enemy = pygame.Rect(5, 5, 10, 10)

print(player.colliderect(enemy))   # → True: прямоугольники пересекаются
print(player.collidepoint(3, 3))   # → True: точка внутри

У любой картинки можно получить прямоугольник нужного размера: rect = player_img.get_rect(center=(320, 240)). Метод rect.clamp_ip(screen.get_rect()) не даёт объекту выйти за край окна.

Спрайты и группы

Когда объектов становится много, используйте класс pygame.sprite.Sprite. Спрайт хранит картинку image и прямоугольник rect, а группы pygame.sprite.Group обновляют и рисуют все спрайты одной командой:

class Coin(pygame.sprite.Sprite):
    def __init__(self, x):
        super().__init__()
        self.image = pygame.Surface((20, 20), pygame.SRCALPHA)  # прозрачный фон
        pygame.draw.circle(self.image, (240, 200, 40), (10, 10), 10)
        self.rect = self.image.get_rect(midtop=(x, -20))

    def update(self, dt):
        self.rect.y += round(200 * dt)
coins = pygame.sprite.Group()
coins.add(Coin(100), Coin(300))

coins.update(dt)     # вызовет update(dt) у каждого спрайта
coins.draw(screen)   # нарисует image каждого спрайта в позиции rect

Столкновение спрайта с группой проверяет pygame.sprite.spritecollide(). С dokill=True задетые спрайты сразу удаляются из всех групп:

caught = pygame.sprite.spritecollide(player, coins, dokill=True)
score += len(caught)

Метод sprite.kill() удаляет спрайт из всех групп — например, когда он улетел за экран. Если классы для вас пока в новинку, сначала прочитайте статью про классы в Python.

Мини-игра «Лови монеты»

Соберём всё вместе. Игрок — синяя платформа внизу экрана, управляется стрелками. Сверху падают монеты, за каждую пойманную начисляется очко. Новые монеты появляются по таймеру через пользовательское событие.

import random

import pygame

WIDTH, HEIGHT = 640, 480
FPS = 60


class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((80, 20))
        self.image.fill((50, 120, 220))
        self.rect = self.image.get_rect(midbottom=(WIDTH // 2, HEIGHT - 10))
        self.speed = 400  # пикселей в секунду

    def update(self, dt):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.rect.x -= round(self.speed * dt)
        if keys[pygame.K_RIGHT]:
            self.rect.x += round(self.speed * dt)
        self.rect.clamp_ip(pygame.Rect(0, 0, WIDTH, HEIGHT))  # не выходить за экран


class Coin(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((20, 20), pygame.SRCALPHA)
        pygame.draw.circle(self.image, (240, 200, 40), (10, 10), 10)
        self.rect = self.image.get_rect(midtop=(random.randint(10, WIDTH - 10), -20))
        self.speed = random.randint(150, 300)

    def update(self, dt):
        self.rect.y += round(self.speed * dt)
        if self.rect.top > HEIGHT:
            self.kill()  # упала мимо — удалить из всех групп


def main():
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Лови монеты")
    clock = pygame.time.Clock()
    font = pygame.font.Font(None, 36)

    player = Player()
    coins = pygame.sprite.Group()
    all_sprites = pygame.sprite.Group(player)

    SPAWN_COIN = pygame.event.custom_type()
    pygame.time.set_timer(SPAWN_COIN, 700)  # новая монета каждые 0,7 с

    score = 0
    running = True
    while running:
        dt = clock.tick(FPS) / 1000  # секунды с прошлого кадра

        # 1. События
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                running = False
            elif event.type == SPAWN_COIN:
                coin = Coin()
                coins.add(coin)
                all_sprites.add(coin)

        # 2. Обновление
        all_sprites.update(dt)
        caught = pygame.sprite.spritecollide(player, coins, dokill=True)
        score += len(caught)

        # 3. Отрисовка
        screen.fill((20, 20, 30))
        all_sprites.draw(screen)
        text = font.render(f"Счёт: {score}", True, (255, 255, 255))
        screen.blit(text, (10, 10))
        pygame.display.flip()

    pygame.quit()


if __name__ == "__main__":
    main()

Сохраните код в файл game.py и запустите: python game.py (в macOS и Linux — python3 game.py). Случайные позиции и скорости монет даёт модуль random.

Идеи для доработки: ограничить игру 60 секундами и показать итоговый счёт, добавить «бомбы», которые отнимают очки, заменить фигуры картинками, добавить звук через pygame.mixer.Sound("coin.wav").play().

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

Окно открывается и сразу закрывается или «не отвечает»

В программе нет игрового цикла или в цикле не вызывается pygame.event.get(). Цикл с обработкой событий обязателен — без него окно не получает сообщений от системы.

Ничего не рисуется

Забыт pygame.display.flip() (или pygame.display.update()) в конце кадра. Ещё одна причина — фон заливается после отрисовки объектов и закрывает их.

Объект оставляет за собой «шлейф»

В начале кадра не вызывается screen.fill(...), и старые изображения остаются на холсте.

pygame.error: video system not initialized

Вызов функций pygame после pygame.quit() или до pygame.init(). Чаще всего — код после цикла, который ещё пытается рисовать.

pygame.error: No video mode has been set

convert() или convert_alpha() вызваны до pygame.display.set_mode() (если не было и pygame.init(), текст ошибки другой — cannot convert without pygame.display initialized). Загружайте картинки после создания окна.

FileNotFoundError при загрузке картинки

Относительный путь считается от текущей папки терминала, а не от папки скрипта. Запускайте игру из её папки или стройте путь от файла: Path(__file__).parent / "player.png".

Ошибка сборки при pip install pygame

Для вашей версии Python нет готовой сборки (см. раздел об установке). Используйте Python 3.13 или пакет pygame-ce.

Частые вопросы

Почему вместо русских букв выводятся прямоугольники?

В выбранном шрифте нет кириллицы — так бывает с декоративными и «пиксельными» шрифтами из интернета. Используйте встроенный шрифт pygame.font.Font(None, 36) (в нём кириллица есть) или файл .ttf с поддержкой кириллицы, положив его рядом с игрой. Системные шрифты подключает pygame.font.SysFont("arial", 24): если такого шрифта нет, pygame подставит встроенный. Список доступных системных шрифтов возвращает pygame.font.get_fonts().

Как сделать задержку, не останавливая игру?

time.sleep() и pygame.time.wait() замораживают весь цикл: окно перестаёт обрабатывать события. Для отложенных действий запоминайте время и сравнивайте его с pygame.time.get_ticks() (миллисекунды с момента pygame.init()):

shield_until = 0

# при подборе бонуса
shield_until = pygame.time.get_ticks() + 3000  # щит на 3 секунды

# в каждом кадре
shield_active = pygame.time.get_ticks() < shield_until

Для повторяющихся событий подходит pygame.time.set_timer(), как в мини-игре выше.

Как проверять столкновения круглых объектов?

colliderect() сравнивает прямоугольники, поэтому круги «сталкиваются» уже углами своих рамок. Для круглых спрайтов задайте атрибут radius и передайте pygame.sprite.collide_circle в spritecollide():

caught = pygame.sprite.spritecollide(
    player, coins, dokill=True, collided=pygame.sprite.collide_circle
)

Если атрибута radius нет, pygame вычислит радиус как половину диагонали rect. Для объектов произвольной формы есть pygame.sprite.collide_mask — проверка по непрозрачным пикселям.

Как добавить фоновую музыку?

Длинные треки проигрывает модуль pygame.mixer.music — он читает файл потоком, не загружая его в память целиком:

pygame.mixer.music.load("theme.ogg")
pygame.mixer.music.set_volume(0.5)  # от 0.0 до 1.0
pygame.mixer.music.play(-1)         # -1 — повторять бесконечно

Короткие звуки (выстрел, монета) загружайте через pygame.mixer.Sound — они могут звучать одновременно с музыкой.

Как называются клавиши Shift, Ctrl и Tab?

Константы клавиш начинаются с K_: pygame.K_LSHIFT и pygame.K_RSHIFT (левый и правый Shift), pygame.K_LCTRL, pygame.K_TAB, pygame.K_RETURN. Чтобы узнать, зажат ли Shift вместе с другой клавишей, проверьте модификаторы события: if event.mod & pygame.KMOD_SHIFT:. Имя любой клавиши по коду возвращает pygame.key.name(event.key) — удобно для отладки.

Итог

Игра на Pygame — это цикл из трёх шагов: обработать события, обновить состояние, нарисовать кадр, а Clock.tick() держит стабильный FPS. Для движения используйте dt, для объектов — спрайты и группы, для столкновений — Rect и spritecollide(). Если игры не ваша цель, но нужен интерфейс с кнопками и полями, посмотрите Tkinter; больше идей для практики — в подборке проектов на Python, а обзор других пакетов — в статье о библиотеках Python. Полный справочник — в документации pygame.

  • #Python
  • #Pygame
  • #Игры
  • #Спрайты
↑ ↓ выборEnter открытьEsc закрыть