Alfa Brain

Создание простой игры на Python при помощи библиотеки Pygame

Алексей ВечкановАлексей Вечканов   

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

Справедливости ради, на Python нет больших игр, язык создан для других целей. Но создать простенькую, кроссплатформенную инди-игру - труда не составит.

Изображение статьи

Pygame - это библиотека для разработки игр и мультимедийных приложений на языке Python. Она была создана в 2000 году программистом из Ирландии, Питером Линдером, как ответ на отсутствие подобных инструментов для Python на тот момент.

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

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

Цель

Мы будем создавать простенькую аркадную по типу Space Invaders для двух игроков. Игроки управляют космическим кораблем и выпускают друг по другу снаряды. У каждого игрока есть некоторое количество жизней. Первый игрок потративший все жизни считается проигравшим.

Пример итоговой игры.
Пример итоговой игры.

Ссылка на репозиторий с готовой игрой, если вам вздумается сразу скачать готовый код и посмотреть результат.

Настройка окружения

Итак, приступим.

Создадим новый проект и файл main.py

Приступаем к работе с новым файлом main.py
Приступаем к работе с новым файлом main.py

Теперь нам необходимо установить библиотеку pygame в наш проект. Воспользуемся пакетным менеджером pip (он устанавливается вместе с Python)

Откройте терминал для ввода команды установки. Если вы используете VS Code вы можете открыть терминал в текущей папке при помощи кнопок на панели инструментов.

Открываем терминал для ввода команд
Открываем терминал для ввода команд

Вводим команду: pip install pygame

Пакетный менеджер pip устанавливает необходимые модули pygame
Пакетный менеджер pip устанавливает необходимые модули pygame

Отлично. Можем начинать создание игры.

Создания окна игры

Перепишите следующий код в файл main.py

Я буду помечать код комментариями, чтобы вам было понятно что происходит на каждой строчке.

# Подключаем игровой движок Pygame
import pygame

# Подготавливаем необходимые модули Pygame
pygame.init()

### Константы ###
# Константы размера окна
WIDTH = 600
HEIGHT = 300

# Задаем размеры игрового окна
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    pygame.display.update()

Запустите программу. Вы должны увидеть окно черного цвета с разрешением 600 на 300 пикселей.

Обратите внимание, что кнопки свернуть, и закрыть программу неактивны.

Кнопки в заголовке программы не активны
Кнопки в заголовке программы не активны

Давайте добавим возможность свернуть и закрыть игру при помощи стандартных кнопок.

Для этого в бесконечный цикл добавьте следующий код:

# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Постоянно проверяем события игры и если присутствует событие Выход - останавливаем игру.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # Обновляем кадры игры
    pygame.display.update()

В бесконечном цикле мы проверяем, не случилось ли события QUIT (выход), в процессе работы программы и если пользователь нажал на выход, значит мы зарегистрировали событие QUIT - закрываем игру при помощи команды pygame.quit()

Рисуем задний фон

Теперь давайте нарисуем задник нашей игры. Сохраните картинку и положите ее в директорию assets (предварительно создав ее) в корне директории вашей игры.

Задний фон игры.
Задний фон игры.
Директория assets в корне проекта игры.
Директория assets в корне проекта игры.

Так же нам понадобится модуль os. Модуль os позволяет работать с файловой системой компьютера, а именно, поможет нам загрузить картинку.

Загрузим картинку в память при помощи следующего кода:

# Подключаем модуль для работы с файловой системой
import os

# Загружаем изображение в память
SPACE_IMAGE = pygame.image.load(os.path.join('./assets', 'space.jpg'))

# Создаем объект фона с разрешением окна
SPACE_BG = pygame.transform.scale(SPACE_IMAGE, (WIDTH, HEIGHT))

И теперь достаточно поместить вызов screen.blit(SPACE, (0, 0)) внутрь нашего бесконечного цикла рисующего карды и фон будет виден игроку. Метод blit позволяет наложить одно изображение на другое. В нашем случае нам нужно наложит изображение поверх окна  screen.

# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Рисуем изображение на заднем фоне
    screen.blit(SPACE_BG, (0, 0))

Промежуточный результат #1

Работает:

- Кнопка закрыть/свернуть

- Отображается задний фон

# Подключаем игровой движок Pygame
import pygame
# Подключаем модуль для работы с файловой системой
import os

# Подготавливаем необходимые модули Pygame
pygame.init()

### Константы ###
# Константы размера окна
WIDTH = 600
HEIGHT = 300

# Задаем размеры игрового окна
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# Загружаем изображение в память
SPACE_IMAGE = pygame.image.load(os.path.join('./assets', 'space.jpg'))

# Создаем объект фона с разрешением окна
SPACE_BG = pygame.transform.scale(SPACE_IMAGE, (WIDTH, HEIGHT))

# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Рисуем изображение на заднем фоне
    screen.blit(SPACE_BG, (0, 0))

    # Постоянно проверяем события игры и если присутствует событие Выход - останавливаем игру.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # Обновляем кадры игры
    pygame.display.update()

Разделитель игрового экрана

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

Создадим объект прямоугольник.

# Формируем объект границы
BORDER = pygame.Rect(WIDTH//2 - 5, 0, 10, HEIGHT)

Обратите внимание, первые два аргумента функции это x и y расположения прямоугольника, а третий и четвертый аргументы это ширина и высота прямоугольника. Ширина прямоугольника 10, а высота такая же как высота экрана игры. По y прямоугольник начинается от верхнего края экрана, а вот по x мы берем ширину экрана, делим ее на 2 и отнимаем 5 (половину ширины прямоугольника). Благодаря этому прямоугольник располагается прямо посередине экрана игры. Осталось только отрисовать этот объект. Добавим отрисовку в бесконечный цикл, рядом с отрисовкой фона.

# Рисуем прямоугольник 
pygame.draw.rect(screen, "white", BORDER)

Метод rect объекта draw позволяет отрисовать прямоугольник. Первый аргумент screeen указывает где нужно отрисовать, второй аргумент задает цвет, третий аргумент задает сам объект рисования, наш BORDER.

Должно получиться вот так:

Рисуем разделитель экрана для игроков.
Рисуем разделитель экрана для игроков.

Промежуточный результат #2

Работает:

- Кнопка закрыть/свернуть

- Отображается задний фон

- Разделитель экрана

# Подключаем игровой движок Pygame
import pygame
# Подключаем модуль для работы с файловой системой
import os

# Подготавливаем необходимые модули Pygame
pygame.init()

### Константы ###
# Константы размера окна
WIDTH = 600
HEIGHT = 300

# Задаем размеры игрового окна
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# Загружаем изображение в память
SPACE_IMAGE = pygame.image.load(os.path.join('./assets', 'space.jpg'))

# Создаем объект фона с разрешением окна
SPACE_BG = pygame.transform.scale(SPACE_IMAGE, (WIDTH, HEIGHT))

# Формируем объект границы
BORDER = pygame.Rect(WIDTH//2 - 2, 0, 4, HEIGHT)

# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Рисуем изображение на заднем фоне
    screen.blit(SPACE_BG, (0, 0))

    # Рисуем прямоугольник 
    pygame.draw.rect(screen, "white", BORDER)

    # Постоянно проверяем события игры и если присутствует событие Выход - останавливаем игру.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # Обновляем кадры игры
    pygame.display.update()

Создаем игрока

Давайте нарисуем игроков и зададим им движение.

Ниже представлены две картинки: Красный космический корабль и Желтый космический корабль.

Скопируйте эти изображения в директорию assets вашего проекта под именами: spaceship_red.png и spaceship_yellow.png

Красный корабль.
Красный корабль.
Желтый корабль.
Желтый корабль.

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

# Размеры корабля
SPACESHIP_WIDTH = 55
SPACESHIP_HEIGHT = 40

В следующем участке кода, мы загружаем изображения красного и желтого кораблей и при помощи rotate разворачиваем изображения в нужную сторону.

# Загружаем изображение красного корабля 
RED_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_red.png'))
# Разворачиваем изображение в нужном направлении
RED_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    RED_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 90)

# Загружаем изображение желтого корабля 
YELLOW_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_yellow.png'))
# Разворачиваем изображение в нужном направлении
YELLOW_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    YELLOW_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 270)

