Pygame не входит в стандартную поставку Python, для установки библиотеки выполните

    pip install pygame

Все возможности библиотеки Pygame нереально рассмотреть в одной статье, поэтому здесь мы затронем только самые базовые концепции – рисование, движение объектов, покадровую анимацию, обработку событий, обновление счетчика, обнаружение столкновения.

1 Окно и главный цикл приложения

Создание любого приложения на базе Pygame начинается с импорта и инициализации библиотеки. Затем нужно определить параметры окна, и по желанию – задать цвет (или изображение) фона:

    import pygame

    # инициализируем библиотеку Pygame
    pygame.init()

    # определяем размеры окна
    window_size = (300, 300)

    # задаем название окна
    pygame.display.set_caption("Синий фон")

    # создаем окно
    screen = pygame.display.set_mode(window_size)

    # задаем цвет фона
    background_color = (0, 0, 255)  # синий

    # заполняем фон заданным цветом
    screen.fill(background_color)

    # обновляем экран для отображения изменений
    pygame.display.flip()

    # показываем окно, пока пользователь не нажмет кнопку "Закрыть"
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()
Фигура

Цикл while True играет роль главного цикла программы – в нем происходит отслеживание событий приложения и действий пользователя. Функция pygame.quit() завершает работу приложения, и ее можно назвать противоположностью функции pygame.init(). Для завершения Python-процесса используется exit(), с той же целью можно использовать sys.exit(), но ее нужно импортировать в начале программы: import sys.

В качестве фона можно использовать изображение:

    import pygame

    pygame.init()

    window_size = (400, 400)
    screen = pygame.display.set_mode(window_size)
    pygame.display.set_caption("Peter the Piglet")

    # загружаем изображение
    background_image = pygame.image.load("background.png")

    # подгоняем масштаб под размер окна
    background_image = pygame.transform.scale(background_image, window_size)

    # накладываем изображение на поверхность
    screen.blit(background_image, (0, 0))

    pygame.display.flip()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()
Фигура

Обработку событий (нажатий клавиш и кликов) в Pygame реализовать очень просто – благодаря встроенным функциям. Приведенный ниже код изменяет цвет фона после клика по кнопке. Обратите внимание, что в Pygame можно задавать цвет несколькими способами:

    import pygame

    pygame.init()
    pygame.display.set_caption('Измени цвет фона')
    window_surface = pygame.display.set_mode((300, 300))
    background = pygame.Surface((300, 300))
    background.fill(pygame.Color('#000000'))

    color_list = [
        pygame.Color('#FF0000'),  # красный
        pygame.Color('#00FF00'),  # зеленый
        pygame.Color('#0000FF'),  # синий
        pygame.Color('#FFFF00'),  # желтый
        pygame.Color('#00FFFF'),  # бирюзовый
        pygame.Color('#FF00FF'),  # пурпурный
        pygame.Color('#FFFFFF')   # белый
    ]

    current_color_index = 0

    button_font = pygame.font.SysFont('Verdana', 15) # используем шрифт Verdana
    button_text_color = pygame.Color("black")
    button_color = pygame.Color("gray")
    button_rect = pygame.Rect(100, 115, 100, 50)
    button_text = button_font.render('Нажми!', True, button_text_color)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()
            elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                if button_rect.collidepoint(event.pos):
                    current_color_index = (current_color_index + 1) % len(color_list)
                    background.fill(color_list[current_color_index])

            window_surface.blit(background, (0, 0))
            pygame.draw.rect(window_surface, button_color, button_rect)
            button_rect_center = button_text.get_rect(center=button_rect.center)
            window_surface.blit(button_text, button_rect_center)
            pygame.display.update()
Фигура

Как очевидно из приведенного выше примера, основной цикл Pygame приложения состоит из трех повторяющихся действий:

  • Обработка событий (нажатий клавиш или кнопок).
  • Обновление состояния.
  • Отрисовка состояния на экране.

2 GUI для PyGame

Pygame позволяет легко и быстро интегрировать в проект многие нужные вещи – шрифты, звук, обработку событий, – однако не имеет встроенных виджетов для создания кнопок, лейблов, индикаторов выполнения и других подобных элементов интерфейса. Эту проблему разработчик должен решать либо самостоятельно (нарисовать прямоугольник, назначить ему функцию кнопки), либо с помощью дополнительных GUI-библиотек. Таких библиотек несколько, к самым популярным относятся:

  • Pygame GUI
  • Thorpy
  • PGU