Картинки загрузили, теперь нужно создать два объекта прямоугольника для будущих кораблей. Так как объекты Rect хранят ширину, высоту, и координаты x и y - это идеальные кандидаты для хранения данных кораблей.

# Создаем два объекта/прямоугольника
red = pygame.Rect(100, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)
yellow = pygame.Rect(500, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)

Отлично, осталось только отрисовать наши корабли на экране. Добавьте следующий код в бесконечный цикл отрисовки экрана.

# Рисуем корабли
screen.blit(YELLOW_SPACESHIP, (yellow.x, yellow.y))
screen.blit(RED_SPACESHIP, (red.x, red.y))

Промежуточный результат #3

Работает:

- Кнопка закрыть/свернуть

- Отображается задний фон

- Разделитель экрана

- Отрисовка игроков

# Подключаем игровой движок Pygame
import pygame
# Подключаем модуль для работы с файловой системой
import os

# Подготавливаем необходимые модули Pygame
pygame.init()

### Константы ###
# Константы размера окна
WIDTH = 600
HEIGHT = 300
# Размеры корабля
SPACESHIP_WIDTH = 55
SPACESHIP_HEIGHT = 40

# Задаем размеры игрового окна
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# Загружаем изображение в память
SPACE_IMAGE = pygame.image.load(os.path.join('./assets', 'space.jpg'))

# Создаем объект фона с разрешением окна
SPACE_BG = pygame.transform.scale(SPACE_IMAGE, (WIDTH, HEIGHT))

# Формируем объект границы
BORDER = pygame.Rect(WIDTH//2 - 2, 0, 4, HEIGHT)

# Загружаем изображение красного корабля 
RED_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_red.png'))
# Разворачиваем изображение в нужном направлении
RED_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    RED_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 90)

# Загружаем изображение желтого корабля 
YELLOW_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_yellow.png'))
# Разворачиваем изображение в нужном направлении
YELLOW_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    YELLOW_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 270)

# Создаем два объекта/прямоугольника
red = pygame.Rect(100, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)
yellow = pygame.Rect(500, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)

# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Рисуем изображение на заднем фоне
    screen.blit(SPACE_BG, (0, 0))

    # Рисуем прямоугольник 
    pygame.draw.rect(screen, "white", BORDER)

    # Рисуем корабли
    screen.blit(YELLOW_SPACESHIP, (yellow.x, yellow.y))
    screen.blit(RED_SPACESHIP, (red.x, red.y))

    # Постоянно проверяем события игры и если присутствует событие Выход - останавливаем игру.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # Обновляем кадры игры
    pygame.display.update()

Движение игроков

Теперь оживим наши корабли.

К блоку констант добавьте новую переменную VELOCITY. Данная переменная, как можно предположить из названия, задает скорость кораблей.

# Скорость корабля
VELOCITY = 1

Теперь напишем функцию движения красного корабля.

# Функция движения красного корабля
def red_handle_movement(keys_pressed, red):
    # Движение ВЛЕВО
    if keys_pressed[pygame.K_a] and red.x - VELOCITY > 0:
        red.x -= VELOCITY
    # Движение ВПРАВО (запрещаем двигаться вправо дальше чем граница игроков)
    if keys_pressed[pygame.K_d] and red.x + VELOCITY + red.width < BORDER.x:
        red.x += VELOCITY
    # Движение ВВЕРХ (запрещаем подниматься выше 0 по координате y)
    if keys_pressed[pygame.K_w] and red.y - VELOCITY > 0:
        red.y -= VELOCITY
    # Движение ВНИЗ (запрещаем опускаться ниже чем высота экрана за вычетом высоты корабля и дополнительных 15 для отступа)
    if keys_pressed[pygame.K_s] and red.y + VELOCITY + red.height < HEIGHT - 15:
        red.y += VELOCITY

Первым аргументом функция получает специальный объект справочник нажатых клавишей. Вторым аргументом функция получает объект Rect красного корабля.

Теперь добавим функцию для желтого корабля:

# Функция движения желтого корабля
def yellow_handle_movement(keys_pressed, yellow):
    # Движение ВЛЕВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_LEFT] and yellow.x - VELOCITY > BORDER.x + BORDER.width:
        yellow.x -= VELOCITY
    # Движение ВПРАВО
    if keys_pressed[pygame.K_RIGHT] and yellow.x + VELOCITY + yellow.width < WIDTH:
        yellow.x += VELOCITY
    # Движение ВВЕРХ
    if keys_pressed[pygame.K_UP] and yellow.y - VELOCITY > 0:
        yellow.y -= VELOCITY
    # Движение ВНИЗ
    if keys_pressed[pygame.K_DOWN] and yellow.y + VELOCITY + yellow.height < HEIGHT - 15:
        yellow.y += VELOCITY

В данных функциях мы проверяем нажатую клавишу (Для красного игрока мы проверяем английские клавиши WASD, для желтого игрока клавиши стрелки), и в зависимости от нажатой клавиши, изменяем нужную координату объекта на скорость корабля (VELOCITY).

Отлично, осталось вызвать эти функции в цикле. Добавьте в бесконечный цикл отрисовки следующие строчки:

# Узнаем нажатие клавишей
  keys_pressed = pygame.key.get_pressed()
  # Выполняем установку координат кораблей
  red_handle_movement(keys_pressed, red)
  yellow_handle_movement(keys_pressed, yellow)

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

Запустите программу, должно получиться вот так:

Движение красного и желтого.
Движение красного и желтого.

Промежуточный результат #4

Работает:

- Кнопка закрыть/свернуть

- Отображается задний фон

- Разделитель экрана

- Отрисовка игроков

- Движение игроков

# Подключаем игровой движок Pygame
import pygame
# Подключаем модуль для работы с файловой системой
import os

# Подготавливаем необходимые модули Pygame
pygame.init()

### Константы ###
# Константы размера окна
WIDTH = 600
HEIGHT = 300
# Размеры корабля
SPACESHIP_WIDTH = 55
SPACESHIP_HEIGHT = 40
# Скорость корабля
VELOCITY = 1

# Задаем размеры игрового окна
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# Загружаем изображение в память
SPACE_IMAGE = pygame.image.load(os.path.join('./assets', 'space.jpg'))

# Создаем объект фона с разрешением окна
SPACE_BG = pygame.transform.scale(SPACE_IMAGE, (WIDTH, HEIGHT))

# Формируем объект границы
BORDER = pygame.Rect(WIDTH//2 - 2, 0, 4, HEIGHT)

# Загружаем изображение красного корабля 
RED_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_red.png'))
# Разворачиваем изображение в нужном направлении
RED_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    RED_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 90)

# Загружаем изображение желтого корабля 
YELLOW_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_yellow.png'))
# Разворачиваем изображение в нужном направлении
YELLOW_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    YELLOW_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 270)

# Создаем два объекта/прямоугольника
red = pygame.Rect(100, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)
yellow = pygame.Rect(500, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)

# Функция движения красного корабля
def red_handle_movement(keys_pressed, red):
    # Движение ВЛЕВО
    if keys_pressed[pygame.K_a] and red.x - VELOCITY > 0:
        red.x -= VELOCITY
    # Движение ВПРАВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_d] and red.x + VELOCITY + red.width < BORDER.x:
        red.x += VELOCITY
    # Движение ВВЕРХ (запрещаем подниматься выше 0 по координате y)
    if keys_pressed[pygame.K_w] and red.y - VELOCITY > 0:
        red.y -= VELOCITY
    # Движение ВНИЗ (запрещаем опускаться ниже чем высота экрана за вычетом высоты корабля и дополнительных 15 для отступа)
    if keys_pressed[pygame.K_s] and red.y + VELOCITY + red.height < HEIGHT - 15:
        red.y += VELOCITY