Вот простой пример использования Pygame GUI – зеленые нули и единицы падают вниз в стиле «Матрицы»:

    import pygame
    import pygame_gui
    import random

    window_size = (800, 600)
    window = pygame.display.set_mode(window_size)
    pygame.display.set_caption('Матрица Lite')
    pygame.init()
    gui_manager = pygame_gui.UIManager(window_size)

    font = pygame.font.SysFont('Consolas', 20)
    text_color = pygame.Color('green')
    text_symbols = ['0', '1']
    text_pos = [(random.randint(0, window_size[0]), 0) for i in range(50)]
    text_speed = [(0, random.randint(1, 5)) for i in range(50)]
    text_surface_list = []

    button_size = (100, 50)
    button_pos = (350, 250)
    button_text = 'Матрица!'

    button = pygame_gui.elements.UIButton(
        relative_rect=pygame.Rect(button_pos, button_size),
        text=button_text,
        manager=gui_manager
    )

    while True:
        time_delta = pygame.time.Clock().tick(60) 

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

            if event.type == pygame_gui.UI_BUTTON_PRESSED:
                text_surface_list = []
                for i in range(50):
                    text_symbol = random.choice(text_symbols)
                    text_surface = font.render(text_symbol, True, text_color)
                    text_surface_list.append(text_surface)

            gui_manager.process_events(event)

        gui_manager.update(time_delta)

        window.fill(pygame.Color('black'))

        for i in range(50):
            text_pos[i] = (text_pos[i][0], text_pos[i][1] + text_speed[i][1])
            if text_pos[i][1] > window_size[1]:
                text_pos[i] = (random.randint(0, window_size[0]), -20)
            if len(text_surface_list) > i:
                window.blit(text_surface_list[i], text_pos[i])

        gui_manager.draw_ui(window)
        pygame.display.update()
Фигура

3 Анимация и обработка событий

Выше, в примере с падающими символами в «Матрице», уже был показан принцип простейшей имитации движения, который заключается в последовательном изменении координат объекта и обновлении экрана с установленной частотой кадра pygame.time.Clock().tick(60). Усложним задачу – сделаем простую анимацию с падающими розовыми «звездами». Приложение будет поддерживать обработку двух событий:

  • При клике мышью по экрану анимация останавливается.
  • При нажатии клавиши Enter – возобновляется.
  • Кроме того, добавим счетчик упавших звезд. Готовый код выглядит так:
    import pygame
    import random

    pygame.init()

    screen_width = 640
    screen_height = 480
    screen = pygame.display.set_mode((screen_width, screen_height))
    pygame.display.set_caption("Звезды падают вниз")

    black = (0, 0, 0)
    white = (255, 255, 255)
    pink = (255, 192, 203)

    font = pygame.font.SysFont("Verdana", 15)

    star_list = []
    for i in range(50):
        x = random.randrange(screen_width)
        y = random.randrange(-200, -50)
        speed = random.randrange(1, 5)
        star_list.append([x, y, speed])
    score = 0

    freeze = False # флаг для определения момента остановки

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()
            if event.type == pygame.MOUSEBUTTONDOWN: # останавливаем падение звезд по клику
                freeze = True
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RETURN: # возобновляем движение вниз, если нажат Enter
                    freeze = False

        if not freeze: # если флаг не активен,
            # звезды падают вниз
            for star in star_list:
                star[1] += star[2]
                if star[1] > screen_height:
                    star[0] = random.randrange(screen_width)
                    star[1] = random.randrange(-200, -50)
                    score += 1

        # рисуем звезды, выводим результаты подсчета
        screen.fill(black)
        for star in star_list:
            pygame.draw.circle(screen, pink, (star[0], star[1]), 3)
        score_text = font.render("Упало звезд: " + str(score), True, white)
        screen.blit(score_text, (10, 10))

        pygame.display.update()

        # устанавливаем частоту обновления экрана
        pygame.time.Clock().tick(60)
Фигура

4 Столкновение объектов

В этом примере расстояние между объектами проверяется до тех пор, пока объекты не столкнутся. В момент столкновение движение прекращается, а цвет объектов – изменяется:

    import pygame
    import math

    pygame.init()
    screen_width = 400
    screen_height = 480
    screen = pygame.display.set_mode((screen_width, screen_height))
    pygame.display.set_caption("Драматическое столкновение")

    # размеры и позиция окружности
    circle_pos = [screen_width/2, 50]
    circle_radius = 20

    # размеры и позиция прямоугольника
    rect_pos = [screen_width/2, screen_height-50]
    rect_width = 100
    rect_height = 50

    # цвета окружности и прямоугольника
    white = (255, 255, 255)
    black = (0, 0, 0)
    green = (0, 255, 0)
    red = (255, 0, 0)

    # скорость движения окружности
    speed = 5

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        # окружность движется вниз
        circle_pos[1] += speed

        # проверяем (используя формулу расстояния),
        # столкнулась ли окружность с прямоугольником
        circle_x = circle_pos[0]
        circle_y = circle_pos[1]
        rect_x = rect_pos[0]
        rect_y = rect_pos[1]
        distance_x = abs(circle_x - rect_x)
        distance_y = abs(circle_y - rect_y)
        if distance_x <= (rect_width/2 + circle_radius) and distance_y <= (rect_height/2 + circle_radius):
            circle_color = red # изменяем цвет фигур
            rect_color = green # в момент столкновения
        else:
            circle_color = green
            rect_color = black

        # рисуем окружность и прямоугольник на экране
        screen.fill(white)
        pygame.draw.circle(screen, circle_color, circle_pos, circle_radius)
        pygame.draw.rect(screen, rect_color, (rect_pos[0]-rect_width/2, rect_pos[1]-rect_height/2, rect_width, rect_height))

        pygame.display.update()

        # останавливаем движение окружности, если она
        # столкнулась с прямоугольником
        if circle_pos[1] + circle_radius >= rect_pos[1] - rect_height/2:
            speed = 0

        # задаем частоту обновления экрана
        pygame.time.Clock().tick(60)
Фигура

5 Управление движением объекта