# Функция движения желтого корабля
def yellow_handle_movement(keys_pressed, yellow):
    # Движение ВЛЕВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_LEFT] and yellow.x - VELOCITY > BORDER.x + BORDER.width:
        yellow.x -= VELOCITY
    # Движение ВПРАВО
    if keys_pressed[pygame.K_RIGHT] and yellow.x + VELOCITY + yellow.width < WIDTH:
        yellow.x += VELOCITY
    # Движение ВВЕРХ
    if keys_pressed[pygame.K_UP] and yellow.y - VELOCITY > 0:
        yellow.y -= VELOCITY
    # Движение ВНИЗ
    if keys_pressed[pygame.K_DOWN] and yellow.y + VELOCITY + yellow.height < HEIGHT - 15:
        yellow.y += VELOCITY


# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Рисуем изображение на заднем фоне
    screen.blit(SPACE_BG, (0, 0))

    # Рисуем прямоугольник 
    pygame.draw.rect(screen, "white", BORDER)

    # Рисуем корабли
    screen.blit(YELLOW_SPACESHIP, (yellow.x, yellow.y))
    screen.blit(RED_SPACESHIP, (red.x, red.y))

    # Постоянно проверяем события игры и если присутствует событие Выход - останавливаем игру.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # Узнаем нажатие клавишей
    keys_pressed = pygame.key.get_pressed()
    # Выполняем установку координат кораблей
    red_handle_movement(keys_pressed, red)
    yellow_handle_movement(keys_pressed, yellow)

    # Обновляем кадры игры
    pygame.display.update()

Ограничитель кадров

Сейчас корабли движутся слишком быстро. Это происходит потому, что наши корабли меняют свое положения в зависимости от скорости цикла While. Дело в том что цикл While очень быстрый и такие простые операции могут выполняться до нескольких тысяч раз в секунду. Это означает что скорость игры и отрисовки кадров будет зависеть от производительности процессора. Нам это не подходит. Давайте добавим ограничитель кадров.

Добавьте константу - количество кадров в секунду (frame per second):

# Количество кадров в секунду
FPS = 60

Затем создайте специальный объект ограничитель кадров.

# Ограничитель кадров
clock = pygame.time.Clock()

И теперь добавьте строчку clock.tick(FPS) сразу после вызова цикла:

# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Ограничиваем количество кадров игры
    clock.tick(FPS)

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

Но теперь корабли движутся очень медленно. Все верно, ведь мы так сильно сократили количество проверок нажатых клавиш. Поправить это просто - поднимите значение VELOCITY. Я подниму до 5.

Промежуточный результат #5

Работает:

- Кнопка закрыть/свернуть

- Отображается задний фон

- Разделитель экрана

- Отрисовка игроков

- Движение игроков

- Ограничитель кадров

# Подключаем игровой движок Pygame
import pygame
# Подключаем модуль для работы с файловой системой
import os

# Подготавливаем необходимые модули Pygame
pygame.init()

### Константы ###
# Константы размера окна
WIDTH = 600
HEIGHT = 300
# Размеры корабля
SPACESHIP_WIDTH = 55
SPACESHIP_HEIGHT = 40
# Скорость корабля
VELOCITY = 5
# Количество кадров в секунду
FPS = 60

# Ограничитель кадров
clock = pygame.time.Clock()

# Задаем размеры игрового окна
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# Загружаем изображение в память
SPACE_IMAGE = pygame.image.load(os.path.join('./assets', 'space.jpg'))

# Создаем объект фона с разрешением окна
SPACE_BG = pygame.transform.scale(SPACE_IMAGE, (WIDTH, HEIGHT))

# Формируем объект границы
BORDER = pygame.Rect(WIDTH//2 - 2, 0, 4, HEIGHT)

# Загружаем изображение красного корабля 
RED_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_red.png'))
# Разворачиваем изображение в нужном направлении
RED_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    RED_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 90)

# Загружаем изображение желтого корабля 
YELLOW_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_yellow.png'))
# Разворачиваем изображение в нужном направлении
YELLOW_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    YELLOW_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 270)

# Создаем два объекта/прямоугольника
red = pygame.Rect(100, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)
yellow = pygame.Rect(500, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)

# Функция движения красного корабля
def red_handle_movement(keys_pressed, red):
    # Движение ВЛЕВО
    if keys_pressed[pygame.K_a] and red.x - VELOCITY > 0:
        red.x -= VELOCITY
    # Движение ВПРАВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_d] and red.x + VELOCITY + red.width < BORDER.x:
        red.x += VELOCITY
    # Движение ВВЕРХ (запрещаем подниматься выше 0 по координате y)
    if keys_pressed[pygame.K_w] and red.y - VELOCITY > 0:
        red.y -= VELOCITY
    # Движение ВНИЗ (запрещаем опускаться ниже чем высота экрана за вычетом высоты корабля и дополнительных 15 для отступа)
    if keys_pressed[pygame.K_s] and red.y + VELOCITY + red.height < HEIGHT - 15:
        red.y += VELOCITY

# Функция движения желтого корабля
def yellow_handle_movement(keys_pressed, yellow):
    # Движение ВЛЕВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_LEFT] and yellow.x - VELOCITY > BORDER.x + BORDER.width:
        yellow.x -= VELOCITY
    # Движение ВПРАВО
    if keys_pressed[pygame.K_RIGHT] and yellow.x + VELOCITY + yellow.width < WIDTH:
        yellow.x += VELOCITY
    # Движение ВВЕРХ
    if keys_pressed[pygame.K_UP] and yellow.y - VELOCITY > 0:
        yellow.y -= VELOCITY
    # Движение ВНИЗ
    if keys_pressed[pygame.K_DOWN] and yellow.y + VELOCITY + yellow.height < HEIGHT - 15:
        yellow.y += VELOCITY


# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Ограничиваем количество кадров игры
    clock.tick(FPS)

    # Рисуем изображение на заднем фоне
    screen.blit(SPACE_BG, (0, 0))

    # Рисуем прямоугольник 
    pygame.draw.rect(screen, "white", BORDER)

    # Рисуем корабли
    screen.blit(YELLOW_SPACESHIP, (yellow.x, yellow.y))
    screen.blit(RED_SPACESHIP, (red.x, red.y))

    # Постоянно проверяем события игры и если присутствует событие Выход - останавливаем игру.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # Узнаем нажатие клавишей
    keys_pressed = pygame.key.get_pressed()
    # Выполняем установку координат кораблей
    red_handle_movement(keys_pressed, red)
    yellow_handle_movement(keys_pressed, yellow)

    # Обновляем кадры игры
    pygame.display.update()

Здоровье игроков

Теперь добавим отображение здоровья игроков.

Создайте две переменные для хранения здоровья игроков:

# Здоровье игроков
red_health = 10
yellow_health = 10

Так же к константам добавьте переменную шрифта:

# Шрифт здоровья
HEALTH_FONT = pygame.font.SysFont('comicsans', 20)

Отлично. Теперь в бесконечном цикле отрисовки нарисуем текст:

# Отображаем здоровье на экране
red_health_text = HEALTH_FONT.render(
    "Health: " + str(red_health), 1, "white")
yellow_health_text = HEALTH_FONT.render(
    "Health: " + str(yellow_health), 1, "white")
screen.blit(red_health_text, (10, 10))
screen.blit(yellow_health_text, (WIDTH - red_health_text.get_width() - 10, 10))

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

Должно получиться так:

Отображение здоровья игроков.
Отображение здоровья игроков.

Промежуточный результат #6

Работает:

- Кнопка закрыть/свернуть

- Отображается задний фон

- Разделитель экрана

- Отрисовка игроков

- Движение игроков

- Ограничитель кадров

- Здоровье игроков

# Подключаем игровой движок Pygame
import pygame
# Подключаем модуль для работы с файловой системой
import os

# Подготавливаем необходимые модули Pygame
pygame.init()

### Константы ###
# Константы размера окна
WIDTH = 600
HEIGHT = 300
# Размеры корабля
SPACESHIP_WIDTH = 55
SPACESHIP_HEIGHT = 40
# Скорость корабля
VELOCITY = 5
# Количество кадров в секунду
FPS = 60
# Шрифт здоровья
HEALTH_FONT = pygame.font.SysFont('comicsans', 20)

# Ограничитель кадров
clock = pygame.time.Clock()