Для управления движением (в нашем случае – с помощью клавиш ← и →) используются pygame.K_RIGHT (вправо) и pygame.K_LEFT (влево). В приведенном ниже примере передвижение падающих окружностей возможно только до момента приземления на дно игрового поля, или на предыдущие фигуры. Для упрощения вычисления факта столкновения фигур окружности вписываются в прямоугольники:

    import pygame
    import random

    pygame.init()
    screen_width = 640
    screen_height = 480
    screen = pygame.display.set_mode((screen_width, screen_height))

    # цвета окружностей
    white = (255, 255, 255)
    red = (255, 0, 0)
    green = (0, 255, 0)
    blue = (0, 0, 255)
    black = (0, 0, 0)
    yellow = (255, 255, 0)

    # цвет, скорость, начальная позиция окружности
    circle_radius = 30
    circle_speed = 3
    circle_color = random.choice([red, green, blue, yellow, white])
    circle_pos = [screen_width//2, -circle_radius]
    circle_landed = False

    # список приземлившихся окружностей и их позиций
    landed_circles = []

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        # если окружность не приземлилась
        if not circle_landed:
            # меняем направление по нажатию клавиши
            keys = pygame.key.get_pressed()
            if keys[pygame.K_LEFT]:
                circle_pos[0] -= circle_speed
            if keys[pygame.K_RIGHT]:
                circle_pos[0] += circle_speed

            # проверяем, столкнулась ли окружность с другой приземлившейся окружностью
            for landed_circle in landed_circles:
                landed_rect = pygame.Rect(landed_circle[0]-circle_radius, landed_circle[1]-circle_radius, circle_radius*2, circle_radius*2)
                falling_rect = pygame.Rect(circle_pos[0]-circle_radius, circle_pos[1]-circle_radius, circle_radius*2, circle_radius*2)
                if landed_rect.colliderect(falling_rect):
                    circle_landed = True
                    collision_x = circle_pos[0]
                    collision_y = landed_circle[1] - circle_radius*2
                    landed_circles.append((collision_x, collision_y, circle_color))
                    break

            # если окружность не столкнулась с другой приземлившейся окружностью
            if not circle_landed:
                # окружность движется вниз
                circle_pos[1] += circle_speed

                # проверяем, достигла ли окружность дна
                if circle_pos[1] + circle_radius > screen_height:
                    circle_pos[1] = screen_height - circle_radius
                    circle_landed = True
                    # добавляем окружность и ее позицию в список приземлившихся окружностей
                    landed_circles.append((circle_pos[0], circle_pos[1], circle_color))

        if circle_landed:
            # если окружность приземлилась, задаем параметры новой
            circle_pos = [screen_width//2, -circle_radius]
            circle_color = random.choice([red, green, blue, yellow, white])
            circle_landed = False

        # рисуем окружности
        screen.fill(black)
        for landed_circle in landed_circles:
            pygame.draw.circle(screen, landed_circle[2], (landed_circle[0], landed_circle[1]), circle_radius)
        pygame.draw.circle(screen, circle_color, circle_pos, circle_radius)
        pygame.display.update()

        # частота обновления экрана
        pygame.time.Clock().tick(60)
Фигура

6 Доп пример. Лабиринт 2D наивная реализация

Pygame игра, которая генерирует лабиринт из вертикальных стен со случайным количеством дверей. Игрок (красная окружность) должен проходить через двери.

    import pygame
    import random

    pygame.init()
    screen_width = 640
    screen_height = 480
    screen = pygame.display.set_mode((screen_width, screen_height))
    pygame.display.set_caption('Лабиринт')

    black = (0,0,0)
    white = (255,255,255)
    red = (255,0,0)
    blue = (0,0,255)
    green = (0,255,0)

    # параметры стен и дверей
    line_width = 10
    line_gap = 40
    line_offset = 20
    door_width = 20
    door_gap = 40
    max_openings_per_line = 5

    # параметры и стартовая позиция игрока
    player_radius = 10
    player_speed = 5
    player_x = screen_width - 12
    player_y = screen_height - line_offset

    # рисуем стены и двери
    lines = []
    for i in range(0, screen_width, line_gap):
        rect = pygame.Rect(i, 0, line_width, screen_height)
        num_openings = random.randint(1, max_openings_per_line)
        if num_openings == 1:
            # одна дверь посередине стены
            door_pos = random.randint(line_offset + door_width, screen_height - line_offset - door_width)
            lines.append(pygame.Rect(i, 0, line_width, door_pos - door_width))
            lines.append(pygame.Rect(i, door_pos + door_width, line_width, screen_height - door_pos - door_width))
        else:
            # несколько дверей
            opening_positions = [0] + sorted([random.randint(line_offset + door_width, screen_height - line_offset - door_width) for _ in range(num_openings-1)]) + [screen_height]
            for j in range(num_openings):
                lines.append(pygame.Rect(i, opening_positions[j], line_width, opening_positions[j+1]-opening_positions[j]-door_width))

    clock = pygame.time.Clock()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        # передвижение игрока
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] and player_x > player_radius:
            player_x -= player_speed
        elif keys[pygame.K_RIGHT] and player_x < screen_width - player_radius:
            player_x += player_speed
        elif keys[pygame.K_UP] and player_y > player_radius:
            player_y -= player_speed
        elif keys[pygame.K_DOWN] and player_y < screen_height - player_radius:
            player_y += player_speed

        # проверка столкновений игрока со стенами
        player_rect = pygame.Rect(player_x - player_radius, player_y - player_radius, player_radius * 2, player_radius * 2)
        for line in lines:
            if line.colliderect(player_rect):
                # в случае столкновения возвращаем игрока назад
                if player_x > line.left and player_x < line.right:
                    if player_y < line.top:
                        player_y = line.top - player_radius
                    else:
                        player_y = line.bottom + player_radius
                elif player_y > line.top and player_y < line.bottom:
                    if player_x < line.left:
                        player_x = line.left - player_radius
                    else:
                        player_x = line.right + player_radius
        screen.fill(black)
        for line in lines:
            pygame.draw.rect(screen, green, line)
        pygame.draw.circle(screen, red, (player_x, player_y), player_radius)
        pygame.display.update()
        clock.tick(60)
Фигура
Навигация
Содержание