# Задаем размеры игрового окна
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# Загружаем изображение в память
SPACE_IMAGE = pygame.image.load(os.path.join('./assets', 'space.jpg'))

# Создаем объект фона с разрешением окна
SPACE_BG = pygame.transform.scale(SPACE_IMAGE, (WIDTH, HEIGHT))

# Формируем объект границы
BORDER = pygame.Rect(WIDTH//2 - 2, 0, 4, HEIGHT)

# Загружаем изображение красного корабля 
RED_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_red.png'))
# Разворачиваем изображение в нужном направлении
RED_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    RED_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 90)

# Загружаем изображение желтого корабля 
YELLOW_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_yellow.png'))
# Разворачиваем изображение в нужном направлении
YELLOW_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    YELLOW_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 270)

# Создаем два объекта/прямоугольника
red = pygame.Rect(100, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)
yellow = pygame.Rect(500, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)

# Здоровье игроков
red_health = 10
yellow_health = 10

# Функция движения красного корабля
def red_handle_movement(keys_pressed, red):
    # Движение ВЛЕВО
    if keys_pressed[pygame.K_a] and red.x - VELOCITY > 0:
        red.x -= VELOCITY
    # Движение ВПРАВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_d] and red.x + VELOCITY + red.width < BORDER.x:
        red.x += VELOCITY
    # Движение ВВЕРХ (запрещаем подниматься выше 0 по координате y)
    if keys_pressed[pygame.K_w] and red.y - VELOCITY > 0:
        red.y -= VELOCITY
    # Движение ВНИЗ (запрещаем опускаться ниже чем высота экрана за вычетом высоты корабля и дополнительных 15 для отступа)
    if keys_pressed[pygame.K_s] and red.y + VELOCITY + red.height < HEIGHT - 15:
        red.y += VELOCITY

# Функция движения желтого корабля
def yellow_handle_movement(keys_pressed, yellow):
    # Движение ВЛЕВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_LEFT] and yellow.x - VELOCITY > BORDER.x + BORDER.width:
        yellow.x -= VELOCITY
    # Движение ВПРАВО
    if keys_pressed[pygame.K_RIGHT] and yellow.x + VELOCITY + yellow.width < WIDTH:
        yellow.x += VELOCITY
    # Движение ВВЕРХ
    if keys_pressed[pygame.K_UP] and yellow.y - VELOCITY > 0:
        yellow.y -= VELOCITY
    # Движение ВНИЗ
    if keys_pressed[pygame.K_DOWN] and yellow.y + VELOCITY + yellow.height < HEIGHT - 15:
        yellow.y += VELOCITY


# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Ограничиваем количество кадров игры
    clock.tick(FPS)

    # Рисуем изображение на заднем фоне
    screen.blit(SPACE_BG, (0, 0))

    # Рисуем прямоугольник 
    pygame.draw.rect(screen, "white", BORDER)

    # Рисуем корабли
    screen.blit(YELLOW_SPACESHIP, (yellow.x, yellow.y))
    screen.blit(RED_SPACESHIP, (red.x, red.y))

    # Отображаем здоровье на экране
    red_health_text = HEALTH_FONT.render(
        "Health: " + str(red_health), 1, "white")
    yellow_health_text = HEALTH_FONT.render(
        "Health: " + str(yellow_health), 1, "white")
    screen.blit(red_health_text, (10, 10))
    screen.blit(yellow_health_text, (WIDTH - red_health_text.get_width() - 10, 10))

    # Постоянно проверяем события игры и если присутствует событие Выход - останавливаем игру.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # Узнаем нажатие клавишей
    keys_pressed = pygame.key.get_pressed()
    # Выполняем установку координат кораблей
    red_handle_movement(keys_pressed, red)
    yellow_handle_movement(keys_pressed, yellow)

    # Обновляем кадры игры
    pygame.display.update()

Выстрелы

Наконец добавим выстрелы. Выстрелы будет работать следующий образом. Каждый игрок может выпустить до трех пуль за раз. Пока они не попадут в цель или не покинут экран, игрок не сможет сделать дополнительный выстрел. При попадании пули у игрока будет отниматься здоровье.

Создайте переменные для хранения выпущенных пуль, переменную для максимального количества выпущенных пуль, а так же переменную - скорость пули:

# Хранение выпущенных пуль
red_bullets = []
yellow_bullets = []
# Макс. выпущенных пуль
MAX_BULLETS = 3
# Скорость пули
BULLET_VEL = 7

Так же создайте два новых пользовательских события - попадание в красного и попадание в желтого:

# Пользовательские события попаданий
YELLOW_HIT = pygame.USEREVENT + 1
RED_HIT = pygame.USEREVENT + 2

Плюс один и плюс два просто позволяют создать новые коды событий, нам они понадобятся ниже.

В блок, где мы перебирали все события игры, и искали событие закрытия программы, чтобы отобразить кнопку закрыть, добавьте следующий код:

# Постоянно проверяем события игры и если присутствует событие Выход - останавливаем игру.
for event in pygame.event.get():
    if event.type == pygame.QUIT:
        pygame.quit()

    # Если произошло событие нажатия клавиши
        if event.type == pygame.KEYDOWN:
            # И нажали клавишу Пробел, а так же если длина листа выпущенных пуль красного меньше минимального
            if event.key == pygame.K_SPACE and len(red_bullets) < MAX_BULLETS:
                # Создаем объект прямоугольника для Пули
                bullet = pygame.Rect(
                    red.x + red.width, red.y + red.height//2 - 2, 10, 5)
                # Добавляем выпущенную пулю красному
                red_bullets.append(bullet)

            # Если нажали клавишу Enter, создаем выпущенную пулю желтому игроку
            if event.key == pygame.K_RETURN and len(yellow_bullets) < MAX_BULLETS:
                # Создаем объект прямоугольника для Пули
                bullet = pygame.Rect(
                    yellow.x, yellow.y + yellow.height//2 - 2, 10, 5)
                # Добавляем выпущенную пулю желтому
                yellow_bullets.append(bullet)

Благодаря этому коду, мы программно выпускаем пули у игроков и сохраняем выпущенные пули в соответствующих списках.

Теперь нужно обработать попадания и вылет пуль за экран.

Напишите следующую функцию:

# Обработка выпущенных пуль
def handle_bullets(yellow_bullets, red_bullets, yellow, red):
    # Перебираем все пули красного
    for bullet in red_bullets:
        # Двигаем пулю вправо
        bullet.x += BULLET_VEL
        # Если пуля задевает желтого игрока
        if yellow.colliderect(bullet):
            # Вызываем пользовательское событие попадание в Желтого
            pygame.event.post(pygame.event.Event(YELLOW_HIT))
            # Удаляем текущую пулю
            red_bullets.remove(bullet)
        # Удаляем пулю если она вышла за экран
        elif bullet.x > WIDTH:
            red_bullets.remove(bullet)

    # Перебираем все пули желтого
    for bullet in yellow_bullets:
        # Двигаем пулю влево
        bullet.x -= BULLET_VEL
        # Если пуля задевает красного игрока
        if red.colliderect(bullet):
            # Вызываем пользовательское событие попадание в Красного
            pygame.event.post(pygame.event.Event(RED_HIT))
            # Удаляем текущую пулю
            yellow_bullets.remove(bullet)
        # Удаляем пулю если она вышла за экран
        elif bullet.x < 0:
            yellow_bullets.remove(bullet)

Данная функция передвигает выстрелы, проверяет вылеты пули за экран, при этом удаляя пулю из листа, а так же проверяет попадание пули в корабли (метод colliderect) и если попадание случилось - вызывает соответствующее пользовательское событие.

Отлично, мы сделали выпуск пуль по нажатиям на клавиши, мы сделали обработку попаданий, но пуль все еще нет на экране. Пора отрисовать их.

Добавьте в бесконечный цикл следующий код:

# Перебираем пули красного и рисуем каждый кадр
for bullet in red_bullets:
    pygame.draw.rect(screen, "red", bullet)
# Перебираем пули желтого и рисуем каждый кадр
for bullet in yellow_bullets:
    pygame.draw.rect(screen, "yellow", bullet)

Каждый кадр мы проверяем наличие пуль в листе и рисуем их.

Ура! Теперь наши корабли стреляют!

Промежуточный результат #7

Работает:

- Кнопка закрыть/свернуть

- Отображается задний фон

- Разделитель экрана

- Отрисовка игроков

- Движение игроков

- Ограничитель кадров

- Здоровье игроков

- Выстрелы кораблей

# Подключаем игровой движок Pygame
import pygame
# Подключаем модуль для работы с файловой системой
import os

# Подготавливаем необходимые модули Pygame
pygame.init()

### Константы ###
# Константы размера окна
WIDTH = 600
HEIGHT = 300
# Размеры корабля
SPACESHIP_WIDTH = 55
SPACESHIP_HEIGHT = 40
# Скорость корабля
VELOCITY = 5
# Количество кадров в секунду
FPS = 60
# Шрифт здоровья
HEALTH_FONT = pygame.font.SysFont('comicsans', 20)
# Пользовательские события попаданий
YELLOW_HIT = pygame.USEREVENT + 1
RED_HIT = pygame.USEREVENT + 2

# Ограничитель кадров
clock = pygame.time.Clock()

# Задаем размеры игрового окна
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# Загружаем изображение в память
SPACE_IMAGE = pygame.image.load(os.path.join('./assets', 'space.jpg'))

# Создаем объект фона с разрешением окна
SPACE_BG = pygame.transform.scale(SPACE_IMAGE, (WIDTH, HEIGHT))

# Формируем объект границы
BORDER = pygame.Rect(WIDTH//2 - 2, 0, 4, HEIGHT)

# Загружаем изображение красного корабля 
RED_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_red.png'))
# Разворачиваем изображение в нужном направлении
RED_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    RED_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 90)

# Загружаем изображение желтого корабля 
YELLOW_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_yellow.png'))
# Разворачиваем изображение в нужном направлении
YELLOW_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    YELLOW_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 270)

# Создаем два объекта/прямоугольника
red = pygame.Rect(100, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)
yellow = pygame.Rect(500, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)

# Здоровье игроков
red_health = 10
yellow_health = 10

# Хранение выпущенных пуль
red_bullets = []
yellow_bullets = []
# Макс. выпущенных пуль
MAX_BULLETS = 3
# Скорость пули
BULLET_VEL = 7

# Функция движения красного корабля
def red_handle_movement(keys_pressed, red):
    # Движение ВЛЕВО
    if keys_pressed[pygame.K_a] and red.x - VELOCITY > 0:
        red.x -= VELOCITY
    # Движение ВПРАВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_d] and red.x + VELOCITY + red.width < BORDER.x:
        red.x += VELOCITY
    # Движение ВВЕРХ (запрещаем подниматься выше 0 по координате y)
    if keys_pressed[pygame.K_w] and red.y - VELOCITY > 0:
        red.y -= VELOCITY
    # Движение ВНИЗ (запрещаем опускаться ниже чем высота экрана за вычетом высоты корабля и дополнительных 15 для отступа)
    if keys_pressed[pygame.K_s] and red.y + VELOCITY + red.height < HEIGHT - 15:
        red.y += VELOCITY

# Функция движения желтого корабля
def yellow_handle_movement(keys_pressed, yellow):
    # Движение ВЛЕВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_LEFT] and yellow.x - VELOCITY > BORDER.x + BORDER.width:
        yellow.x -= VELOCITY
    # Движение ВПРАВО
    if keys_pressed[pygame.K_RIGHT] and yellow.x + VELOCITY + yellow.width < WIDTH:
        yellow.x += VELOCITY
    # Движение ВВЕРХ
    if keys_pressed[pygame.K_UP] and yellow.y - VELOCITY > 0:
        yellow.y -= VELOCITY
    # Движение ВНИЗ
    if keys_pressed[pygame.K_DOWN] and yellow.y + VELOCITY + yellow.height < HEIGHT - 15:
        yellow.y += VELOCITY

# Обработка выпущенных пуль
def handle_bullets(yellow_bullets, red_bullets, yellow, red):
    # Перебираем все пули красного
    for bullet in red_bullets:
        # Двигаем пулю вправо
        bullet.x += BULLET_VEL
        # Если пуля задевает желтого игрока
        if yellow.colliderect(bullet):
            # Вызываем пользовательское событие попадание в Желтого
            pygame.event.post(pygame.event.Event(YELLOW_HIT))
            # Удаляем текущую пулю
            red_bullets.remove(bullet)
        # Удаляем пулю если она вышла за экран
        elif bullet.x > WIDTH:
            red_bullets.remove(bullet)

    # Перебираем все пули желтого
    for bullet in yellow_bullets:
        # Двигаем пулю влево
        bullet.x -= BULLET_VEL
        # Если пуля задевает красного игрока
        if red.colliderect(bullet):
            # Вызываем пользовательское событие попадание в Красного
            pygame.event.post(pygame.event.Event(RED_HIT))
            # Удаляем текущую пулю
            yellow_bullets.remove(bullet)
        # Удаляем пулю если она вышла за экран
        elif bullet.x < 0:
            yellow_bullets.remove(bullet)


# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Ограничиваем количество кадров игры
    clock.tick(FPS)

    # Рисуем изображение на заднем фоне
    screen.blit(SPACE_BG, (0, 0))

    # Рисуем прямоугольник 
    pygame.draw.rect(screen, "white", BORDER)

    # Рисуем корабли
    screen.blit(YELLOW_SPACESHIP, (yellow.x, yellow.y))
    screen.blit(RED_SPACESHIP, (red.x, red.y))

    # Отображаем здоровье на экране
    red_health_text = HEALTH_FONT.render(
        "Health: " + str(red_health), 1, "white")
    yellow_health_text = HEALTH_FONT.render(
        "Health: " + str(yellow_health), 1, "white")
    screen.blit(red_health_text, (10, 10))
    screen.blit(yellow_health_text, (WIDTH - red_health_text.get_width() - 10, 10))

    # Постоянно проверяем события игры и если присутствует событие Выход - останавливаем игру.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

        # Если произошло событие нажатия клавиши
        if event.type == pygame.KEYDOWN:
            # И нажали клавишу Пробел, а так же если длина листа выпущенных пуль красного меньше минимального
            if event.key == pygame.K_SPACE and len(red_bullets) < MAX_BULLETS:
                # Создаем объект прямоугольника для Пули
                bullet = pygame.Rect(
                    red.x + red.width, red.y + red.height//2 - 2, 10, 5)
                # Добавляем выпущенную пулю красному
                red_bullets.append(bullet)

            # Если нажали клавишу Enter, создаем выпущенную пулю желтому игроку
            if event.key == pygame.K_RETURN and len(yellow_bullets) < MAX_BULLETS:
                # Создаем объект прямоугольника для Пули
                bullet = pygame.Rect(
                    yellow.x, yellow.y + yellow.height//2 - 2, 10, 5)
                # Добавляем выпущенную пулю желтому
                yellow_bullets.append(bullet)

    # Узнаем нажатие клавишей
    keys_pressed = pygame.key.get_pressed()
    # Выполняем установку координат кораблей
    red_handle_movement(keys_pressed, red)
    yellow_handle_movement(keys_pressed, yellow)

    # Проверяем столкновения пуль
    handle_bullets(yellow_bullets, red_bullets, yellow, red)

    # Перебираем пули красного и рисуем каждый кадр
    for bullet in red_bullets:
        pygame.draw.rect(screen, "red", bullet)
    # Перебираем пули желтого и рисуем каждый кадр
    for bullet in yellow_bullets:
        pygame.draw.rect(screen, "yellow", bullet)

    # Обновляем кадры игры
    pygame.display.update()

Учет попаданий - Отображение победителя

Теперь нужно при попадании уменьшать здоровье игроков. Ранее мы создали пользовательские события RED_HIT и YELLOW_HIT и при попадании в соответствующего игрока вызвали соответствующее событие. Теперь достаточно обработать эти события.

Добавьте этот код к участку кода где мы проверяли события нажатия кнопки выйти и нажатия кнопок Space (Пробел) и Enter.

# Если случилось пользовательское событие RED_HIT отнимаем жизни у Красного
if event.type == RED_HIT:
    red_health -= 1

# Если случилось пользовательское событие YELLOW_HIT отнимаем жизни у Желтого
if event.type == YELLOW_HIT:  
    yellow_health -= 1

Если случилось пользовательское событие RED_HIT отнимаем жизни у Красного, если событие YELLOW_HIT отнимаем жизни у Желтого. Думаю теперь стало понятно для чего мы делали пользовательские события. Проверьте. Теперь здоровье должно отниматься, и визуально тоже.

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

Напишите следующую функцию:

# Функция рисует экран победителя
def draw_winner(text):
    # Создаем шрифт для победителя
    WINNER_FONT = pygame.font.SysFont('comicsans', 60)
    # Создаем надпись
    draw_text = WINNER_FONT.render(text, 1, 'white')
    # Устанавливаем надпись в центре игрового поля
    screen.blit(draw_text, (WIDTH/2 - draw_text.get_width() /
                         2, HEIGHT/2 - draw_text.get_height()/2))
    
    # Обновляем кадр игры
    pygame.display.update()
    # Задержка после победы
    pygame.time.delay(2000)

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

Теперь в секции бесконечного цикла добавьте этот код:

# Если здоровье упало до нуля, рисуем имя победителя.
winner_text = ""
if red_health <= 0:
    winner_text = "Yellow Wins!"

if yellow_health <= 0:
    winner_text = "Red Wins!"

if winner_text != "":
    draw_winner(winner_text)
    # Обнуляем пули
    red_bullets.clear()
    yellow_bullets.clear()
    # Восстанавливаем здоровье
    red_health = 10
    yellow_health = 10

Каждый кадр игры мы проверяем, не опустился ли показатель здоровья одного из игроков до нуля и если это случилось, рисуем имя победителя, затем делаем паузу в две секунды, затем обнуляем все показатели игры - выстрелы и здоровье.

Поздравляю - база игры готова!

Основа игры.
Основа игры.

Промежуточный результат #8

Работает:

- Кнопка закрыть/свернуть

- Отображается задний фон

- Разделитель экрана

- Отрисовка игроков

- Движение игроков

- Ограничитель кадров

- Здоровье игроков

- Выстрелы кораблей

- Экран победителя и обнуление показателей

# Подключаем игровой движок Pygame
import pygame
# Подключаем модуль для работы с файловой системой
import os

# Подготавливаем необходимые модули Pygame
pygame.init()

### Константы ###
# Константы размера окна
WIDTH = 600
HEIGHT = 300
# Размеры корабля
SPACESHIP_WIDTH = 55
SPACESHIP_HEIGHT = 40
# Скорость корабля
VELOCITY = 5
# Количество кадров в секунду
FPS = 60
# Шрифт здоровья
HEALTH_FONT = pygame.font.SysFont('comicsans', 20)
# Пользовательские события попаданий
YELLOW_HIT = pygame.USEREVENT + 1
RED_HIT = pygame.USEREVENT + 2

# Ограничитель кадров
clock = pygame.time.Clock()

# Задаем размеры игрового окна
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# Загружаем изображение в память
SPACE_IMAGE = pygame.image.load(os.path.join('./assets', 'space.jpg'))

# Создаем объект фона с разрешением окна
SPACE_BG = pygame.transform.scale(SPACE_IMAGE, (WIDTH, HEIGHT))

# Формируем объект границы
BORDER = pygame.Rect(WIDTH//2 - 2, 0, 4, HEIGHT)

# Загружаем изображение красного корабля 
RED_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_red.png'))
# Разворачиваем изображение в нужном направлении
RED_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    RED_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 90)

# Загружаем изображение желтого корабля 
YELLOW_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_yellow.png'))
# Разворачиваем изображение в нужном направлении
YELLOW_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    YELLOW_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 270)

# Создаем два объекта/прямоугольника
red = pygame.Rect(100, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)
yellow = pygame.Rect(500, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)

# Здоровье игроков
red_health = 10
yellow_health = 10

# Хранение выпущенных пуль
red_bullets = []
yellow_bullets = []
# Макс. выпущенных пуль
MAX_BULLETS = 3
# Скорость пули
BULLET_VEL = 7

# Функция движения красного корабля
def red_handle_movement(keys_pressed, red):
    # Движение ВЛЕВО
    if keys_pressed[pygame.K_a] and red.x - VELOCITY > 0:
        red.x -= VELOCITY
    # Движение ВПРАВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_d] and red.x + VELOCITY + red.width < BORDER.x:
        red.x += VELOCITY
    # Движение ВВЕРХ (запрещаем подниматься выше 0 по координате y)
    if keys_pressed[pygame.K_w] and red.y - VELOCITY > 0:
        red.y -= VELOCITY
    # Движение ВНИЗ (запрещаем опускаться ниже чем высота экрана за вычетом высоты корабля и дополнительных 15 для отступа)
    if keys_pressed[pygame.K_s] and red.y + VELOCITY + red.height < HEIGHT - 15:
        red.y += VELOCITY

# Функция движения желтого корабля
def yellow_handle_movement(keys_pressed, yellow):
    # Движение ВЛЕВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_LEFT] and yellow.x - VELOCITY > BORDER.x + BORDER.width:
        yellow.x -= VELOCITY
    # Движение ВПРАВО
    if keys_pressed[pygame.K_RIGHT] and yellow.x + VELOCITY + yellow.width < WIDTH:
        yellow.x += VELOCITY
    # Движение ВВЕРХ
    if keys_pressed[pygame.K_UP] and yellow.y - VELOCITY > 0:
        yellow.y -= VELOCITY
    # Движение ВНИЗ
    if keys_pressed[pygame.K_DOWN] and yellow.y + VELOCITY + yellow.height < HEIGHT - 15:
        yellow.y += VELOCITY

# Обработка выпущенных пуль
def handle_bullets(yellow_bullets, red_bullets, yellow, red):
    # Перебираем все пули красного
    for bullet in red_bullets:
        # Двигаем пулю вправо
        bullet.x += BULLET_VEL
        # Если пуля задевает желтого игрока
        if yellow.colliderect(bullet):
            # Вызываем пользовательское событие попадание в Желтого
            pygame.event.post(pygame.event.Event(YELLOW_HIT))
            # Удаляем текущую пулю
            red_bullets.remove(bullet)
        # Удаляем пулю если она вышла за экран
        elif bullet.x > WIDTH:
            red_bullets.remove(bullet)

    # Перебираем все пули желтого
    for bullet in yellow_bullets:
        # Двигаем пулю влево
        bullet.x -= BULLET_VEL
        # Если пуля задевает красного игрока
        if red.colliderect(bullet):
            # Вызываем пользовательское событие попадание в Красного
            pygame.event.post(pygame.event.Event(RED_HIT))
            # Удаляем текущую пулю
            yellow_bullets.remove(bullet)
        # Удаляем пулю если она вышла за экран
        elif bullet.x < 0:
            yellow_bullets.remove(bullet)

# Функция рисует экран победителя
def draw_winner(text):
    # Создаем шрифт для победителя
    WINNER_FONT = pygame.font.SysFont('comicsans', 60)
    # Создаем надпись
    draw_text = WINNER_FONT.render(text, 1, 'white')
    # Устанавливаем надпись в центре игрового поля
    screen.blit(draw_text, (WIDTH/2 - draw_text.get_width() /
                         2, HEIGHT/2 - draw_text.get_height()/2))
    
    # Обновляем кадр игры
    pygame.display.update()
    # Задержка после победы
    pygame.time.delay(2000)

# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Ограничиваем количество кадров игры
    clock.tick(FPS)

    # Рисуем изображение на заднем фоне
    screen.blit(SPACE_BG, (0, 0))

    # Рисуем прямоугольник 
    pygame.draw.rect(screen, "white", BORDER)

    # Рисуем корабли
    screen.blit(YELLOW_SPACESHIP, (yellow.x, yellow.y))
    screen.blit(RED_SPACESHIP, (red.x, red.y))

    # Отображаем здоровье на экране
    red_health_text = HEALTH_FONT.render(
        "Health: " + str(red_health), 1, "white")
    yellow_health_text = HEALTH_FONT.render(
        "Health: " + str(yellow_health), 1, "white")
    screen.blit(red_health_text, (10, 10))
    screen.blit(yellow_health_text, (WIDTH - red_health_text.get_width() - 10, 10))

    # Постоянно проверяем события игры и если присутствует событие Выход - останавливаем игру.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

        # Если случилось пользовательское событие RED_HIT отнимаем жизни у Красного
        if event.type == RED_HIT:
            red_health -= 1

        # Если случилось пользовательское событие YELLOW_HIT отнимаем жизни у Желтого
        if event.type == YELLOW_HIT:
            yellow_health -= 1

        # Если произошло событие нажатия клавиши
        if event.type == pygame.KEYDOWN:
            # И нажали клавишу Пробел, а так же если длина листа выпущенных пуль красного меньше минимального
            if event.key == pygame.K_SPACE and len(red_bullets) < MAX_BULLETS:
                # Создаем объект прямоугольника для Пули
                bullet = pygame.Rect(
                    red.x + red.width, red.y + red.height//2 - 2, 10, 5)
                # Добавляем выпущенную пулю красному
                red_bullets.append(bullet)

            # Если нажали клавишу Enter, создаем выпущенную пулю желтому игроку
            if event.key == pygame.K_RETURN and len(yellow_bullets) < MAX_BULLETS:
                # Создаем объект прямоугольника для Пули
                bullet = pygame.Rect(
                    yellow.x, yellow.y + yellow.height//2 - 2, 10, 5)
                # Добавляем выпущенную пулю желтому
                yellow_bullets.append(bullet)

    # Узнаем нажатие клавишей
    keys_pressed = pygame.key.get_pressed()
    # Выполняем установку координат кораблей
    red_handle_movement(keys_pressed, red)
    yellow_handle_movement(keys_pressed, yellow)

    # Проверяем столкновения пуль
    handle_bullets(yellow_bullets, red_bullets, yellow, red)

    # Перебираем пули красного и рисуем каждый кадр
    for bullet in red_bullets:
        pygame.draw.rect(screen, "red", bullet)
    # Перебираем пули желтого и рисуем каждый кадр
    for bullet in yellow_bullets:
        pygame.draw.rect(screen, "yellow", bullet)

    # Если здоровье упало до нуля, рисуем имя победителя.
    winner_text = ""
    if red_health <= 0:
        winner_text = "Yellow Wins!"

    if yellow_health <= 0:
        winner_text = "Red Wins!"

    if winner_text != "":
        draw_winner(winner_text)
        # Обнуляем пули
        red_bullets.clear()
        yellow_bullets.clear()
        # Восстанавливаем здоровье
        red_health = 10
        yellow_health = 10

    # Обновляем кадры игры
    pygame.display.update()

Звуки

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

Скачайте два звука grenade.mp3 и silencer.mp3 по этой ссылке и положите их в папку assets.

В самом верху нашего кода, там где импортировали модули, добавьте строчку (секция Подготавливаем необходимые модули Pygame):

pygame.mixer.init()

Данная строчка инициализирует модуль работы со звуками.

Теперь подключим два наших звука к игре.

# Подключаем звуки к игре
# Звук попадания
BULLET_HIT_SOUND = pygame.mixer.Sound('./assets/grenade.mp3')
# Звук выстрела
BULLET_FIRE_SOUND = pygame.mixer.Sound('./assets/silencer.mp3')

Теперь необходимо расставить нужные звуки в нужных местах. Добавьте строчку BULLET_HIT_SOUND.play() в местах где мы отнимаем здоровье (обрабатываем попадание)

# Если случилось пользовательское событие RED_HIT отнимаем жизни у Красного
if event.type == RED_HIT:
    red_health -= 1
    # Звук попадания
    BULLET_HIT_SOUND.play()

# Если случилось пользовательское событие YELLOW_HIT отнимаем жизни у Желтого
if event.type == YELLOW_HIT:
    yellow_health -= 1
    # Звук попадания
    BULLET_HIT_SOUND.play()

Так же добавьте строчку BULLET_FIRE_SOUND.play() в местах где мы создаем пули при нажатии кнопок:

# Если произошло событие нажатия клавиши
if event.type == pygame.KEYDOWN:
    # И нажали клавишу Пробел, а так же если длина листа выпущенных пуль красного меньше минимального
    if event.key == pygame.K_SPACE and len(red_bullets) < MAX_BULLETS:
        # Создаем объект прямоугольника для Пули
        bullet = pygame.Rect(
            red.x + red.width, red.y + red.height//2 - 2, 10, 5)
        # Добавляем выпущенную пулю красному
        red_bullets.append(bullet)
        # Звук выстрела
        BULLET_FIRE_SOUND.play()

    # Если нажали клавишу Enter, создаем выпущенную пулю желтому игроку
    if event.key == pygame.K_RETURN and len(yellow_bullets) < MAX_BULLETS:
        # Создаем объект прямоугольника для Пули
        bullet = pygame.Rect(
            yellow.x, yellow.y + yellow.height//2 - 2, 10, 5)
        # Добавляем выпущенную пулю желтому
        yellow_bullets.append(bullet)
        # Звук выстрела
        BULLET_FIRE_SOUND.play()

Так намного лучше! Игра заиграла новыми красками!

Итоговый результат

Работает:

- Кнопка закрыть/свернуть

- Отображается задний фон

- Разделитель экрана

- Отрисовка игроков

- Движение игроков

- Ограничитель кадров

- Здоровье игроков

- Выстрелы кораблей

- Звуки выстрелов и попаданий

# Подключаем игровой движок Pygame
import pygame
# Подключаем модуль для работы с файловой системой
import os

# Подготавливаем необходимые модули Pygame
pygame.init()
pygame.mixer.init()

# Подключаем звуки к игре
# Звук попадания
BULLET_HIT_SOUND = pygame.mixer.Sound('./assets/grenade.mp3')
# Звук выстрела
BULLET_FIRE_SOUND = pygame.mixer.Sound('./assets/silencer.mp3')

### Константы ###
# Константы размера окна
WIDTH = 600
HEIGHT = 300
# Размеры корабля
SPACESHIP_WIDTH = 55
SPACESHIP_HEIGHT = 40
# Скорость корабля
VELOCITY = 5
# Количество кадров в секунду
FPS = 60
# Шрифт здоровья
HEALTH_FONT = pygame.font.SysFont('comicsans', 20)
# Пользовательские события попаданий
YELLOW_HIT = pygame.USEREVENT + 1
RED_HIT = pygame.USEREVENT + 2

# Ограничитель кадров
clock = pygame.time.Clock()

# Задаем размеры игрового окна
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# Загружаем изображение в память
SPACE_IMAGE = pygame.image.load(os.path.join('./assets', 'space.jpg'))

# Создаем объект фона с разрешением окна
SPACE_BG = pygame.transform.scale(SPACE_IMAGE, (WIDTH, HEIGHT))

# Формируем объект границы
BORDER = pygame.Rect(WIDTH//2 - 2, 0, 4, HEIGHT)

# Загружаем изображение красного корабля 
RED_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_red.png'))
# Разворачиваем изображение в нужном направлении
RED_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    RED_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 90)

# Загружаем изображение желтого корабля 
YELLOW_SPACESHIP_IMAGE = pygame.image.load(
    os.path.join('./assets', 'spaceship_yellow.png'))
# Разворачиваем изображение в нужном направлении
YELLOW_SPACESHIP = pygame.transform.rotate(pygame.transform.scale(
    YELLOW_SPACESHIP_IMAGE, (SPACESHIP_WIDTH, SPACESHIP_HEIGHT)), 270)

# Создаем два объекта/прямоугольника
red = pygame.Rect(100, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)
yellow = pygame.Rect(500, 150, SPACESHIP_WIDTH, SPACESHIP_HEIGHT)

# Здоровье игроков
red_health = 10
yellow_health = 10

# Хранение выпущенных пуль
red_bullets = []
yellow_bullets = []
# Макс. выпущенных пуль
MAX_BULLETS = 3
# Скорость пули
BULLET_VEL = 7

# Функция движения красного корабля
def red_handle_movement(keys_pressed, red):
    # Движение ВЛЕВО
    if keys_pressed[pygame.K_a] and red.x - VELOCITY > 0:
        red.x -= VELOCITY
    # Движение ВПРАВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_d] and red.x + VELOCITY + red.width < BORDER.x:
        red.x += VELOCITY
    # Движение ВВЕРХ (запрещаем подниматься выше 0 по координате y)
    if keys_pressed[pygame.K_w] and red.y - VELOCITY > 0:
        red.y -= VELOCITY
    # Движение ВНИЗ (запрещаем опускаться ниже чем высота экрана за вычетом высоты корабля и дополнительных 15 для отступа)
    if keys_pressed[pygame.K_s] and red.y + VELOCITY + red.height < HEIGHT - 15:
        red.y += VELOCITY

# Функция движения желтого корабля
def yellow_handle_movement(keys_pressed, yellow):
    # Движение ВЛЕВО (запрещаем пересекать границу игроков)
    if keys_pressed[pygame.K_LEFT] and yellow.x - VELOCITY > BORDER.x + BORDER.width:
        yellow.x -= VELOCITY
    # Движение ВПРАВО
    if keys_pressed[pygame.K_RIGHT] and yellow.x + VELOCITY + yellow.width < WIDTH:
        yellow.x += VELOCITY
    # Движение ВВЕРХ
    if keys_pressed[pygame.K_UP] and yellow.y - VELOCITY > 0:
        yellow.y -= VELOCITY
    # Движение ВНИЗ
    if keys_pressed[pygame.K_DOWN] and yellow.y + VELOCITY + yellow.height < HEIGHT - 15:
        yellow.y += VELOCITY

# Обработка выпущенных пуль
def handle_bullets(yellow_bullets, red_bullets, yellow, red):
    # Перебираем все пули красного
    for bullet in red_bullets:
        # Двигаем пулю вправо
        bullet.x += BULLET_VEL
        # Если пуля задевает желтого игрока
        if yellow.colliderect(bullet):
            # Вызываем пользовательское событие попадание в Желтого
            pygame.event.post(pygame.event.Event(YELLOW_HIT))
            # Удаляем текущую пулю
            red_bullets.remove(bullet)
        # Удаляем пулю если она вышла за экран
        elif bullet.x > WIDTH:
            red_bullets.remove(bullet)

    # Перебираем все пули желтого
    for bullet in yellow_bullets:
        # Двигаем пулю влево
        bullet.x -= BULLET_VEL
        # Если пуля задевает красного игрока
        if red.colliderect(bullet):
            # Вызываем пользовательское событие попадание в Красного
            pygame.event.post(pygame.event.Event(RED_HIT))
            # Удаляем текущую пулю
            yellow_bullets.remove(bullet)
        # Удаляем пулю если она вышла за экран
        elif bullet.x < 0:
            yellow_bullets.remove(bullet)

# Функция рисует экран победителя
def draw_winner(text):
    # Создаем шрифт для победителя
    WINNER_FONT = pygame.font.SysFont('comicsans', 60)
    # Создаем надпись
    draw_text = WINNER_FONT.render(text, 1, 'white')
    # Устанавливаем надпись в центре игрового поля
    screen.blit(draw_text, (WIDTH/2 - draw_text.get_width() /
                         2, HEIGHT/2 - draw_text.get_height()/2))
    
    # Обновляем кадр игры
    pygame.display.update()
    # Задержка после победы
    pygame.time.delay(2000)

# Запускаем бесконечный цикл программы
# Это делается чтобы программа не завершалась и постоянно рисовала новые кадры игры
while True:
    # Ограничиваем количество кадров игры
    clock.tick(FPS)

    # Рисуем изображение на заднем фоне
    screen.blit(SPACE_BG, (0, 0))

    # Рисуем прямоугольник 
    pygame.draw.rect(screen, "white", BORDER)

    # Рисуем корабли
    screen.blit(YELLOW_SPACESHIP, (yellow.x, yellow.y))
    screen.blit(RED_SPACESHIP, (red.x, red.y))

    # Отображаем здоровье на экране
    red_health_text = HEALTH_FONT.render(
        "Health: " + str(red_health), 1, "white")
    yellow_health_text = HEALTH_FONT.render(
        "Health: " + str(yellow_health), 1, "white")
    screen.blit(red_health_text, (10, 10))
    screen.blit(yellow_health_text, (WIDTH - red_health_text.get_width() - 10, 10))

    # Постоянно проверяем события игры и если присутствует событие Выход - останавливаем игру.
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

        # Если случилось пользовательское событие RED_HIT отнимаем жизни у Красного
        if event.type == RED_HIT:
            red_health -= 1
            # Звук попадания
            BULLET_HIT_SOUND.play()

        # Если случилось пользовательское событие YELLOW_HIT отнимаем жизни у Желтого
        if event.type == YELLOW_HIT:
            yellow_health -= 1
            # Звук попадания
            BULLET_HIT_SOUND.play()

        # Если произошло событие нажатия клавиши
        if event.type == pygame.KEYDOWN:
            # И нажали клавишу Пробел, а так же если длина листа выпущенных пуль красного меньше минимального
            if event.key == pygame.K_SPACE and len(red_bullets) < MAX_BULLETS:
                # Создаем объект прямоугольника для Пули
                bullet = pygame.Rect(
                    red.x + red.width, red.y + red.height//2 - 2, 10, 5)
                # Добавляем выпущенную пулю красному
                red_bullets.append(bullet)
                # Звук выстрела
                BULLET_FIRE_SOUND.play()

            # Если нажали клавишу Enter, создаем выпущенную пулю желтому игроку
            if event.key == pygame.K_RETURN and len(yellow_bullets) < MAX_BULLETS:
                # Создаем объект прямоугольника для Пули
                bullet = pygame.Rect(
                    yellow.x, yellow.y + yellow.height//2 - 2, 10, 5)
                # Добавляем выпущенную пулю желтому
                yellow_bullets.append(bullet)
                # Звук выстрела
                BULLET_FIRE_SOUND.play()

    # Узнаем нажатие клавишей
    keys_pressed = pygame.key.get_pressed()
    # Выполняем установку координат кораблей
    red_handle_movement(keys_pressed, red)
    yellow_handle_movement(keys_pressed, yellow)

    # Проверяем столкновения пуль
    handle_bullets(yellow_bullets, red_bullets, yellow, red)

    # Перебираем пули красного и рисуем каждый кадр
    for bullet in red_bullets:
        pygame.draw.rect(screen, "red", bullet)
    # Перебираем пули желтого и рисуем каждый кадр
    for bullet in yellow_bullets:
        pygame.draw.rect(screen, "yellow", bullet)

    # Если здоровье упало до нуля, рисуем имя победителя.
    winner_text = ""
    if red_health <= 0:
        winner_text = "Yellow Wins!"

    if yellow_health <= 0:
        winner_text = "Red Wins!"

    if winner_text != "":
        draw_winner(winner_text)
        # Обнуляем пули
        red_bullets.clear()
        yellow_bullets.clear()
        # Восстанавливаем здоровье
        red_health = 10
        yellow_health = 10

    # Обновляем кадры игры
    pygame.display.update()

Поздравляю! Наша игра готова! Теперь можно подумать как ее улучшить. Например, добавить новые звуки, создать возможность собирать powerup-ы и т.д.

Ссылка на репозиторий с готовой игрой.

Заключение

Библиотека PyGame не предоставит вам инструмента для создания AAA-игры, но для обучения вполне подходящий инструмент. С помощью PyGame легко рисовать фигуры, добавлять на них текстуры, работать с нажатиями клавиш и звуком. Все что нужно для Indie игры. Вот, кстати несколько примеров таких игр.

Если же хочется большего, думаю, стоит присмотреться к игровому движку Godot. Язык программирования Godot Script очень похож на Python, так что разобраться будет не сложно, а проекты там на порядок выше. Вот пример.

Надеюсь данная статься была вам интересна и вы нашли в ней что-то полезное! Удачи!



Поделиться: