Время на прочтение
6 мин
Количество просмотров 76K
Это первая часть серии руководств «Разработка игр с помощью Pygame
». Она предназначена для программистов начального и среднего уровней, которые заинтересованы в создании игр и улучшении собственных навыков кодирования на Python.
Что такое Pygame?
Pygame — это «игровая библиотека», набор инструментов, помогающих программистам создавать игры. К ним относятся:
-
Графика и анимация
-
Звук (включая музыку)
-
Управление (мышь, клавиатура, геймпад и так далее)
Игровой цикл
В сердце каждой игры лежит цикл, который принято называть «игровым циклом». Он запускается снова и снова, делая все, чтобы работала игра. Каждый цикл в игре называется кадром.
В каждом кадре происходит масса вещей, но их можно разбить на три категории:
1.Обработка ввода (события)
Речь идет обо всем, что происходит вне игры — тех событиях, на которые она должна реагировать. Это могут быть нажатия клавиш на клавиатуре, клики мышью и так далее.
2.Обновление игры
Изменение всего, что должно измениться в течение одного кадра. Если персонаж в воздухе, гравитация должна потянуть его вниз. Если два объекта встречаются на большой скорости, они должны взорваться.
3.Рендеринг (прорисовка)
В этом шаге все выводится на экран: фоны, персонажи, меню. Все, что игрок должен видеть, появляется на экране в нужном месте.
Время
Еще один важный аспект игрового цикла — скорость его работы. Многие наверняка знакомы с термином FPS, который расшифровывается как Frames Per Second (или кадры в секунду). Он указывает на то, сколько раз цикл должен повториться за одну секунду. Это важно, чтобы игра не была слишком медленной или быстрой. Важно и то, чтобы игра не работала с разной скоростью на разных ПК. Если персонажу необходимо 10 секунд на то, чтобы пересечь экран, эти 10 секунд должны быть неизменными для всех компьютеров.
Создание шаблона Pygame
Теперь, зная из каких элементов состоит игра, можно переходить к процессу написания кода. Начать стоит с создания простейшей программы pygame, которая всего лишь открывает окно и запускает игровой цикл. Это отправная точка для любого проекта pygame.
В начале программы нужно импортировать необходимые библиотеки и задать базовые переменные настроек игры:
# Pygame шаблон - скелет для нового проекта Pygame
import pygame
import random
WIDTH = 360 # ширина игрового окна
HEIGHT = 480 # высота игрового окна
FPS = 30 # частота кадров в секунду
Дальше необходимо открыть окно игры:
# создаем игру и окно
pygame.init()
pygame.mixer.init() # для звука
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
pygame.init()
— это команда, которая запускает pygame. screen
— окно программы, которое создается, когда мы задаем его размер в настройках. Дальше необходимо создать clock
, чтобы убедиться, что игра работает с заданной частотой кадров.
Теперь необходимо создать игровой цикл:
# Цикл игры
running = True
while running:
# Ввод процесса (события)
# Обновление
# Визуализация (сборка)
Игровой цикл — это цикл while
, контролируемый переменной running
. Если нужно завершить игру, необходимо всего лишь поменять значение running
на False
. В результате цикл завершится. Теперь можно заполнить каждый раздел базовым кодом.
Раздел рендеринга (отрисовки)
Начнем с раздела отрисовки. Персонажей пока нет, поэтому экран можно заполнить сплошным цветом. Чтобы сделать это, нужно разобраться, как компьютер обрабатывает цвета.
Экраны компьютеров сделаны из пикселей, каждый из которых содержит 3 элемента: красный, зеленый и синий. Цвет пикселя определяется тем, как горит каждый из элементов:
Каждый из трех основных цветов может иметь значение от 0 (выключен) до 255 (включен на 100%), так что для каждого элемента есть 256 вариантов.
Узнать общее количество отображаемых компьютером цветов можно, умножив:
>>> 256 * 256 * 256
16,777,216
Теперь, зная, как работают цвета, можно задать их в начале программ:
# Цвета (R, G, B)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
А после этого — заполнить весь экран.
# Рендеринг
screen.fill(BLACK)
Но этого недостаточно. Дисплей компьютера работает не так. Изменить пиксель — значит передать команду видеокарте, чтобы она передала соответствующую команду экрану. По компьютерным меркам это очень медленный процесс. Если нужно нарисовать на экране много всего, это займет много времени. Исправить это можно оригинальным способом, который называется — двойная буферизация. Звучит необычно, но вот что это такое.
Представьте, что у вас есть двусторонняя доска, которую можно поворачивать, показывая то одну, то вторую сторону. Одна будет дисплеем (то, что видит игрок), а вторая — оставаться скрытой, ее сможет «видеть» только компьютер. С каждым кадром рендеринг будет происходить на задней части доски. Когда отрисовка завершается, доска поворачивается и ее содержимое демонстрируется игроку.
А это значит, что процесс отрисовки происходит один раз за кадр, а не при добавлении каждого элемента.
В pygame это происходит автоматически. Нужно всего лишь сказать доске, чтобы она перевернулась, когда отрисовка завершена. Эта команда называется flip()
:
# Рендеринг
screen.fill(BLACK)
# после отрисовки всего, переворачиваем экран
pygame.display.flip()
Главное — сделать так, чтобы функция flip()
была в конце. Если попытаться отрисовать что-то после поворота, это содержимое не отобразится на экране.
Раздел ввода (событий)
Игры еще нет, поэтому пока сложно сказать, какие кнопки или другие элементы управления понадобятся. Но нужно настроить одно важное событие. Если попытаться запустить программу сейчас, то станет понятно, что нет возможности закрыть окно. Нажать на крестик в верхнем углу недостаточно. Это тоже событие, и необходимо сообщить программе, чтобы она считала его и, соответственно, закрыла игру.
События происходят постоянно. Что, если игрок нажимает кнопку прыжка во время отрисовки? Это нельзя игнорировать, иначе игрок будет разочарован. Для этого pygame сохраняет все события, произошедшие с момента последнего кадра. Даже если игрок будет лупить по кнопкам, вы не пропустите ни одну из них. Создается список, и с помощью цикла for можно пройтись по всем из них.
for event in pygame.event.get():
# проверить закрытие окна
if event.type == pygame.QUIT:
running = False
В pygame много событий, на которые он способен реагировать. pygame.QUIT
— событие, которое стартует после нажатия крестика и передает значение False
переменной running
, в результате чего игровой цикл заканчивается.
Контроль FPS
Пока что нечего поместить в раздел Update
(обновление), но нужно убедиться, что настройка FPS
контролирует скорость игры. Это можно сделать следующим образом:
while running:
# держим цикл на правильной скорости
clock.tick(FPS)
Команда tick()
просит pygame определить, сколько занимает цикл, а затем сделать паузу, чтобы цикл (целый кадр) длился нужно время. Если задать значение FPS 30, это значит, что длина одного кадра — 1/30, то есть 0,03 секунды. Если цикл кода (обновление, рендеринг и прочее) занимает 0,01 секунды, тогда pygame сделает паузу на 0,02 секунды.
Итог
Наконец, нужно убедиться, что когда игровой цикл завершается, окно игры закрывается. Для этого нужно поместить функцию pygame.quit()
в конце кода. Финальный шаблон pygame будет выглядеть вот так:
# Pygame шаблон - скелет для нового проекта Pygame
import pygame
import random
WIDTH = 360
HEIGHT = 480
FPS = 30
# Задаем цвета
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# Создаем игру и окно
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
# Цикл игры
running = True
while running:
# Держим цикл на правильной скорости
clock.tick(FPS)
# Ввод процесса (события)
for event in pygame.event.get():
# check for closing window
if event.type == pygame.QUIT:
running = False
<span style="box-sizing: border-box; font-weight: inherit !important; font-size: inherit; padding-left: 0px; color: rgb(152, 143, 129);" class="token comment"># Обновление</span>
<span style="box-sizing: border-box; font-weight: inherit !important; font-size: inherit; padding-left: 0px; color: rgb(152, 143, 129);" class="token comment"># Рендеринг</span>
screen<span style="box-sizing: border-box; font-weight: inherit !important; font-size: inherit; color: rgb(168, 160, 149);" class="token punctuation">.</span>fill<span style="box-sizing: border-box; font-weight: inherit !important; font-size: inherit; color: rgb(168, 160, 149);" class="token punctuation">(</span>BLACK<span style="box-sizing: border-box; font-weight: inherit !important; font-size: inherit; color: rgb(168, 160, 149);" class="token punctuation">)</span>
<span style="box-sizing: border-box; font-weight: inherit !important; font-size: inherit; padding-left: 0px; color: rgb(152, 143, 129);" class="token comment"># После отрисовки всего, переворачиваем экран</span>
pygame<span style="box-sizing: border-box; font-weight: inherit !important; font-size: inherit; color: rgb(168, 160, 149);" class="token punctuation">.</span>display<span style="box-sizing: border-box; font-weight: inherit !important; font-size: inherit; color: rgb(168, 160, 149);" class="token punctuation">.</span>flip<span style="box-sizing: border-box; font-weight: inherit !important; font-size: inherit; color: rgb(168, 160, 149);" class="token punctuation">(</span><span style="box-sizing: border-box; font-weight: inherit !important; font-size: inherit; color: rgb(168, 160, 149);" class="token punctuation">)</span>
pygame.quit()
Ура! У вас есть рабочий шаблон Pygame. Сохраните его в файле с понятным названием, например, pygame_template.py
, чтобы можно было использовать его каждый раз при создании нового проекта pygame.
В следующем руководстве этот шаблон будет использован как отправная точка для изучения процесса отрисовки объектов на экране и их движения.
В этом руководстве мы подробно познакомимся с библиотекой PyGame и посмотрим, как с ее помощью можно создавать простые игры.
Содержание
- Основные аспекты перед началом
- Установка PyGame
- Простое PyGame-приложение
- Интерактивность
- Добавление функциональности
- Добавление изображений
- Работа со звуком
- Геометрические рисунки
- Шрифты и текст
- Модели ввода
- Логика сцены
- Заключение
Основные аспекты перед началом
Для создания игры необходимо ответить на 3 основных вопроса:
- Какую игру вы хотите создать?
- На каком языке вы хотите программировать?
- Для какой платформы вы хотите выпустить свою игру?
В большинстве случаев можно ответить на каждый из этих вопросов и найти идеальный фреймворк, соответствующий вашим требованиям. В других случаях это может оказаться невозможным.
PyGame вам подойдет, если на приведенные выше вопросы вы дали следующие ответы:
- Игра будет графической, но не 3D
- Вы хотите программировать на языке Python, который уже немного знаете
- Вы хотите создать клиентское приложение, которое потенциально может быть обернуто в отдельный исполняемый файл
Для других сценариев, особенно для 3D-игр, лучше подойдут другие языки и фреймворки.
Итак, давайте рассмотрим, как установить PyGame.
Хотите скачать книги по Python в 2 клика? Тогда вам в наш телеграм канал PythonBooks
Установить PyGame совсем не сложно. Но первым необходимым условием является установка Python.
Установка Python как на Windows, так и на Linux очень проста и понятна. Скачайте и установите его из официального сайта. Также вы можете установить Python через консоль, используя brew, apt, snap или любой другой менеджер пакетов, доступный в вашей ОС.
Далее необходимо загрузить PyGame. Инструкция о том, как это сделать, есть на официальном сайте библиотеки.
Установка очень проста. Просто следуйте инструкциям, а настройки по умолчанию считаются оптимальными.
Простое PyGame-приложение
Теперь можем приступить к созданию нашей первой игры при помощи PyGame.
Ниже представлено очень простое приложение, созданное с использованием конвейера PyGame. Ознакомьтесь с ним:
import pygame pygame.init() screen = pygame.display.set_mode((400, 300)) done = False while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True pygame.display.flip()
Давайте разберемся с синтаксисом.
import pygame
. Это, конечно же, необходимо для доступа к фреймворку PyGame.pygame.init()
. Этот вызов инициализирует все модули, необходимые для работы с PyGame.pygame.display.set_mode((width, height))
. Это создает окно заданного размера. Возвращаемое значение — это объектSurface
, над котором вы будете выполнять графические операции.pygame.event.get()
. Этот вызов очищает очередь событий. Если вы не вызовете его, сообщения от операционной системы начнут накапливаться, и ваша игра станет неотзывчивой.pygame.QUIT
. Это тип события, который генерируется при нажатии на кнопку закрытия в углу окна.pygame.display.flip()
. PyGame использует двойной буфер, а этот вызов обменивает буферы. Вам просто нужно знать, что этот вызов необходим, чтобы любые обновления, которые вы вносите в коде, стали видимыми.
Итак, что же получается на выходе, когда мы выполним приведенный выше код? Результат будет примерно таким:
Выглядит довольно просто, не так ли? Давайте начнем добавлять содержимое на наш экран. Для начала мы можем нарисовать прямоугольник. Это очень просто, и для этого мы используем pygame.draw.rect
.
# Add this somewhere after the event pumping and before the display.flip() pygame.draw.rect(screen, (0, 128, 255), pygame.Rect(30, 30, 60, 60))
Эта функция принимает три аргумента:
- Экземпляр поверхности, на которой нужно нарисовать прямоугольник.
- Кортеж
(red, green, blue)
, представляющий цвет для рисования. - Экземпляр
pygame.Rect
. Аргументами этого конструктора являются координаты x и y левого верхнего угла, ширина и высота.
Что же мы можем увидеть после добавления этого маленького кусочка кода? Добавился прямоугольник:
Вроде бы пока ничего особенного. Но начинать с чего-то нужно, верно?
Далее мы рассмотрим, как сделать игру более интерактивной.
Интерактивность
Смысл игры заключается в том, чтобы быть интерактивной. Сейчас единственное, с чем вы можете взаимодействовать, — это кнопка закрытия. А это не очень интересная игра, верно?
Все события пользовательского ввода проходят через очередь событий. Просто добавьте в этот цикл for
больше операторов if
, чтобы добавить интерактивности.
Вставьте перед циклом следующую строку:
is_blue = True
Измените код прямоугольника так, чтобы он выбирал цвет условно:
if is_blue: color = (0, 128, 255) else: color = (255, 100, 0) pygame.draw.rect(screen, color, pygame.Rect(30, 30, 60, 60))
И наконец, самое важное. Добавьте следующий оператор if
в цикл for
в той же последовательности, что и остальные операторы if
.
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: is_blue = not is_blue
Нажатие клавиши пробела изменит цвет квадрата. Вывод:
Довольно просто, не так ли? Далее в этом материале мы рассмотрим, как можно добавить в игру некоторую функциональность.
Добавление функциональности
Добавим возможность двигать наш квадрат. Теперь весь наш код выглядит примерно так:
import pygame pygame.init() screen = pygame.display.set_mode((400, 300)) done = False is_blue = True x = 30 y = 30 while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: is_blue = not is_blue pressed = pygame.key.get_pressed() if pressed[pygame.K_UP]: y -= 3 if pressed[pygame.K_DOWN]: y += 3 if pressed[pygame.K_LEFT]: x -= 3 if pressed[pygame.K_RIGHT]: x += 3 if is_blue: color = (0, 128, 255) else: color = (255, 100, 0) pygame.draw.rect(screen, color, pygame.Rect(x, y, 60, 60)) pygame.display.flip()
Проверим вывод при попытке сдвинуть прямоугольник вправо:
Это немного не то, чего мы ожидали, верно?
Две вещи являются неправильными:
- Каждый раз при отрисовке прямоугольника на экране остается прямоугольник из предыдущих кадров.
- Он движется очень быстро.
В первом случае перед рисованием прямоугольника нужно просто сбросить экран на черный. Для этого в Surface существует простой метод fill
. Он принимает кортеж RGB:
screen.fill((0, 0, 0))
Что касается второй проблемы, длительность каждого кадра настолько мала, насколько это может сделать ваш супермощный компьютер. Частота кадров должна быть приведена до разумного числа, например 60 кадров в секунду.
К счастью, в pygame.time
есть простой класс Clock
, который делает это за нас. В нем есть метод tick
, принимающий желаемую частоту кадров в секунду.
clock = pygame.time.Clock() ... while not done: ... # will block execution until 1/60 seconds have passed # since the previous time clock.tick was called. clock.tick(60)
Сложите все это вместе и получите:
import pygame pygame.init() screen = pygame.display.set_mode((400, 300)) done = False is_blue = True x = 30 y = 30 clock = pygame.time.Clock() while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: is_blue = not is_blue pressed = pygame.key.get_pressed() if pressed[pygame.K_UP]: y -= 3 if pressed[pygame.K_DOWN]: y += 3 if pressed[pygame.K_LEFT]: x -= 3 if pressed[pygame.K_RIGHT]: x += 3 screen.fill((0, 0, 0)) if is_blue: color = (0, 128, 255) else: color = (255, 100, 0) pygame.draw.rect(screen, color, pygame.Rect(x, y, 60, 60)) pygame.display.flip() clock.tick(60)
Как же теперь работает наш код? Посмотрите:
Далее мы рассмотрим, как можно работать с изображениями и как их можно интегрировать в нашу игру.
Добавление изображений
Вы можете создать пустую поверхность, просто вызвав конструктор Surface с кортежем ширины и высоты.
surface = pygame.Surface((100, 100))
В результате будет создано пустое 24-битное RGB-изображение размером 100 x 100 пикселей. По умолчанию оно будет иметь черный цвет. При наложении такого изображения на белый фон получится следующее:
Однако если требуется получить 32-битное RGBA-изображение, то можно включить необязательный аргумент в конструктор Surface. Для этого достаточно добавить в код следующую строку:
surface = pygame.Surface((100, 100), pygame.SRCALPHA)
В результате будет создано изображение размером 100 x 100, инициализированное как прозрачное. При рендеринге такого изображения на белом фоне получится следующее:
Одноцветные изображения и прямоугольники не очень интересны. Давайте воспользуемся файлом изображения.
Возьмем PNG-изображение шара. Его имя — ball.png
. Вот это изображение, посмотрите и скачайте:
Для использования изображения из файла существует простой вызов pygame.image.load()
.
Обратите внимание на следующий синтаксис:
image = pygame.image.load('ball.png')
Заменив код pygame.Surface((100, 100))
на приведенный выше код, мы получим на выходе вот такой результат:
Не используйте pygame.image.load
многократно для одного и того же изображения в цикле игры. Это неэффективный способ кодирования. Лучше инициализировать его единожды и использовать в дальнейшем любое количество раз.
Лучше всего создать словарь string-to-surface в одном централизованном месте. А затем написать функцию get_image
, которая принимает путь к файлу.
Если изображение уже загружено, то возвращается инициализированное изображение. Если это не происходит, то выполняется инициализация.
Преимущество этого подхода заключается в его быстроте и в том, что он устраняет беспорядок при инициализации изображений в начале ключевых участков логики игры. Также с его помощью можно централизовать абстракцию разделителей каталогов для разных операционных систем.
Но фрагмент кода стоит тысячи слов! Вот он:
import pygame import os _image_library = {} def get_image(path): global _image_library image = _image_library.get(path) if image == None: canonicalized_path = path.replace('/', os.sep).replace('', os.sep) image = pygame.image.load(canonicalized_path) _image_library[path] = image return image pygame.init() screen = pygame.display.set_mode((400, 300)) done = False clock = pygame.time.Clock() while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True screen.fill((255, 255, 255)) screen.blit(get_image('ball.png'), (20, 20)) pygame.display.flip() clock.tick(60)
Примечание
Windows не чувствительна к регистру имен файлов. Все остальные основные операционные системы чувствительны к регистру. Если ваш файл называется ball.png
и вы используете pygame.image.load('BALL.PNG')
, то игра будет работать, если вы работаете под Windows. Однако, если вы дадите свою игру кому-то, работающему на mac или Linux, она не будет работать и может выдать ошибочный результат.
Далее мы рассмотрим, как можно внедрить в игру музыку и звуковые эффекты.
Работа со звуком
API для работы со звуком и музыкой довольно просты. Давайте рассмотрим основные принципы, а потом можно будет двигаться дальше.
Однократное воспроизведение песни:
pygame.mixer.music.load('foo.mp3') pygame.mixer.music.play(0)
Воспроизведение песни бесконечно:
pygame.mixer.music.load('foo.mp3') pygame.mixer.music.play(-1)
Передаваемое число — это количество повторов песни. 0
соответствует однократному воспроизведению.
Вызов функции play
без числа аналогичен вызову с числом 0
.
pygame.mixer.music.play() # play once
Постановка песни в очередь:
pygame.mixer.music.queue('next_song.mp3')
Остановка песни:
pygame.mixer.music.stop()
Функция stop
также обнуляет все записи в очереди.
Если, например, требуется случайное воспроизведение из списка 5 песен, то можно создать список песен в виде глобальной очереди:
_songs = ['song_1.mp3', 'song_2.mp3', 'song_3.mp3', 'song_4.mp3', 'song_5.mp3']
Добавить флаг, указывающий, какая песня воспроизводится в данный момент:
_currently_playing_song = None
И написать функцию, выбирающую случайным образом другую песню, которая будет вызываться каждый раз при наступлении события SONG_END
:
import random def play_a_different_song(): global _currently_playing_song, _songs next_song = random.choice(_songs) while next_song == _currently_playing_song: next_song = random.choice(_songs) _currently_playing_song = next_song pygame.mixer.music.load(next_song) pygame.mixer.music.play()
А если вы хотите, чтобы они каждый раз воспроизводились в одной и той же последовательности, то код будет таким:
def play_next_song(): global _songs _songs = _songs[1:] + [_songs[0]] # move current song to the back of the list pygame.mixer.music.load(_songs[0]) pygame.mixer.music.play()
Музыкальный API очень централизован. Однако звуки требуют создания звуковых объектов, которые необходимо удерживать. Подобно изображениям, звуки имеют простой метод .play()
, который запускает воспроизведение звука.
effect = pygame.mixer.Sound('beep.wav') effect.play()
Поскольку можно ошибиться и сохранить лишние экземпляры звуков, я предлагаю создать библиотеку звуков, подобную библиотеке изображений:
_sound_library = {} def play_sound(path): global _sound_library sound = _sound_library.get(path) if sound == None: canonicalized_path = path.replace('/', os.sep).replace('', os.sep) sound = pygame.mixer.Sound(canonicalized_path) _sound_library[path] = sound sound.play()
Существует множество других возможностей, но это все, что вам нужно для выполнения 95% того, что требуется от большинства игр.
Далее мы рассмотрим, как можно реализовать в игре геометрические фигуры.
Геометрические рисунки
Как и в модуле mixer
, API рисования достаточно прост и имеет несколько параметров.
Создание прямоугольника
pygame.draw.rect(surface, color, pygame.Rect(left, top, width, height))
Создание круга
pygame.draw.circle(surface, color, (x, y), radius)
Встроенные контурные изображения очень плохи!
Это первое предостережение, о котором следует знать. Метод PyGame для создания «более толстых» контуров окружностей заключается в рисовании нескольких 1-пиксельных контуров. Теоретически это звучит неплохо, пока вы не увидите результат:
Круг имеет заметные разрывы в пикселях. Еще более неудобным является прямоугольник, в котором используется 4 вызова рисования линий нужной толщины. Это создает странные углы.
Для большинства вызовов API рисования можно использовать необязательный последний параметр — толщину.
# draw a rectangle pygame.draw.rect(surface, color, pygame.Rect(10, 10, 100, 100), 10) # draw a circle pygame.draw.circle(surface, color, (300, 60), 50, 10)
Примечание: Когда вы рисуете многоугольник, прямоугольник, круг и т.д., рисуйте его залитым или с толщиной в 1 пиксель. Все остальное реализовано не очень хорошо.
Если вам необходимо нарисовать прямоугольник с границами толщиной 10 пикселей, то лучше всего реализовать логику самостоятельно, используя либо 10 вызовов прямоугольника толщиной 1 пиксель, либо 4 вызова прямоугольника толщиной 10 пикселей для каждой стороны.
Создание многоугольника
Этот API довольно прост. Список точек представляет собой список кортежей координат x-y для многоугольника.
pygame.draw.polygon(surface, color, point_list)
Создание линии
pygame.draw.line(surface, color, (startX, startY), (endX, endY), width)
Посмотрите на этот удивительный вращающийся проволочный каркас куба, созданный с помощью метода line
и большого количества математических вычислений:
import pygame import math import time # Ignore these 3 functions. Scroll down for the relevant code. def create_background(width, height): colors = [(255, 255, 255), (212, 212, 212)] background = pygame.Surface((width, height)) tile_width = 20 y = 0 while y < height: x = 0 while x < width: row = y // tile_width col = x // tile_width pygame.draw.rect( background, colors[(row + col) % 2], pygame.Rect(x, y, tile_width, tile_width)) x += tile_width y += tile_width return background def is_trying_to_quit(event): pressed_keys = pygame.key.get_pressed() alt_pressed = pressed_keys[pygame.K_LALT] or pressed_keys[pygame.K_RALT] x_button = event.type == pygame.QUIT altF4 = alt_pressed and event.type == pygame.KEYDOWN and event.key == pygame.K_F4 escape = event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE return x_button or altF4 or escape def run_demos(width, height, fps): pygame.init() screen = pygame.display.set_mode((width, height)) pygame.display.set_caption('press space to see next demo') background = create_background(width, height) clock = pygame.time.Clock() demos = [ do_rectangle_demo, do_circle_demo, do_horrible_outlines, do_nice_outlines, do_polygon_demo, do_line_demo ] the_world_is_a_happy_place = 0 while True: the_world_is_a_happy_place += 1 for event in pygame.event.get(): if is_trying_to_quit(event): return if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: demos = demos[1:] screen.blit(background, (0, 0)) if len(demos) == 0: return demos[0](screen, the_world_is_a_happy_place) pygame.display.flip() clock.tick(fps) # Everything above this line is irrelevant to this tutorial. def do_rectangle_demo(surface, counter): left = (counter // 2) % surface.get_width() top = (counter // 3) % surface.get_height() width = 30 height = 30 color = (128, 0, 128) # purple # Draw a rectangle pygame.draw.rect(surface, color, pygame.Rect(left, top, width, height)) def do_circle_demo(surface, counter): x = surface.get_width() // 2 y = surface.get_height() // 2 max_radius = min(x, y) * 4 // 5 radius = abs(int(math.sin(counter * 3.14159 * 2 / 200) * max_radius)) + 1 color = (0, 140, 255) # aquamarine # Draw a circle pygame.draw.circle(surface, color, (x, y), radius) def do_horrible_outlines(surface, counter): color = (255, 0, 0) # red # draw a rectangle pygame.draw.rect(surface, color, pygame.Rect(10, 10, 100, 100), 10) # draw a circle pygame.draw.circle(surface, color, (300, 60), 50, 10) def do_nice_outlines(surface, counter): color = (0, 128, 0) # green # draw a rectangle pygame.draw.rect(surface, color, pygame.Rect(10, 10, 100, 10)) pygame.draw.rect(surface, color, pygame.Rect(10, 10, 10, 100)) pygame.draw.rect(surface, color, pygame.Rect(100, 10, 10, 100)) pygame.draw.rect(surface, color, pygame.Rect(10, 100, 100, 10)) # draw a circle center_x = 300 center_y = 60 radius = 45 iterations = 150 for i in range(iterations): ang = i * 3.14159 * 2 / iterations dx = int(math.cos(ang) * radius) dy = int(math.sin(ang) * radius) x = center_x + dx y = center_y + dy pygame.draw.circle(surface, color, (x, y), 5) def do_polygon_demo(surface, counter): color = (255, 255, 0) # yellow num_points = 8 point_list = [] center_x = surface.get_width() // 2 center_y = surface.get_height() // 2 for i in range(num_points * 2): radius = 100 if i % 2 == 0: radius = radius // 2 ang = i * 3.14159 / num_points + counter * 3.14159 / 60 x = center_x + int(math.cos(ang) * radius) y = center_y + int(math.sin(ang) * radius) point_list.append((x, y)) pygame.draw.polygon(surface, color, point_list) def rotate_3d_points(points, angle_x, angle_y, angle_z): new_points = [] for point in points: x = point[0] y = point[1] z = point[2] new_y = y * math.cos(angle_x) - z * math.sin(angle_x) new_z = y * math.sin(angle_x) + z * math.cos(angle_x) y = new_y # isn't math fun, kids? z = new_z new_x = x * math.cos(angle_y) - z * math.sin(angle_y) new_z = x * math.sin(angle_y) + z * math.cos(angle_y) x = new_x z = new_z new_x = x * math.cos(angle_z) - y * math.sin(angle_z) new_y = x * math.sin(angle_z) + y * math.cos(angle_z) x = new_x y = new_y new_points.append([x, y, z]) return new_points def do_line_demo(surface, counter): color = (0, 0, 0) # black cube_points = [ [-1, -1, 1], [-1, 1, 1], [1, 1, 1], [1, -1, 1], [-1, -1, -1], [-1, 1, -1], [1, 1, -1], [1, -1, -1]] connections = [ (0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7) ] t = counter * 2 * 3.14159 / 60 # this angle is 1 rotation per second # rotate about x axis every 2 seconds # rotate about y axis every 4 seconds # rotate about z axis every 6 seconds points = rotate_3d_points(cube_points, t / 2, t / 4, t / 6) flattened_points = [] for point in points: flattened_points.append( (point[0] * (1 + 1.0 / (point[2] + 3)), point[1] * (1 + 1.0 / (point[2] + 3)))) for con in connections: p1 = flattened_points[con[0]] p2 = flattened_points[con[1]] x1 = p1[0] * 60 + 200 y1 = p1[1] * 60 + 150 x2 = p2[0] * 60 + 200 y2 = p2[1] * 60 + 150 # This is the only line that really matters pygame.draw.line(surface, color, (x1, y1), (x2, y2), 4) run_demos(400, 300, 60)
Далее мы рассмотрим, как работать со шрифтами и текстом.
Шрифты и текст
Если вы ищете быстрый ответ на вопрос о том, как отобразить текст, то вот он:
import pygame pygame.init() screen = pygame.display.set_mode((640, 480)) clock = pygame.time.Clock() done = False font = pygame.font.SysFont("comicsansms", 72) text = font.render("Hello, World", True, (0, 128, 0)) while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: done = True screen.fill((255, 255, 255)) screen.blit(text, (320 - text.get_width() // 2, 240 - text.get_height() // 2)) pygame.display.flip() clock.tick(60)
Но, конечно, есть несколько неидеальных моментов.
Правило 1: никогда не следует предполагать, что на компьютере пользователя установлен определенный шрифт. Даже в CSS есть возможность определить иерархию используемых шрифтов. Если лучший вариант шрифта недоступен, используется альтернативный. Вы должны следовать той же схеме.
К счастью, в PyGame есть возможность перечислить все доступные на машине шрифты:
all_fonts = pygame.font.get_fonts()
Кроме того, существует способ инстанцирования системного шрифта по умолчанию:
font = pygame.font.Font(None, size)
Также вместо None
можно передать имя файла шрифта, который вы включаете в код, чтобы гарантировать существование идеального шрифта:
font = pygame.font.Font("myresources/fonts/Papyrus.ttf", 26)
Используя любую комбинацию из перечисленных выше, можно написать более совершенную функцию создания шрифта.
Например, вот функция, которая принимает список имен шрифтов, размер шрифта и создает экземпляр шрифта для первого доступного шрифта в списке. Если таковых нет, то будет использован системный шрифт по умолчанию.
def make_font(fonts, size): available = pygame.font.get_fonts() # get_fonts() returns a list of lowercase spaceless font names choices = map(lambda x:x.lower().replace(' ', ''), fonts) for choice in choices: if choice in available: return pygame.font.SysFont(choice, size) return pygame.font.Font(None, size)
Можно еще более усовершенствовать его, кэшируя экземпляр шрифта по его имени и размеру.
_cached_fonts = {} def get_font(font_preferences, size): global _cached_fonts key = str(font_preferences) + '|' + str(size) font = _cached_fonts.get(key, None) if font == None: font = make_font(font_preferences, size) _cached_fonts[key] = font return font
Можно пойти еще дальше и фактически кэшировать сам отрисованный текст. Хранить изображение дешевле, чем создавать новое, особенно если вы планируете, что один и тот же текст будет отображаться более чем в одном кадре подряд. Да! Если вы хотите, чтобы текст был читаемым, то это ваш план.
_cached_text = {} def create_text(text, fonts, size, color): global _cached_text key = '|'.join(map(str, (fonts, size, color, text))) image = _cached_text.get(key, None) if image == None: font = get_font(fonts, size) image = font.render(text, True, color) _cached_text[key] = image return image
Если собрать все это вместе, то получится "Hello, World"
, но с улучшенным кодом:
import pygame def make_font(fonts, size): available = pygame.font.get_fonts() # get_fonts() returns a list of lowercase spaceless font names choices = map(lambda x:x.lower().replace(' ', ''), fonts) for choice in choices: if choice in available: return pygame.font.SysFont(choice, size) return pygame.font.Font(None, size) _cached_fonts = {} def get_font(font_preferences, size): global _cached_fonts key = str(font_preferences) + '|' + str(size) font = _cached_fonts.get(key, None) if font == None: font = make_font(font_preferences, size) _cached_fonts[key] = font return font _cached_text = {} def create_text(text, fonts, size, color): global _cached_text key = '|'.join(map(str, (fonts, size, color, text))) image = _cached_text.get(key, None) if image == None: font = get_font(fonts, size) image = font.render(text, True, color) _cached_text[key] = image return image pygame.init() screen = pygame.display.set_mode((640, 480)) clock = pygame.time.Clock() done = False font_preferences = [ "Bizarre-Ass Font Sans Serif", "They definitely dont have this installed Gothic", "Papyrus", "Comic Sans MS"] text = create_text("Hello, World", font_preferences, 72, (0, 128, 0)) while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: done = True screen.fill((255, 255, 255)) screen.blit(text, (320 - text.get_width() // 2, 240 - text.get_height() // 2)) pygame.display.flip() clock.tick(60)
Далее мы рассмотрим, как можно учитывать входные данные.
Модели входов
Существует два основных способа получения информации о состоянии любого устройства ввода. Это проверка очереди событий и опрос.
Каждый раз, когда нажимается или отпускается клавиша или кнопка, или перемещается мышь, событие добавляется в очередь событий. Вы должны очищать эту очередь событий каждый кадр, вызывая pygame.event.get()
или pygame.event.pump()
.
pygame.event.get()
вернет список всех событий, произошедших с момента последнего опустошения очереди. Способ обработки этих событий зависит от их типа. Тип события можно проверить, прочитав поле event.type
.
Примеры практически всех типов распространенных событий можно увидеть в приведенном ниже примере расширенного кода. Есть и другие типы, но они достаточно редки.
Другой способ проверки событий — опрос состояния клавиш или кнопок.
pygame.key.get_pressed()
получает список булевых значений, описывающих состояние каждой клавиши клавиатуры.
pygame.mouse.get_pos()
возвращает координаты курсора мыши. Если мышь еще не перемещалась по экрану, возвращается значение (0, 0)
.
pygame.mouse.get_pressed()
возвращает состояние каждой кнопки мыши (как и pygame.key.get_pressed()
). Возвращаемое значение представляет собой кортеж размера 3, соответствующий левой, средней и правой кнопкам.
Вот небольшая программа, в которой есть немного всего:
- При перемещении мыши за ней рисуется след.
- Нажатие клавиши W при удержании Ctrl приводит к закрытию окна. То же самое для Alt + F4.
- Нажатие кнопки ESC приводит к закрытию окна
- При нажатии клавиш r, g или b след становится красным, зеленым и синим соответственно.
- При нажатии левой кнопки мыши след становится толще.
- При нажатии правой кнопки мыши след становится тоньше.
import pygame def main(): pygame.init() screen = pygame.display.set_mode((640, 480)) clock = pygame.time.Clock() radius = 15 x = 0 y = 0 mode = 'blue' points = [] while True: pressed = pygame.key.get_pressed() alt_held = pressed[pygame.K_LALT] or pressed[pygame.K_RALT] ctrl_held = pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL] for event in pygame.event.get(): # determin if X was clicked, or Ctrl+W or Alt+F4 was used if event.type == pygame.QUIT: return if event.type == pygame.KEYDOWN: if event.key == pygame.K_w and ctrl_held: return if event.key == pygame.K_F4 and alt_held: return if event.key == pygame.K_ESCAPE: return # determine if a letter key was pressed if event.key == pygame.K_r: mode = 'red' elif event.key == pygame.K_g: mode = 'green' elif event.key == pygame.K_b: mode = 'blue' if event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: # left click grows radius radius = min(200, radius + 1) elif event.button == 3: # right click shrinks radius radius = max(1, radius - 1) if event.type == pygame.MOUSEMOTION: # if mouse moved, add point to list position = event.pos points = points + [position] points = points[-256:] screen.fill((0, 0, 0)) # draw all points i = 0 while i < len(points) - 1: drawLineBetween(screen, i, points[i], points[i + 1], radius, mode) i += 1 pygame.display.flip() clock.tick(60) def drawLineBetween(screen, index, start, end, width, color_mode): c1 = max(0, min(255, 2 * index - 256)) c2 = max(0, min(255, 2 * index)) if color_mode == 'blue': color = (c1, c1, c2) elif color_mode == 'red': color = (c2, c1, c1) elif color_mode == 'green': color = (c1, c2, c1) dx = start[0] - end[0] dy = start[1] - end[1] iterations = max(abs(dx), abs(dy)) for i in range(iterations): progress = 1.0 * i / iterations aprogress = 1 - progress x = int(aprogress * start[0] + progress * end[0]) y = int(aprogress * start[1] + progress * end[1]) pygame.draw.circle(screen, color, (x, y), width) main()
И, наконец, мы должны рассмотреть так называемую централизованную логику сцены.
Централизованная логика сцены
Этот раздел касается не столько PyGame, сколько применения концепций проектирования хорошего программного обеспечения. Эта модель работы хорошо зарекомендовала себя при создании многих сложных игр.
Вот определение класса SceneBase
:
class SceneBase: def __init__(self): self.next = self def ProcessInput(self, events): print("uh-oh, you didn't override this in the child class") def Update(self): print("uh-oh, you didn't override this in the child class") def Render(self, screen): print("uh-oh, you didn't override this in the child class") def SwitchToScene(self, next_scene): self.next = next_scene
При переопределении этого класса необходимо заполнить 3 реализации методов.
ProcessInput
будет получать все события, произошедшие с момента последнего кадра.Update
. Поместите сюда свою игровую логику для сцены.Render
. Поместите сюда код рендеринга. В качестве входных данных он будет получатьSurface
главного экрана.
Конечно, для работы с этим классом нужна соответствующая обвязка. Вот пример программы, которая делает нечто простое: запускает конвейер PyGame со сценой, которая представляет собой пустой красный фон. Когда вы нажимаете клавишу ENTER, фон меняется на синий.
Этот код может показаться избыточным, но он также выполняет много других тонких моментов и в то же время сохраняет сложность логики игры в элегантной объектно-ориентированной модели. Как только вы начнете добавлять больше сложности в вашу игру, эта модель сэкономит вам много времени на отладке и изменении кода.
import pygame def main(): pygame.init() screen = pygame.display.set_mode((640, 480)) clock = pygame.time.Clock() radius = 15 x = 0 y = 0 mode = 'blue' points = [] while True: pressed = pygame.key.get_pressed() alt_held = pressed[pygame.K_LALT] or pressed[pygame.K_RALT] ctrl_held = pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL] for event in pygame.event.get(): # determin if X was clicked, or Ctrl+W or Alt+F4 was used if event.type == pygame.QUIT: return if event.type == pygame.KEYDOWN: if event.key == pygame.K_w and ctrl_held: return if event.key == pygame.K_F4 and alt_held: return if event.key == pygame.K_ESCAPE: return # determine if a letter key was pressed if event.key == pygame.K_r: mode = 'red' elif event.key == pygame.K_g: mode = 'green' elif event.key == pygame.K_b: mode = 'blue' if event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: # left click grows radius radius = min(200, radius + 1) elif event.button == 3: # right click shrinks radius radius = max(1, radius - 1) if event.type == pygame.MOUSEMOTION: # if mouse moved, add point to list position = event.pos points = points + [position] points = points[-256:] screen.fill((0, 0, 0)) # draw all points i = 0 while i < len(points) - 1: drawLineBetween(screen, i, points[i], points[i + 1], radius, mode) i += 1 pygame.display.flip() clock.tick(60) def drawLineBetween(screen, index, start, end, width, color_mode): c1 = max(0, min(255, 2 * index - 256)) c2 = max(0, min(255, 2 * index)) if color_mode == 'blue': color = (c1, c1, c2) elif color_mode == 'red': color = (c2, c1, c1) elif color_mode == 'green': color = (c1, c2, c1) dx = start[0] - end[0] dy = start[1] - end[1] iterations = max(abs(dx), abs(dy)) for i in range(iterations): progress = 1.0 * i / iterations aprogress = 1 - progress x = int(aprogress * start[0] + progress * end[0]) y = int(aprogress * start[1] + progress * end[1]) pygame.draw.circle(screen, color, (x, y), width) main()
Заключение
Я надеюсь, что этот материал по PyGame поможет вам начать использовать эту библиотеку.
Разработайте несколько игр с использованием данного руководства и поделитесь результатами в комментариях!
Перевод статьи «PyGame Tutorial».
Python — самый популярный язык программирования нового поколения. Во всех новых областях компьютерных наук Python активно присутствует. Python имеет обширные библиотеки для различных областей, таких как машинное обучение (Numpy, Pandas, Matplotlib), искусственный интеллект (Pytorch, TensorFlow) и разработка игр (Pygame, Pyglet).
В этом уроке мы узнаем о разработке игр и рассмотрим возможности программирования с использованием Pygame (библиотека Python).
- Pygame в Python— это кроссплатформенный набор модулей Python, который используется для создания видеоигр.
- Он состоит из компьютерной графики и звуковых библиотек, предназначенных для использования с языком программирования Python.
- Pygame был официально написан Питом Шиннерсом для замены PySDL.
- Pygame подходит для создания клиентских приложений, которые потенциально могут быть упакованы в автономный исполняемый файл.
Прежде чем изучать pygame, нам нужно понять, какую игру мы хотим разработать. Чтобы изучить pygame, необходимо иметь базовые знания Python.
Установка pygame в Windows
Перед установкой Pygame в системе должен быть установлен Python, желательно версии 3.6.1 или выше, потому что она намного удобнее для новичков и, кроме того, работает быстрее. В основном есть два способа установить Pygame, которые приведены ниже:
1. Установка через pip. Хороший способ установить Pygame — использовать инструмент pip (применяется в Python для установки пакетов). Команда следующая:
py -m pip install -U pygame --user
2. Установка через IDE. Второй способ — установить его через IDE, и здесь мы используем Pycharm IDE. Установка pygame в pycharm проста. Мы можем установить его, выполнив указанную выше команду в терминале или выполнив следующие шаги:
- Откройте вкладку «Файл» и нажмите «Настройки».
- Выберите интерпретатор проекта и щелкните значок +.
- Он отобразит окно поиска. Найдите pygame и нажмите кнопку установки пакета.
Чтобы проверить, правильно ли установлен pygame, в интерпретаторе IDLE введите следующую команду и нажмите Enter:
import pygame
Если команда выполняется успешно без каких-либо ошибок, это означает, что мы успешно установили Pygame и нашли правильную версию IDLE для использования в программировании pygame.
Установка pygame на Mac
Чтобы установить pygame на Mac, выполните следующие действия.
- Посетите официальный сайт pygame pygame.org и в следующем окне загрузите pygame-1.9.1release-python.org-32bit-py2.7-macosx10.3.dmg:
Примечание. Если вы используете другую версию Python, загрузите последнюю ссылку.
- Дважды щелкните загруженный файл и разархивируйте его. Теперь дважды щелкните распакованный файл mpkg, чтобы запустить программу установки. Следуйте инструкциям по установке pygame.
- Запустите терминал (Cmd+Пробел, затем введите «терминал» в поле поиска). В терминале введите «python2.7» и нажмите Enter.
- Python должен запуститься, отобразить версию 2.7.2 (2.7.1 тоже подойдет) и выдать подсказку. В командной строке Python введите «import pygame». Если вы не получаете никаких ошибок, все работает нормально.
Простой пример pygame
Вот простая программа pygame, которая дает общее представление о синтаксисе.
import pygame pygame.init() screen = pygame.display.set_mode((400,500)) done = False while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True pygame.display.flip()
Выход:
После успешного выполнения он выдаст следующее окно в качестве вывода:
Давайте разберемся с основным синтаксисом приведенной выше программы построчно:
- import pygame — обеспечивает доступ к фреймворку pygame и импортирует все функции pygame.
- pygame.init() — используется для инициализации всех необходимых модулей pygame.
- pygame.display.set_mode((width, height)) — используется для отображения окна нужного размера. Возвращаемое значение — это объект Surface, который является объектом, в котором мы будем выполнять графические операции.
- pygame.event.get() — очищает очередиь событий. Если мы не назовем это, оконные сообщения начнут накапливаться, и игра перестанет отвечать на запросы операционной системы.
- pygame.QUIT — используется для завершения события, когда мы нажимаем кнопку закрытия в углу окна.
- pygame.display.flip() — Pygame использует двойную буферизацию, поэтому буферы смещаются. Очень важно вызвать эту функцию, чтобы сделать видимыми любые обновления, которые вы делаете на игровом экране.
Surface Pygame
Поверхность pygame используется для отображения любого изображения. Pygame имеет предустановленное разрешение и формат пикселей. Цвет поверхности по умолчанию черный. Его размер определяется передачей аргумента размера.
Поверхности могут иметь ряд дополнительных атрибутов, таких как альфа-плоскости, цветовые ключи, отсечение исходного прямоугольника и другие. Подпрограммы blit будут пытаться использовать аппаратное ускорение, когда это возможно; в противном случае они будут использовать усовершенствованные методы копирования программного обеспечения.
Pygame часы
Время представлено в миллисекундах (1/1000 секунды) в pygame. Часы Pygame используются для отслеживания времени. Время необходимо для создания движения, воспроизведения звука или реакции на любое событие. В общем, мы не считаем время в секундах. Считаем в миллисекундах. Часы также предоставляют различные функции, помогающие контролировать частоту кадров в игре. Имеют несколько функций:
- tick()
Эта функция используется для обновления часов. Синтаксис следующий:
tick(framerate=0)
Данный метод следует вызывать один раз за кадр. Он подсчитает, сколько миллисекунд прошло с момента предыдущего вызова. Аргумент частоты кадров является необязательным для передачи в функцию, и если он передается в качестве аргумента, функция будет делать задержку, чтобы игра работала медленнее, чем заданное количество тактов в секунду.
- tick_busy_loop()
Функция tick_busy_loop() аналогична функции tick(). При вызове Clock.tick_busy_loop(20) один раз за кадр программа никогда не будет работать со скоростью более 20 кадров в секунду. Синтаксис следующий:
tick_busy_loop()
- get_time()
get_time() используется для получения предыдущего тика. Число миллисекунд, которое isdra прошло между двумя последними вызовами в Clock.tick().
get_time()
Что такое Pygame Blit в Python?
Блиттинг pygame в Python — это процесс рендеринга игрового объекта на поверхность, и этот процесс называется блиттингом. Когда мы создаем игровой объект, нам нужно его визуализировать. Если мы не отрендерим игровые объекты и не запустим программу, то на выходе она выдаст черное окно.
Блиттинг — одна из самых медленных операций в любой игре, поэтому нам нужно быть осторожными, чтобы не сильно блитить на экране в каждом кадре. Основная функция, используемая в блиттинге, — это blit(), а именно:
blit(source,dest,area=None,special_flags=0)
Эта функция используется для вставки одного изображения в другое. Может быть размещена с аргументом dest. Аргумент dest может быть парой координат, представляющих левый верхний угол источника.
Добавление изображения
Чтобы добавить изображение в окно, сначала нам нужно создать пустую поверхность, вызвав конструктор Surface с кортежем ширины и высоты.
surface = pygame.Surface((100,100))
Приведенная выше строка создает пустое 24-битное изображение RGB размером 100*100 пикселей с черным цветом по умолчанию.
Для прозрачной инициализации Surface передайте аргумент SRCALPHA.
surface = pygame.Surface((100,100), pygame.SRCALPHA)
Рассмотрим следующий пример для отображения изображения на поверхности:
import pygame pygame.init() white =(255, 255, 255) # assigning values to height and width variable height = 400 width = 400 # creating the display surface object # of specific dimension..e(X, Y). display_surface = pygame.display.set_mode((height, width)) # set the pygame window name pygame.display.set_caption('Image') # creating a surface object, image is drawn on it. image = pygame.image.load(r'C:\Users\DEVANSH SHARMA\Desktop\download.png') # infinite loop while True: display_surface.fill(white) display_surface.blit(image,(0, 0)) for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() # quit the program. quit() # Draws the surface object to the screen. pygame.display.update()
Выход:
Прямоугольник в Pygame
Rect используется для рисования прямоугольника в Pygame. Pygame использует объекты Rect для хранения прямоугольных областей и управления ими. Rect может быть сформирован из комбинации значений left, top, width и height. Его также можно создать из объектов Python, которые уже являются Rect или имеют атрибут с именем «rect».
Функция rect() используется для изменения положения или размера прямоугольника. Он возвращает новую копию Rect с затронутыми изменениями. Никаких изменений в исходном прямоугольнике не происходит.
Объект Rect имеет различные виртуальные атрибуты, которые можно использовать для перемещения и выравнивания:
x,y top, left, right, bottom topleft, bottomleft, topright, bottomright midtop, midleft, midbottom, midright center, centerx, centery size, width, height w,h
Размер прямоугольника можно изменить, задав размер, ширину или высоту. Все остальные присваивания перемещают прямоугольник без изменения его размера.
Если ширина или высота являются ненулевым значением Rect, то для ненулевого теста будет возвращено значение True. Некоторые методы возвращают Rect с размерами 0 для представления недопустимого прямоугольника.
Давайте создадим Rectangle в окне pygame, используя Rect:
import pygame pygame.init() screen = pygame.display.set_mode((400, 300)) done = False while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True pygame.draw.rect(screen,(0, 125, 255), pygame.Rect(30, 30, 60, 60)) pygame.display.flip()
После выполнения приведенного выше кода он отобразит прямоугольник в окне pygame.
Клавиши Pygame
Pygame KEYDOWN и KEYUP обнаруживают событие, если клавиша физически нажата и отпущена. KEYDOWN определяет нажатие клавиши, KEYUP определяет ее отпускание. Оба события (нажатие клавиши и отпускание клавиши) имеют два следующих атрибута:
- key: ключ — это целочисленный идентификатор, который представляет каждый ключ в ключевом слове.
- mod: это битовая маска всех клавиш-модификаторов, которые были в нажатом состоянии, когда произошло событие.
Рассмотрим следующий пример нажатия и отпускания клавиши.
import pygame pygame.init() # sets the window title pygame.display.set_caption(u'Keyboard events') # sets the window size pygame.display.set_mode((400, 400)) while True: # gets a single event from the event queue event = pygame.event.wait() # if the 'close' button of the window is pressed if event.type == pygame.QUIT: # stops the application break # Detects the 'KEYDOWN' and 'KEYUP' events if event.type in(pygame.KEYDOWN, pygame.KEYUP): # gets the key name key_name = pygame.key.name(event.key) # converts to uppercase the key name key_name = key_name.upper() # if any key is pressed if event.type == pygame.KEYDOWN: # prints on the console the key pressed print(u'"{}" key pressed'.format(key_name)) # if any key is released elif event.type == pygame.KEYUP: # prints on the console the released key print(u'"{}" key released'.format(key_name))
Выход:
Давайте посмотрим на другой пример:
import pygame pygame.init() screen = pygame.display.set_mode((400, 300)) done = False is_blue = True x = 30 y = 30 while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: is_blue = not is_blue pressed = pygame.key.get_pressed() if pressed[pygame.K_UP]: y -= 3 if pressed[pygame.K_DOWN]: y += 3 if pressed[pygame.K_LEFT]: x -= 3 if pressed[pygame.K_RIGHT]: x += 3 if is_blue: color =(0, 128, 255) else: color =(255, 100, 0) pygame.draw.rect(screen, color, pygame.Rect(x, y, 60, 60)) pygame.display.flip()
В приведенном выше коде прямоугольник будет отображаться в окне pygame.
Когда мы нажимаем клавишу «Вниз», прямоугольник меняет форму вниз. Вывод следующий:
Pygame Draw
Pygame предоставляет функции геометрии для рисования простых фигур на поверхности. Эти функции будут работать для рендеринга в любой формат на поверхности. Большинство функций принимают аргумент ширины, чтобы обозначить размер толщины вокруг края фигуры. Если ширина передана 0, то форма будет сплошной (заполненной).
Все функции рисования принимают аргумент цвета, который может быть в одном из следующих форматов:
- Объекты pygame.Color
- Триплет(RGB)(кортеж/список)
- Четверка(RGBA)(кортеж/список)
- Целочисленное значение, сопоставленное с пиксельным форматом поверхности.
Рисование прямоугольника
Следующие функции используются для рисования прямоугольника на заданной поверхности.
pygame.draw.rect(surface, color, rect) pygame.draw.rect(surface, color, rect, width=0)
Параметры:
- Surface — экран для рисования.
- color — этот аргумент используется для окрашивания заданной формы. Альфа-значение является необязательным, если мы используем кортеж.
- rect(Rect) – рисование прямоугольника, положение и размеры.
- width(int) — чтобы использовать толщину линии или указать, что прямоугольник заполнен, необязателен.
if width == 0,(default) fill the rectangle if width > 0, used for line thickness if width < 0, nothing will be drawn
Рисование многоугольника
Следующие функции используются для рисования многоугольника на заданной поверхности.
- pygame.draw.polygon(surface,color,points)
- pygame.draw.polygon(surface, color, points, width=0)
Параметры:
- Surface — экран для рисования.
- color — этот аргумент используется для окрашивания заданной формы. Альфа-значение является необязательным, если мы используем кортеж.
- points(tuple(coordinate) или list(coordinate)): последовательность из 3 или более(x, y) координат, составляющих вершины многоугольника. Каждая координата в последовательности должна быть кортежем/списком.
Примечание. – Если len(points) < 3 или точки не являются последовательностью или точки не содержат числовой пары, это вызовет ошибку значения.
Эллипс
Следующие функции используются для рисования эллипса на заданной поверхности.
pygame.draw.ellipse(surface, color, rect) pygame.draw.ellipse(surface, color, rect, width=0)
Параметры:
- Surface — экран для рисования.
- color — этот аргумент используется для окрашивания заданной формы. Альфа-значение является необязательным, если мы используем кортеж.
- rect(Rect) – рисование прямоугольника, положение и размеры.
Прямая линия
Этот метод используется для рисования прямой линии на заданной поверхности. Концовок нет.
pygame.draw.line(surface,color,start_pos,end_pos,width) pygame.draw.line(surface,color,start_pos,end_pos,width=1)
Параметры:
- Surface — экран для рисования.
- color — этот аргумент используется для окрашивания заданной формы. Альфа-значение является необязательным, если мы используем кортеж.
- start_pos — начальная позиция линии(x,y)
- end_pos — конечная позиция строки.
Круг
Ниже приведены функции, которые используются для рисования окружности на заданной поверхности.
- circle(surface, color, center, radius)
- circle(surface, color, center, radius, width=0)
Параметры:
- Surface — экран для рисования.
- color — этот аргумент используется для окрашивания заданной формы. Альфа-значение является необязательным, если мы используем кортеж.
- center – центральная точка круга как последовательность двух int/float, например(x,y)
- radius(int или float) – радиус окружности, отсчитываемый от параметра center, если радиус равен нулю, то будет отрисовываться только центральный пиксель.
Эллиптическая дуга
Следующие функции используются для рисования эллиптической дуги на заданной поверхности.
? arc(surface, color, rect, start_angle, stop_angle) ? arc(surface, color, rect, start_angle, stop_angle, width=1)
Параметры:
- Surface — экран для рисования.
- color — этот аргумент используется для окрашивания заданной формы. Альфа-значение является необязательным, если мы используем кортеж.
- rect(Rect) – рисование прямоугольника, его положение и размеры.
- start_angle — начальный угол дуги в радианах.
- stop_angle — конечный угол дуги в радианах.
Есть три условия для параметров start_angle и stop_angle:
- Если start_angle < stop_angle, то дуга будет нарисована против часовой стрелки от start_angle до end_angle.
- Если start_angle>stop_angle, то tau(tau=2*pi) будет добавлено к углу остановки.
- Если start_angle==stop_angle, ничего не будет нарисовано.
Рассмотрим пример:
import pygame from math import pi pygame.init() # size variable is using for set screen size size = [400, 300] screen = pygame.display.set_mode(size) pygame.display.set_caption("Example program to draw geometry") # done variable is using as flag done = False clock = pygame.time.Clock() while not done: # clock.tick() limits the while loop to a max of 10 times per second. clock.tick(10) for event in pygame.event.get(): # User did something if event.type == pygame.QUIT: # If user clicked on close symbol done = True # done variable that we are complete, so we exit this loop # All drawing code occurs after the for loop and but # inside the main while done==False loop. # Clear the default screen background and set the white screen background screen.fill((0, 0, 0)) # Draw on the screen a green line which is 5 pixels wide. pygame.draw.line(screen,(0, 255, 0), [0, 0], [50, 30], 5) # Draw on the screen a green line which is 5 pixels wide. pygame.draw.lines(screen,(0, 0, 0), False, [[0, 80], [50, 90], [200, 80], [220, 30]], 5) # Draw a rectangle outline pygame.draw.rect(screen,(0, 0, 0), [75, 10, 50, 20], 2) # Draw a solid rectangle pygame.draw.rect(screen,(0, 0, 0), [150, 10, 50, 20]) # This draw an ellipse outline, using a rectangle as the outside boundaries pygame.draw.ellipse(screen,(255, 0, 0), [225, 10, 50, 20], 2) # This draw a solid ellipse, using a rectangle as the outside boundaries pygame.draw.ellipse(screen,(255, 0, 0), [300, 10, 50, 20]) # Draw a triangle using the polygon function pygame.draw.polygon(screen,(0, 0, 0), [[100, 100], [0, 200], [200, 200]], 5) # This draw a circle pygame.draw.circle(screen,(0, 0, 255), [60, 250], 40) # This draw an arc pygame.draw.arc(screen,(0, 0, 0), [210, 75, 150, 125], 0, pi / 2, 2) # This function must write after all the other drawing commands. pygame.display.flip() # Quite the execution when clicking on close pygame.quit()
Выход:
Текст и шрифт Pygame
Pygame также предоставляет средства для рендеринга шрифта и текста. Мы можем загружать шрифты из системы с помощью функции pygame.font.SysFont(). Pygame поставляется со встроенным шрифтом по умолчанию, доступ к которому можно получить, передав имя шрифта или None. Есть множество функций, помогающих работать со шрифтом.
Объекты шрифта создаются с помощью pygame.font.Font(). Фактические объекты шрифта выполняют большую часть работы, выполняемой со шрифтами. Объекты шрифта обычно используются для рендеринга текста в новые объекты Surface. Вот несколько важных функций шрифта:
- render()
Эта функция используется для рисования текста на новой поверхности. Pygame не имеет возможности рисовать текст на существующей поверхности. Это создает новую поверхность с указанным текстовым рендерингом на ней. Синтаксис следующий:
render(text, antialias, color, background=None)
- size()
Эта функция используется для определения количества пробелов или позиций, необходимых для отображения текста. Его также можно использовать для переноса слов и других эффектов макета. Синтаксис следующий:
size(bool)
- set_bold()
Эта функция используется для выделения текста жирным шрифтом. Синтаксис следующий:
set_bold(bool)
Рассмотрим следующий пример:
import pygame pygame.init() screen = pygame.display.set_mode((640, 480)) done = False #load the fonts font = pygame.font.SysFont("Times new Roman", 72) # Render the text in new surface text = font.render("Hello, Pygame", True,(158, 16, 16)) while not done: for event in pygame.event.get(): if event.type == pygame.QUIT: done = True if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: done = True screen.fill((255, 255, 255)) #We will discuss blit() in the next topic screen.blit(text,(320 - text.get_width() // 2, 240 - text.get_height() // 2)) pygame.display.flip()
Выход:
Примечание . Необходимо помнить, что определенный шрифт должен быть установлен на компьютере пользователя. Если вы не знаете, установлены шрифты или нет, у pygame есть следующая функция для перечисления всех доступных для пользователя шрифтов:
all_font = pygame.font.get_fonts()
Существует еще одна функция для создания экземпляра системного шрифта по умолчанию:
font = pygame.font.Font(None,size)
Используя любые вышеперечисленные функции, мы можем работать с привлекательным шрифтом в игре.
Pygame Sprite и обнаружение столкновений
Sprite pygame — это двумерное изображение, являющееся частью большой графической сцены. Обычно Sprite представляет собой какой-то объект на сцене.
Одним из самых важных преимуществ работы со Sprite является возможность работать с ними в группах. Мы можем легко перемещать и рисовать их одной командой, если они находятся в группе.
Модуль Sprite содержит различные простые классы для использования в играх. Необязательно использовать классы Sprite и различные групповые классы при использовании pygame.
Pygame предоставляет спрайты и группы, которые помогают обнаруживать столкновения. Обнаружение столкновений — это процесс, когда два объекта на экране сталкиваются друг с другом. Например, если в игрока попала вражеская пуля, то он может потерять жизнь или программе нужно знать, когда игрок касается монеты, чтобы она автоматически подбиралась.
Рассмотрим следующий пример:
import pygame import sys #Sprite class class Sprite(pygame.sprite.Sprite): def __init__(self, pos): pygame.sprite.Sprite.__init__(self) self.image = pygame.Surface([20, 20]) self.image.fill((255, 0, 255)) self.rect = self.image.get_rect() self.rect.center = pos def main(): pygame.init() clock = pygame.time.Clock() fps = 50 bg = [0, 0, 0] size =[300, 300] screen = pygame.display.set_mode(size) player = Sprite([40, 50]) # Define keys for player movement player.move = [pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN] player.vx = 5 player.vy = 5 wall = Sprite([100, 60]) wall_group = pygame.sprite.Group() wall_group.add(wall) player_group = pygame.sprite.Group() player_group.add(player) while True: for event in pygame.event.get(): if event.type == pygame.QUIT: return False key = pygame.key.get_pressed() for i in range(2): if key[player.move[i]]: player.rect.x += player.vx * [-1, 1][i] for i in range(2): if key[player.move[2:4][i]]: player.rect.y += player.vy * [-1, 1][i] screen.fill(bg) # first parameter takes a single sprite # second parameter takes sprite groups # third parameter is a kill command if true hit = pygame.sprite.spritecollide(player, wall_group, True) if hit: # if collision is detected call a function to destroy # rect player.image.fill((255, 255, 255)) player_group.draw(screen) wall_group.draw(screen) pygame.display.update() clock.tick(fps) pygame.quit() sys.exit if __name__ == '__main__':
Выход:
После нажатия клавиш со стрелками один прямоугольник столкнется с другим прямоугольником, после чего выводится:
Pyglet
Python предоставляет еще одну игровую библиотеку под названием pyglet, которая представляет собой кроссплатформенную библиотеку окон и мультимедиа для Python. Она используется для разработки игр и других визуально насыщенных приложений.
Pyglet поддерживает обработку событий пользовательского интерфейса, работу с окнами, графику OpenGL, загрузку изображений и видео, а также воспроизведение звуков и музыки.
Вот несколько особенностей pyglet:
- Никаких внешних требований к установке или зависимостей.
- Воспользуйтесь преимуществами нескольких окон и нескольких мониторов.
- Он может загружать изображения, звук, музыку и видео в любом формате.
- Pyglet предоставляется под лицензией BSD с открытым исходным кодом.
- Он поддерживает как Python 2, так и 3.
Установка pyglet проста; его можно установить, введя следующую команду.
pip install pyglet
Рассмотрим следующий пример.
import pyglet window = pyglet.window.Window() lable = pyglet.text.Label('Hello world', font_name='Times New Roman', font_size=36, x= window.width//2,y=window.height//2,anchor_x='center', anchor_y='center') @window.event def on_draw(): window.clear() lable.draw() pyglet.app.run()
Выход:
Сравнение между Pygame и Pyglet
Pyglet | Pygame |
---|---|
3D поддержка
Поскольку pyglet так прочно слился с OpenGL, это позволяет поддерживать рисунок в 3D. |
Простой синтаксис Python
Pygame использует Python в качестве языка сценариев. Python считается одним из самых естественных языков для понимания даже новичками. |
Кроссплатформенность
Он может работать с Windows, Linux и OS X. |
Использование API
API очень простой. |
Написан на чистом Python
Его можно скомпилировать с помощью других интерпретаторов Python. |
Лучшая система рисования
Pygame предоставляет систему рисования, которая позволяет пользователю создавать и рисовать неограниченное количество холстов. |
Меньше популярности
Pyglet менее популярен, потому что у него небольшая поддержка сообщества. |
Более популярный
Pygame более популярен, чем pyglet. |
В этом руководстве мы обсудили простой подход к программированию разработки игр, установив модуль pygame с открытым исходным кодом в среду программирования Python 3.
Изучаю Python вместе с вами, читаю, собираю и записываю информацию опытных программистов.
Когда я начал изучать компьютерное программирование в конце прошлого тысячелетия, это было вызвано моим желанием писать компьютерные игры. Я пытался понять, как писать игры на каждом языке и на каждой платформе, которую я изучил, включая Python. Так я обнаружил pygame и научился использовать его для написания игр и других графических программ.
К концу этой статьи вы сможете:
- Рисовать предметы на экране
- Воспроизводить звуковые эффекты и музыку
- Обрабатывать пользовательский ввод
- Реализовать циклы событий
- Описать, чем программирование игр отличается от стандартного процедурного программирования на Python.
Предыстория и установка
pygame представляет собой оболочку Python для библиотеки SDL, что означает Simple DirectMedia Layer. SDL обеспечивает межплатформенный доступ к базовым мультимедийным аппаратным компонентам вашей системы, таким как звук, видео, мышь, клавиатура и джойстик. pygame начал жизнь как замена застопорившемуся проекту PySDL . Кроссплатформенный характер SDL pygame означает, что вы можете писать игры и многофункциональные мультимедийные программы Python для любой платформы, которая их поддерживает!
Для установки pygame на вашей платформе используйте соответствующую команду pip:
$ pip install pygame
Вы можете проверить установку, загрузив один из примеров, поставляемых с библиотекой:
$ python3 -m pygame.examples.aliens
Если появится окно с игрой, значит pygame установлен правильно! Если у вас возникнут проблемы, в руководстве по началу работы описаны некоторые известные проблемы и предостережения для всех платформ.
Базовая программа PyGame
Прежде чем перейти к специфике, давайте взглянем на основную программу pygame. Эта программа создает окно, заливает фон белым цветом и рисует синий круг посередине:
# Simple pygame program
# Import and initialize the pygame library
import pygame
pygame.init()
# Set up the drawing window
screen = pygame.display.set_mode([500, 500])
# Run until the user asks to quit
running = True
while running:
# Did the user click the window close button?
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Fill the background with white
screen.fill((255, 255, 255))
# Draw a solid blue circle in the center
pygame.draw.circle(screen, (0, 0, 255), (250, 250), 75)
# Flip the display
pygame.display.flip()
# Done! Time to quit.
pygame.quit()
Когда вы запустите эту программу, вы увидите окно, похожее на это:
Разберем этот код по частям:
- Строки 4 и 5 импортируют и инициализируют библиотеку pygame. Без этих строк не будет pygame.
- Строка 8 настраивает окно отображения вашей программы. Вы предоставляете либо список, либо кортеж, определяющий ширину и высоту создаваемого окна. Эта программа использует список для создания квадратного окна с 500 пикселями с каждой стороны.
- Строки 11 и 12 настраивают игровой цикл для управления завершением программы. Позже в этом руководстве вы познакомитесь с игровыми циклами.
- Строки с 15 по 17 сканируют и обрабатывают события внутри игрового цикла. Вы также доберетесь до событий немного позже. В этом случае обрабатывается только одно событие pygame.QUIT, которое происходит, когда пользователь нажимает кнопку закрытия окна.
- Строка 20 заполняет окно сплошным цветом. screen.fill() принимает либо список, либо кортеж, определяющий значения RGB для цвета.
- Строка 23 рисует круг в окне, используя следующие параметры: screen: окно, в котором будет происходить процесс(0, 0, 255): кортеж, содержащий значения цвета RGB.(250, 250): кортеж, определяющий координаты центра круга75: радиус круга для рисования в пикселях
- Строка 26 обновляет содержимое дисплея на экране. Без этого вызова в окне ничего не появляется!
- Строка 29 выходит из pygame. Это происходит только после завершения цикла.
Это была база pygame. Теперь давайте углубимся в концепции, лежащие в основе этого кода.
Концепции PyGame
Поскольку библиотеки pygame и SDL переносимы на разные платформы и устройства, обе они должны определять абстракции и работать с ними для различных реалий аппаратного обеспечения. Понимание этих концепций и абстракций поможет вам проектировать и разрабатывать собственные игры.
Инициализация и модули
Библиотека pygame состоит из ряда конструкций Python, которые включают в себя несколько различных модулей . Эти модули обеспечивают абстрактный доступ к определенному оборудованию в вашей системе, а также унифицированные методы работы с этим оборудованием. Например, display обеспечивает единый доступ к вашему видеодисплею, а joystick позволяет абстрактно управлять вашим джойстиком.
После импорта библиотеки pygame из приведенного выше примера первое, что вы сделали, — это инициализировали PyGame с помощью pygame.init(). Эта функция вызывает отдельные init() функции всех включенных модулей pygame. Поскольку эти модули являются абстракциями для конкретного оборудования, этот шаг инициализации необходим, чтобы вы могли работать с одним и тем же кодом в Linux, Windows и Mac.
Дисплеи и поверхности
В дополнение к модулям pygame также включает несколько классов Python , которые инкапсулируют концепции, не зависящие от оборудования. Одним из них является Surface, который в своей основе определяет прямоугольную область, на которой вы можете рисовать. Surface объекты используются во многих контекстах в pygame. Позже вы увидите, как загрузить изображение в файл Surface и отобразить его на экране.
В pygame всё просматривается в одном созданном пользователем display, который может быть окном или полным экраном. Дисплей создается с помощью .set_mode(), который возвращает представление Surface видимой части окна. Surface — это то, что вы передаете в функции рисования, такие как pygame.draw.circle(), и содержимое этого Surface выталкивается на дисплей, когда вы вызываете pygame.display.flip().
Изображения и прямоугольники
Ваша базовая программа pygame рисовала фигуру прямо на экране Surface, но вы также можете работать с изображениями на диске. Модуль позволяет загружать и сохранять изображения image в различных популярных форматах. Изображения загружаются в объекты, которыми затем можно манипулировать и отображать различными способами.
Как упоминалось выше, объекты Surface представлены прямоугольниками, как и многие другие объекты в pygame. Прямоугольники используются настолько широко, что существует специальный класс Rect только для их обработки. Вы будете использовать прямоугольные объекты и изображения в своей игре для рисования игроков и врагов, а также для управления столкновениями между ними.
Ладно, хватит теории. Давайте придумаем и напишем игру!
Базовый дизайн игры
Прежде чем вы начнете писать какой-либо код, всегда полезно иметь некоторый дизайн. Поскольку это обучающая игра, давайте также разработаем для нее базовый игровой процесс:
- Цель игры состоит в том, чтобы избегать приближающихся препятствий: Игрок начинает с левой стороны экрана.Препятствия входят случайным образом справа и движутся влево по прямой линии.
- Игрок может двигаться влево, вправо, вверх или вниз, чтобы избежать препятствий.
- Игрок не может уйти с экрана.
- Игра заканчивается либо когда игрок сталкивается с препятствием, либо когда пользователь закрывает окно.
Один мой бывший коллега , описывая программные проекты, говорил: «Вы не знаете, что делаете, пока не узнаете, чего не делаете». Имея это в виду, вот некоторые вещи, которые не будут рассмотрены в этом руководстве:
- Нет нескольких жизней
- Нет ведения счета
- Нет возможности атаки игрока
- Нет продвинутых уровней
- Нет боссов
Вы можете попробовать свои силы в добавлении этих и других функций в свою программу.
Давайте начнем!
Импорт и инициализация PyGame
После импорта pygame вам также потребуется инициализировать его. Это позволяет подключить абстракции pygame к вашему конкретному оборудованию:
# Import the pygame module
import pygame
# Import pygame.locals for easier access to key coordinates
# Updated to conform to flake8 and black standards
from pygame.locals import (
K_UP,
K_DOWN,
K_LEFT,
K_RIGHT,
K_ESCAPE,
KEYDOWN,
QUIT,
)
# Initialize pygame
pygame.init()
Библиотека pygame определяет множество вещей помимо модулей и классов. Она также определяет некоторые локальные константы для таких вещей, как нажатия клавиш, движения мыши и атрибуты отображения. Вы ссылаетесь на эти константы, используя синтаксис pygame.<CONSTANT>. Импортируя определенные константы из pygame.locals, вы можете вместо этого использовать синтаксис <CONSTANT>. Это сэкономит вам несколько нажатий клавиш и улучшит общую читаемость.
Настройка дисплея
Теперь вам нужно то, на чём вы будете рисовать! Создайте экран , который будет общим холстом:
# Import the pygame module
import pygame
# Import pygame.locals for easier access to key coordinates
# Updated to conform to flake8 and black standards
from pygame.locals import (
K_UP,
K_DOWN,
K_LEFT,
K_RIGHT,
K_ESCAPE,
KEYDOWN,
QUIT,
)
# Initialize pygame
pygame.init()
# Define constants for the screen width and height
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
# Create the screen object
# The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
Вы создаете экран для использования, вызывая pygame.display.set_mode() и передавая кортеж или список с желаемой шириной и высотой. В данном случае окно имеет размер 800×600, как определено константами SCREEN_WIDTH и SCREEN_HEIGHT в строках 20 и 21. Это часть окна, которой вы можете управлять, в то время как ОС управляет границами окна и строкой заголовка.
Если вы запустите эту программу сейчас, вы увидите всплывающее окно на короткое время, которое сразу же исчезнет при выходе из программы. Не моргайте, иначе вы можете пропустить это! В следующем разделе вы сосредоточитесь на основном игровом цикле, чтобы убедиться, что ваша программа завершает работу только при правильном вводе.
Настройка игрового цикла
Каждая игра от Pong до Fortnite использует игровой цикл для управления игровым процессом. Игровой цикл делает четыре очень важные вещи:
- Обрабатывает пользовательский ввод
- Обновляет состояние всех игровых объектов
- Обновляет дисплей и аудиовыход
- Поддерживает скорость игры
Каждый цикл игрового цикла называется фреймом, и чем быстрее вы будете выполнять действия в каждом цикле, тем быстрее будет работать ваша игра. Кадры продолжают появляться до тех пор, пока не будет выполнено какое-либо условие для выхода из игры. В вашем дизайне есть два условия, которые могут завершить игровой цикл:
- Игрок сталкивается с препятствием.
- Игрок закрывает окно.
Первое, что делает игровой цикл, — обрабатывает пользовательский ввод, чтобы позволить игроку перемещаться по экрану. Следовательно, вам нужен какой-то способ захвата и обработки различных входных данных. Вы делаете это, используя систему событий pygame.
Обработка событий
Нажатия клавиш, движения мыши и даже движения джойстика — вот некоторые из способов, которыми пользователь может вводить данные. Все действия пользователя приводят к генерации события . События могут произойти в любое время и часто (но не всегда) возникают вне программы. Все события pygame помещаются в очередь событий, к которой затем можно получить доступ и которой можно манипулировать. Работа с событиями называется их обработкой, а код для этого называется обработчиком событий .
Каждое событие в pygame имеет связанный с ним тип события. В вашей игре типы событий, на которых вы сосредоточитесь, — это нажатия клавиш и закрытие окна. События нажатия клавиш имеют тип события KEYDOWN, а событие закрытия окна имеет тип QUIT. Различные типы событий также могут иметь другие связанные с ними данные. Например, у типа события KEYDOWN также есть переменная , вызываемая key для указания того, какая клавиша была нажата.
Вы получаете доступ к списку всех активных событий в очереди, вызывая pygame.event.get(). Затем вы просматриваете этот список, проверяете каждый тип события и отвечаете соответствующим образом:
# Variable to keep the main loop running
running = True
# Main loop
while running:
# Look at every event in the queue
for event in pygame.event.get():
# Did the user hit a key?
if event.type == KEYDOWN:
# Was it the Escape key? If so, stop the loop.
if event.key == K_ESCAPE:
running = False
# Did the user click the window close button? If so, stop the loop.
elif event.type == QUIT:
running = False
Давайте подробнее рассмотрим этот игровой цикл:
- В строке 28 задается управляющая переменная для игрового цикла. Для выхода из цикла и игры вы устанавливаете running = False. Игровой цикл начинается в строке 29.
- Строка 31 запускает обработчик событий, просматривая каждое событие, находящееся в данный момент в очереди. Если событий нет, то список пуст, и обработчик ничего не делает.
- Строки с 35 по 38 проверяют, является ли текущее KEYDOWN событием. Если да, то программа проверяет, какая клавиша была нажата, по атрибуту event.key. Если ключом является Esc, обозначенный K_ESCAPE, то он выходит из игрового цикла, устанавливая running = False.
- Строки 41 и 42 выполняют аналогичную проверку для типа события с именем QUIT. Это событие происходит только тогда, когда пользователь нажимает кнопку закрытия окна. Пользователь также может использовать любое другое действие операционной системы, чтобы закрыть окно.
Когда вы добавите эти строки к предыдущему коду и запустите его, вы увидите окно с пустым или черным экраном:
Окно не исчезнет, пока вы не нажмете клавишу Esc или иным образом не вызовете событие QUIT, закрыв окно.
Рисование на экране
В примере программы вы рисовали на экране с помощью двух команд:
- screen.fill() — заполнить фон
- pygame.draw.circle() — нарисовать круг
Теперь вы узнаете о третьем способе рисования на экране: использовании файла Surface.
Напомним, что Surface— это прямоугольный объект, на котором можно рисовать как, например, на чистом листе бумаги. Объект screen представляет собой Surface, и вы можете создавать свои собственные Surface — объекты отдельно от экрана дисплея. Давайте посмотрим, как это работает:
# Fill the screen with white
screen.fill((255, 255, 255))
# Create a surface and pass in a tuple containing its length and width
surf = pygame.Surface((50, 50))
# Give the surface a color to separate it from the background
surf.fill((0, 0, 0))
rect = surf.get_rect()
После того, как экран заполнен белым цветом в строке 45, в строке 48 создается новый Surface. Он имеет ширину 50 пикселей, высоту 50 пикселей и назначается surf. На этом этапе вы относитесь к нему так же, как к файлу screen. Итак, в строке 51 вы заполняете его черным цветом. Вы также можете получить доступ к его основе, используя .get_rect(). Это сохраняет rect для последующего использования.
Использование .blit() и .flip()
Просто создать новое Surface недостаточно, чтобы увидеть его на экране. Для этого вам нужно скопировать на Surface другой Surface. Термин blit означает Block Transfer, а .blit() означает, как вы копируете содержимое одного Surface в другое. Вот как происходит процесс рисования surf на экране:
# This line says «Draw surf onto the screen at the center»
screen.blit(surf, (SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
pygame.display.flip()
Вызов .blit() в строке 55 принимает два аргумента:
- Рисование Surface_
- Место, в котором нужно нарисовать его
Координаты (SCREEN_WIDTH/2, SCREEN_HEIGHT/2) говорят вашей программе разместить surf точно в центре экрана, но это не совсем так:
Причина, по которой изображение выглядит не по центру, заключается в том, что верхний левый угол не помещает .blit() в указанное место. Если вы хотите быть в центре, то вам придется сделать некоторые математические расчеты, чтобы сместить его вверх и влево. Вы можете сделать это, вычитая ширину и высоту из ширины и высоты экрана, разделив каждую на 2, чтобы найти центр, а затем передав эти числа в качестве аргументов :surfsurfsurfscreen.blit()
# Put the center of surf at the center of the display
surf_center = (
(SCREEN_WIDTH-surf.get_width())/2,
(SCREEN_HEIGHT-surf.get_height())/2
)
# Draw surf at the new coordinates
screen.blit(surf, surf_center)
pygame.display.flip()
Обратите внимание на вызов pygame.display.flip() после вызова blit(). Это обновляет весь экран с момента последнего перелистывания. Без вызова .flip() ничего не будет отображаться.
Спрайты
В вашем игровом дизайне игрок начинает слева, а препятствия появляются справа. Вы можете изобразить все препятствия Surface объектами, чтобы упростить рисование, но как узнать, где их рисовать? Как узнать, столкнулось ли препятствие с игроком? Что происходит, когда препятствие улетает за пределы экрана? Что делать, если вы хотите рисовать фоновые изображения, которые также движутся? Что делать, если вы хотите, чтобы ваши изображения были анимированными? Вы можете справиться со всеми этими и другими ситуациями с помощью спрайтов .
В терминах программирования спрайт — это двухмерное представление чего-либо на экране. По сути, это картинка. pygame предоставляет класс Sprite , предназначенный для хранения одного или нескольких графических представлений любого игрового объекта, который вы хотите отобразить на экране. Чтобы использовать его, вы создаёте новый класс, который расширяет Sprite. Это позволяет использовать его встроенные методы.
Игроки
Вот как вы используете объекты Sprite в текущей игре для определения игрока. Вставьте этот код после строки 18:
# Define a Player object by extending pygame.sprite.Sprite
# The surface drawn on the screen is now an attribute of ‘player’
class Player(pygame.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.surf = pygame.Surface((75, 25))
self.surf.fill((255, 255, 255))
self.rect = self.surf.get_rect()
Сначала вы определяете Player, расширяя строку 22 pygame.sprite.Sprite. Затем используете .__init__() .super()для вызова метода.
Далее вы определяете и инициализируете сохранение изображения .surf для отображения, которое в настоящее время является белым прямоугольником. Вы также определяете и инициализируете .rect, которые позже будете использовать для рисования игрока. Чтобы использовать этот новый класс, вам нужно создать новый объект и изменить код рисования. В блоке ниже представлено всё, что у нас есть на данном этапе:
# Import the pygame module
import pygame
# Import pygame.locals for easier access to key coordinates
# Updated to conform to flake8 and black standards
from pygame.locals import (
K_UP,
K_DOWN,
K_LEFT,
K_RIGHT,
K_ESCAPE,
KEYDOWN,
QUIT,
)
# Define constants for the screen width and height
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
# Define a player object by extending pygame.sprite.Sprite
# The surface drawn on the screen is now an attribute of ‘player’
class Player(pygame.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.surf = pygame.Surface((75, 25))
self.surf.fill((255, 255, 255))
self.rect = self.surf.get_rect()
# Initialize pygame
pygame.init()
# Create the screen object
# The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
# Instantiate player. Right now, this is just a rectangle.
player = Player()
# Variable to keep the main loop running
running = True
# Main loop
while running:
# for loop through the event queue
for event in pygame.event.get():
# Check for KEYDOWN event
if event.type == KEYDOWN:
# If the Esc key is pressed, then exit the main loop
if event.key == K_ESCAPE:
running = False
# Check for QUIT event. If QUIT, then set running to false.
elif event.type == QUIT:
running = False
# Fill the screen with black
screen.fill((0, 0, 0))
# Draw the player on the screen
screen.blit(player.surf, (SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
# Update the display
pygame.display.flip()
Запустите этот код. Вы увидите белый прямоугольник примерно посередине экрана:
Как вы думаете, что произойдет, если вы замените строку 59 на screen.blit(player.surf, player.rect)? Попробуйте и посмотрите:
# Fill the screen with black
screen.fill((0, 0, 0))
# Draw the player on the screen
screen.blit(player.surf, player.rect)
# Update the display
pygame.display.flip()
Когда вы передаете Rectto .blit(), он использует координаты верхнего левого угла для рисования поверхности. Вы будете использовать это позже, чтобы заставить вашего игрока двигаться!
Пользовательский ввод
Итак, вы узнали, как создавать pygame и рисовать объекты на экране. Теперь начинается настоящее веселье! Вы сделаете плеер управляемым с помощью клавиатуры.
Ранее вы видели, что pygame.event.get() возвращает список событий в очереди, которую вы сканируете на наличие типов событий KEYDOWN. Ну, это не единственный способ читать нажатия клавиш. pygame также предоставляет pygame.event.get_pressed(), который возвращает словарь , содержащий все текущие события в очереди KEYDOWN.
Поместите это в свой игровой цикл сразу после цикла обработки событий. Это возвращает словарь, содержащий клавиши, нажатые в начале каждого кадра:
# Get the set of keys pressed and check for user input
pressed_keys = pygame.key.get_pressed()
Затем вы пишете метод Player, чтобы принять этот словарь. Он будет определять поведение спрайта в зависимости от нажатых клавиш. Вот как это может выглядеть:
# Move the sprite based on user keypresses
def update(self, pressed_keys):
if pressed_keys[K_UP]:
self.rect.move_ip(0, -5)
if pressed_keys[K_DOWN]:
self.rect.move_ip(0, 5)
if pressed_keys[K_LEFT]:
self.rect.move_ip(-5, 0)
if pressed_keys[K_RIGHT]:
self.rect.move_ip(5, 0)
K_UP, K_DOWN, K_LEFT и K_RIGHT соответствуют клавишам со стрелками на клавиатуре. Если словарная статья для этой клавиши — True, то эта клавиша нажата, и вы перемещаете игрока в правильном направлении. Здесь вы используете .move_ip(), что означает перемещение на месте , чтобы переместить текущий Rect.
Затем вы можете вызвать каждый кадр .update() для перемещения спрайта игрока в ответ на нажатия клавиш. Добавьте этот вызов сразу после вызова .get_pressed():
# Main loop
while running:
# for loop through the event queue
for event in pygame.event.get():
# Check for KEYDOWN event
if event.type == KEYDOWN:
# If the Esc key is pressed, then exit the main loop
if event.key == K_ESCAPE:
running = False
# Check for QUIT event. If QUIT, then set running to false.
elif event.type == QUIT:
running = False
# Get all the keys currently pressed
pressed_keys = pygame.key.get_pressed()
# Update the player sprite based on user keypresses
player.update(pressed_keys)
# Fill the screen with black
screen.fill((0, 0, 0))
Теперь вы можете перемещать прямоугольник вашего плеера по экрану с помощью клавиш со стрелками:
Далее необходимо решить 2 проблемы:
- Прямоугольник игрока может двигаться очень быстро, если клавиша удерживается нажатой. Вы поработаете над этим позже.
- Прямоугольник игрока может перемещаться за пределы экрана. Давайте решим это сейчас.
Чтобы игрок оставался на экране, вам нужно добавить некоторую логику, чтобы определить, собирается ли rect уйти за пределы экрана. Для этого вы проверяете, не вышли ли координаты за пределы экрана. Если это так, то вы указываете программе переместить его обратно к краю:
# Move the sprite based on user keypresses
def update(self, pressed_keys):
if pressed_keys[K_UP]:
self.rect.move_ip(0, -5)
if pressed_keys[K_DOWN]:
self.rect.move_ip(0, 5)
if pressed_keys[K_LEFT]:
self.rect.move_ip(-5, 0)
if pressed_keys[K_RIGHT]:
self.rect.move_ip(5, 0)
# Keep player on the screen
if self.rect.left < 0:
self.rect.left = 0
if self.rect.right > SCREEN_WIDTH:
self.rect.right = SCREEN_WIDTH
if self.rect.top <= 0:
self.rect.top = 0
if self.rect.bottom >= SCREEN_HEIGHT:
self.rect.bottom = SCREEN_HEIGHT
Здесь вместо использования .move() вы просто меняете соответствующие координаты .top, .bottom, .leftили .right напрямую. Проверьте это, и вы обнаружите, что прямоугольник игрока больше не может перемещаться за пределы экрана.
Теперь давайте добавим врагов!
Враги
Какая игра без врагов? Вы будете использовать методы, которые вы уже изучили, чтобы создать базовый класс врагов, а затем создать множество из них, чтобы ваш игрок их избегал. Сначала импортируйте библиотеку random:
# Import random for random numbers
import random
Затем создайте новый класс спрайтов с именем Enemy, следуя тому же шаблону, который вы использовали для Player:
# Define the enemy object by extending pygame.sprite.Sprite
# The surface you draw on the screen is now an attribute of ‘enemy’
class Enemy(pygame.sprite.Sprite):
def __init__(self):
super(Enemy, self).__init__()
self.surf = pygame.Surface((20, 10))
self.surf.fill((255, 255, 255))
self.rect = self.surf.get_rect(
center=(
random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
random.randint(0, SCREEN_HEIGHT),
)
)
self.speed = random.randint(5, 20)
# Move the sprite based on speed
# Remove the sprite when it passes the left edge of the screen
def update(self):
self.rect.move_ip(-self.speed, 0)
if self.rect.right < 0:
self.kill()
Есть четыре заметных различия между Enemy и Player:
- В строках с 62 по 67 вы обновляете случайное место вдоль правого края экрана. Центр прямоугольника находится за пределами экрана. Он расположен в некотором положении между 20 и 100 пикселями от правого края и где-то между верхним и нижним краями.
- В строке 68 вы определяете .speed как случайное число от 5 до 20. Это указывает, как быстро враг движется к игроку.
- В строках с 73 по 76 вы определяете .update(). Этот метод не требует аргументов, так как враги двигаются автоматически. Вместо этого .update() перемещает врага к левой стороне экрана.
- В строке 74 вы проверяете, ушел ли враг за пределы экрана. Чтобы убедиться, что значок Enemy полностью исчез с экрана и не исчезнет, пока он все еще виден, убедитесь, что правая часть экрана .rect вышла за левую часть экрана. Как только враг исчезает с экрана, вы вызываете .kill(), чтобы предотвратить его дальнейшую обработку.
Итак, что делает .kill()? Чтобы понять это, вы должны знать о группах спрайтов .
Группы спрайтов
Еще один очень полезный класс, предоставляющий возможности — Sprite Group. Это объект, который содержит группу объектов Sprite. Так зачем его использовать? Разве вы не можете вместо этого просто отслеживать свои объекты в списке? Ну, можете, но преимущество использования заключается в методах Group, которые он раскрывает. Эти методы помогают определить, не столкнулся ли кто-либо с файлом Player, что значительно упрощает обновление.
Давайте посмотрим, как создавать группы спрайтов. Вы создадите два разных объекта Group:
- Первый Group проведет каждый Sprite в игре.
- Второй Group будет содержать только объекты Enemy.
Вот как это выглядит в коде:
# Create the ‘player’
player = Player()
# Create groups to hold enemy sprites and all sprites
# — enemies is used for collision detection and position updates
# — all_sprites is used for rendering
enemies = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
all_sprites.add(player)
# Variable to keep the main loop running
running = True
Когда вы вызываете .kill(), Sprite удаляется из всех Group, которым он принадлежит. Это также удаляет ссылки на, что позволяет сборщику мусора Python освобождать память по мере необходимости.
Теперь, когда у вас есть группа all_sprites, вы можете изменить способ рисования объектов. Вместо того, чтобы вызывать .blit(), вы можете перебрать всё в all_sprites:
# Fill the screen with black
screen.fill((0, 0, 0))
# Draw all sprites
for entity in all_sprites:
screen.blit(entity.surf, entity.rect)
# Flip everything to the display
pygame.display.flip()
Теперь all_sprites с каждым кадром будет прорисовываться что угодно, будь то враг или игрок.
Есть только одна проблема… У тебя нет врагов! Вы можете создать кучу врагов в начале игры, но игра быстро станет скучной, когда все они покинут экран через несколько секунд. Вместо этого давайте рассмотрим, как поддерживать постоянный приток врагов по ходу игры.
Пользовательские события
Дизайн требует, чтобы враги появлялись через равные промежутки времени. Это означает, что через определенные промежутки времени вам нужно делать две вещи:
- Создавать новый Enemy.
- Добавить его в all_sprites и enemies.
У вас уже есть код, обрабатывающий случайные события. Цикл событий предназначен для поиска случайных событий, происходящих в каждом кадре, и соответствующей их обработки. К счастью, pygame не ограничивает вас использованием только тех типов событий, которые он определил. Вы можете определить свои собственные события для обработки по своему усмотрению.
Давайте посмотрим, как создать пользовательское событие, которое генерируется каждые несколько секунд:
# Create the screen object
# The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
# Create a custom event for adding a new enemy
ADDENEMY = pygame.USEREVENT + 1
pygame.time.set_timer(ADDENEMY, 250)
# Instantiate player. Right now, this is just a rectangle.
player = Player()
pygame внутренне определяет события как целые числа, поэтому вам необходимо определить новое событие с уникальным целым числом. Последнее резервное событие pygame называется USEREVENT, поэтому определение ADDENEMY = pygame.USEREVENT + 1 в строке 83 гарантирует его уникальность.
Затем вам нужно вставить это новое событие в очередь через равные промежутки времени на протяжении всей игры. Вот тут-то и появляется модуль time. Строка 84 запускает новое событие ADDENEMY каждые 250 миллисекунд, или четыре раза в секунду. Вы вызываете .set_timer() вне игрового цикла, так как вам нужен только один таймер, но он будет срабатывать на протяжении всей игры.
Добавьте код для обработки вашего нового события:
# Main loop
while running:
# Look at every event in the queue
for event in pygame.event.get():
# Did the user hit a key?
if event.type == KEYDOWN:
# Was it the Escape key? If so, stop the loop.
if event.key == K_ESCAPE:
running = False
# Did the user click the window close button? If so, stop the loop.
elif event.type == QUIT:
running = False
# Add a new enemy?
elif event.type == ADDENEMY:
# Create the new enemy and add it to sprite groups
new_enemy = Enemy()
enemies.add(new_enemy)
all_sprites.add(new_enemy)
# Get the set of keys pressed and check for user input
pressed_keys = pygame.key.get_pressed()
player.update(pressed_keys)
# Update enemy position
enemies.update()
Всякий раз, когда обработчик событий видит новое событие ADDENEMY в строке 115, он создает Enemy и добавляет его в enemies и all_sprites. Поскольку Enemy находится в all_sprites, он будет отображаться в каждом кадре. Вам также нужно вызвать строку 126 enemies.update(), которая обновляет все в enemies, чтобы убедиться, что они перемещаются правильно:
Однако это не единственная причина, по которой существует группа только для enemies.
Обнаружение столкновений
Ваш игровой дизайн требует, чтобы игра заканчивалась всякий раз, когда враг сталкивается с игроком. Проверка на коллизии — базовый метод программирования игр, и обычно требуется нетривиальная математика, чтобы определить, будут ли два спрайта накладываться друг на друга.
В этом уроке вы будете использовать метод под названием .spritecollideany(), который читается как «столкновение спрайтов». Этот метод принимает Sprite и Group в качестве параметров. Он просматривает каждый объект в Group и проверяет, пересекается ли .rect с объектом .rect в Sprite. Если да, то возвращается True. В противном случае возвращается False. Это идеально подходит для этой игры, так как вам нужно проверить, не сталкивается ли сингл player с одним из Group enemies.
Вот как это выглядит в коде:
# Draw all sprites
for entity in all_sprites:
screen.blit(entity.surf, entity.rect)
# Check if any enemies have collided with the player
if pygame.sprite.spritecollideany(player, enemies):
# If so, then remove the player and stop the loop
player.kill()
running = False
Строка 135 проверяет, не столкнулся ли player с каким-либо из объектов в enemies. Если это так, то player.kill() вызывается удалить его из каждой группы, к которой он принадлежит. Поскольку рендерятся только объекты в all_sprites, они больше не будут этого делать. Как только игрок был убит, вам также нужно выйти из игры, поэтому вы устанавливаете running = False в строке 138.
На данный момент у вас есть основные элементы игры:
Теперь давайте немного украсим её, сделаем более играбельной и добавим некоторые дополнительные возможности, чтобы она выделялась.
Изображения спрайтов
Хорошо, у вас есть игра, но давайте будем честными… Она немного уродлива. Игрок и враги — это просто белые блоки на черном фоне. Это было ультрасовременно, когда Pong был новым, но сейчас он просто не подходит. Давайте заменим все эти скучные белые прямоугольники более крутыми изображениями, которые сделают игру похожей на настоящую.
Ранее вы узнали, что изображения на диске могут быть загружены в файл Surface с помощью модуля image. Для этого урока мы сделали небольшой реактивный самолет для игрока и несколько ракет для врагов. Вы можете использовать этот вариант, рисовать свои собственные или загружать некоторые бесплатные игровые изображения. Вы можете щелкнуть ссылку ниже, чтобы загрузить рисунок, использованный в этом уроке:
Изменение конструкторов объектов
Прежде чем использовать изображения для представления спрайтов игрока и врагов, необходимо внести некоторые изменения в их конструкторы. Приведенный ниже код заменяет код, использовавшийся ранее:
# Import pygame.locals for easier access to key coordinates
# Updated to conform to flake8 and black standards
# from pygame.locals import *
from pygame.locals import (
RLEACCEL,
K_UP,
K_DOWN,
K_LEFT,
K_RIGHT,
K_ESCAPE,
KEYDOWN,
QUIT,
)
# Define constants for the screen width and height
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
# Define the Player object by extending pygame.sprite.Sprite
# Instead of a surface, use an image for a better-looking sprite
class Player(pygame.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.surf = pygame.image.load(«jet.png»).convert()
self.surf.set_colorkey((255, 255, 255), RLEACCEL)
self.rect = self.surf.get_rect()
Давайте немного распакуем строку 31 pygame.image.load(), которая загружает образ с диска. Вы передаете её путь к файлу. Она возвращает Surface, а вызов .convert() оптимизирует Surface, ускоряя будущие вызовы .blit().
Строка 32 .set_colorkey() используется для указания того, что цвет pygame будет отображаться как прозрачный. В этом случае вы выбираете белый, потому что это цвет фона изображения. Константа RLEACCEL — это необязательный параметр, который помогает pygame ускорить отрисовку на неускоренных дисплеях. Это добавляется в pygame.locals оператор импорта в строке 11.
Больше ничего не нужно менять. Изображение по-прежнему Surface, за исключением того, что теперь на нем что-то нарисовано. Вы все еще используете его таким же образом.
Вот как выглядят похожие изменения Enemy:
# Define the enemy object by extending pygame.sprite.Sprite
# Instead of a surface, use an image for a better-looking sprite
class Enemy(pygame.sprite.Sprite):
def __init__(self):
super(Enemy, self).__init__()
self.surf = pygame.image.load(«missile.png»).convert()
self.surf.set_colorkey((255, 255, 255), RLEACCEL)
# The starting position is randomly generated, as is the speed
self.rect = self.surf.get_rect(
center=(
random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
random.randint(0, SCREEN_HEIGHT),
)
)
self.speed = random.randint(5, 20)
Запуск программы сейчас должен показать, что это та же игра, что и раньше, за исключением того, что теперь вы добавили несколько красивых графических скинов с изображениями. Но зачем останавливаться на том, чтобы спрайты игрока и врагов выглядели красиво? Давайте добавим несколько проплывающих мимо облаков, чтобы создать впечатление реактивного самолета, летящего по небу.
Добавление фоновых изображений
Для фоновых облаков используются те же принципы, что и для Player и Enemy:
- Создайте класс Cloud.
- Добавьте к нему изображение облака.
- Создайте метод .update(), который перемещает cloud к левой стороне экрана.
- Создайте пользовательское событие и обработчик для создания новых объектов cloud с заданным интервалом времени.
- Добавьте вновь созданные объекты cloud в новый файл Group с именем clouds.
- Обновите и нарисуйте clouds в своем игровом цикле.
Вот как это выглядит:
# Define the cloud object by extending pygame.sprite.Sprite
# Use an image for a better-looking sprite
class Cloud(pygame.sprite.Sprite):
def __init__(self):
super(Cloud, self).__init__()
self.surf = pygame.image.load(«cloud.png»).convert()
self.surf.set_colorkey((0, 0, 0), RLEACCEL)
# The starting position is randomly generated
self.rect = self.surf.get_rect(
center=(
random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
random.randint(0, SCREEN_HEIGHT),
)
)
# Move the cloud based on a constant speed
# Remove the cloud when it passes the left edge of the screen
def update(self):
self.rect.move_ip(-5, 0)
if self.rect.right < 0:
self.kill()
Это должно выглядеть очень знакомо. Это почти то же самое, что и Enemy.
Чтобы облака появлялись через определенные промежутки времени, вы будете использовать код создания событий, аналогичный тому, который вы использовали для создания новых врагов. Поместите его прямо под событием создания врага:
# Create custom events for adding a new enemy and a cloud
ADDENEMY = pygame.USEREVENT + 1
pygame.time.set_timer(ADDENEMY, 250)
ADDCLOUD = pygame.USEREVENT + 2
pygame.time.set_timer(ADDCLOUD, 1000)
Это говорит о том, что нужно подождать 1000 миллисекунд или одну секунду, прежде чем создавать следующий файл cloud.
Затем создайте новый Group для хранения каждого вновь созданного cloud:
# Create groups to hold enemy sprites, cloud sprites, and all sprites
# — enemies is used for collision detection and position updates
# — clouds is used for position updates
# — all_sprites is used for rendering
enemies = pygame.sprite.Group()
clouds = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
all_sprites.add(player)
Затем добавьте обработчик нового события ADDCLOUD в обработчик событий:
# Main loop
while running:
# Look at every event in the queue
for event in pygame.event.get():
# Did the user hit a key?
if event.type == KEYDOWN:
# Was it the Escape key? If so, then stop the loop.
if event.key == K_ESCAPE:
running = False
# Did the user click the window close button? If so, stop the loop.
elif event.type == QUIT:
running = False
# Add a new enemy?
elif event.type == ADDENEMY:
# Create the new enemy and add it to sprite groups
new_enemy = Enemy()
enemies.add(new_enemy)
all_sprites.add(new_enemy)
# Add a new cloud?
elif event.type == ADDCLOUD:
# Create the new cloud and add it to sprite groups
new_cloud = Cloud()
clouds.add(new_cloud)
all_sprites.add(new_cloud)
Наконец, убедитесь, что clouds обновляются каждый кадр:
# Update the position of enemies and clouds
enemies.update()
clouds.update()
# Fill the screen with sky blue
screen.fill((135, 206, 250))
Строка 172 обновляет оригинал screen.fill(), чтобы заполнить экран приятным небесно-голубым цветом. Вы можете изменить этот цвет на что-то другое. Может быть, вы хотите инопланетный мир с фиолетовым небом, ядовитую пустошь в неоново-зеленом цвете или поверхность Марса в красном цвете!
Обратите внимание, что каждый новый Cloud и Enemy добавляется all_sprites так же, как clouds и enemies. Это сделано потому, что каждая группа используется для отдельной цели:
- Рендеринг выполняется с помощью all_sprites.
- Обновление позиции выполняется с помощью clouds и enemies.
- Обнаружение столкновений выполняется с помощью enemies.
Вы создаете несколько групп, чтобы вы могли изменить способ движения или поведения спрайтов, не влияя на движение или поведение других спрайтов.
Скорость игры
Во время тестирования игры вы могли заметить, что враги двигаются немного быстрее. Если нет, то ничего страшного, так как на разных машинах в этот момент будут разные результаты.
Причина этого в том, что игровой цикл обрабатывает кадры настолько быстро, насколько позволяют процессор и окружающая среда. Поскольку все спрайты перемещаются один раз за кадр, они могут перемещаться сотни раз в секунду. Количество кадров, обрабатываемых каждую секунду, называется частотой кадров , и правильное понимание этого значения является разницей между игрой, в которую можно играть, и игрой, которую можно забыть.
Обычно вам нужна максимально высокая частота кадров, но в этой игре вам нужно немного замедлить ее, чтобы в нее можно было играть. К счастью, модуль time содержит файл Clock, который предназначен именно для этой цели.
Использование Clock для установки воспроизводимой частоты кадров требует всего две строки кода. Первый создает новый Clock перед началом игрового цикла:
# Setup the clock for a decent framerate
clock = pygame.time.Clock()
Второй вызывает .tick(), чтобы сообщить pygame, что программа достигла конца кадра:
# Flip everything to the display
pygame.display.flip()
# Ensure program maintains a rate of 30 frames per second
clock.tick(30)
Переданный аргумент .tick() устанавливает желаемую частоту кадров. Для этого .tick() вычисляется количество миллисекунд, которое должен занимать каждый кадр, исходя из желаемой частоты кадров. Затем он сравнивает это число с количеством миллисекунд, прошедших с момента последнего вызова .tick(). Если прошло недостаточно времени, .tick()задерживает обработку, чтобы гарантировать, что она никогда не превысит указанную частоту кадров.
Передача меньшей частоты кадров приведет к увеличению времени в каждом кадре для вычислений, в то время как большая частота кадров обеспечивает более плавный (и, возможно, более быстрый) игровой процесс:
Поэкспериментируйте с этим числом, чтобы понять, что вам больше подходит!
Звуковые эффекты
До сих пор вы были сосредоточены на игровом процессе и визуальных аспектах вашей игры. Теперь давайте рассмотрим, как придать вашей игре слуховой оттенок. pygame обеспечивает обработку всех действий mixer, связанных со звуком. Вы будете использовать классы и методы этого модуля для предоставления фоновой музыки и звуковых эффектов для различных действий.
Название mixer относится к тому факту, что модуль смешивает различные звуки в единое целое. С помощью подмодуля music вы можете транслировать отдельные звуковые файлы в различных форматах, таких как MP3 , Ogg и Mod . Вы также можете использовать Sound для хранения одного звукового эффекта, который будет воспроизводиться в формате Ogg или несжатом формате WAV . Все воспроизведение происходит в фоновом режиме, поэтому при воспроизведении Sound метод возвращает значение сразу после воспроизведения звука.
Примечание. В документации pygame указано, что поддержка MP3 ограничена, а неподдерживаемые форматы могут привести к сбою системы. Звуки, упомянутые в этой статье, были протестированы, и мы рекомендуем тщательно протестировать все звуки перед выпуском игры.
Как и в большинстве случаев pygame, использование mixer начинается с шага инициализации. К счастью, этим уже занимается pygame.init(). Вам нужно только вызвать pygame.mixer.init(), если вы хотите изменить значения по умолчанию:
# Setup for sounds. Defaults are good.
pygame.mixer.init()
# Initialize pygame
pygame.init()
# Set up the clock for a decent framerate
clock = pygame.time.Clock()
pygame.mixer.init() принимает несколько аргументов , но в большинстве случаев значения по умолчанию работают нормально. Обратите внимание, что если вы хотите изменить значения по умолчанию, вам нужно вызвать pygame.mixer.init() перед вызовом pygame.init(). В противном случае значения по умолчанию будут действовать независимо от ваших изменений.
После инициализации системы вы можете настроить звуки и фоновую музыку:
# Load and play background music
# Sound source: http://ccmixter.org/files/Apoxode/59262
# License: https://creativecommons.org/licenses/by/3.0/
pygame.mixer.music.load(«Apoxode_-_Electric_1.mp3»)
pygame.mixer.music.play(loops=-1)
# Load all sound files
# Sound sources: Jon Fincher
move_up_sound = pygame.mixer.Sound(«Rising_putter.ogg»)
move_down_sound = pygame.mixer.Sound(«Falling_putter.ogg»)
collision_sound = pygame.mixer.Sound(«Collision.ogg»)
Строки 138 и 139 загружают фоновый звуковой клип и начинают его воспроизведение. Вы можете указать звуковому клипу зацикливаться и никогда не заканчиваться, установив именованный параметр loops=-1.
Строки с 143 по 145 загружают три звука, которые вы будете использовать для различных звуковых эффектов. Первые два — это звуки роста и падения, которые воспроизводятся, когда игрок перемещается вверх или вниз. Последний звук используется всякий раз, когда происходит столкновение. Вы также можете добавить другие звуки, например звук при создании Enemy или финальный звук при завершении игры.
Итак, как вы используете звуковые эффекты? Вы хотите воспроизводить каждый звук, когда происходит определенное событие. Например, когда корабль движется вверх, вы хотите сыграть move_up_sound. Поэтому вы добавляете вызов .play() всякий раз, когда обрабатываете это событие. В дизайне это означает добавление следующих вызовов .update() для Player:
# Define the Player object by extending pygame.sprite.Sprite
# Instead of a surface, use an image for a better-looking sprite
class Player(pygame.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.surf = pygame.image.load(«jet.png»).convert()
self.surf.set_colorkey((255, 255, 255), RLEACCEL)
self.rect = self.surf.get_rect()
# Move the sprite based on keypresses
def update(self, pressed_keys):
if pressed_keys[K_UP]:
self.rect.move_ip(0, -5)
move_up_sound.play()
if pressed_keys[K_DOWN]:
self.rect.move_ip(0, 5)
move_down_sound.play()
При столкновении между игроком и врагом вы воспроизводите звук, вызываемый при обнаружении столкновений:
# Check if any enemies have collided with the player
if pygame.sprite.spritecollideany(player, enemies):
# If so, then remove the player
player.kill()
# Stop any moving sounds and play the collision sound
move_up_sound.stop()
move_down_sound.stop()
collision_sound.play()
# Stop the loop
running = False
Здесь вы сначала останавливаете любые другие звуковые эффекты, потому что при столкновении игрок больше не движется. Затем вы воспроизводите звук столкновения и продолжаете выполнение оттуда.
Наконец, когда игра закончится, все звуки должны прекратиться. Это верно независимо от того, заканчивается ли игра из-за столкновения или пользователь выходит вручную. Для этого в конце программы после цикла добавьте следующие строки:
# All done! Stop and quit the mixer.
pygame.mixer.music.stop()
pygame.mixer.quit()
Технически эти последние несколько строк не требуются, так как программа завершается сразу после этого. Однако, если вы позже решите добавить в игру вступительный экран или экран выхода, то после окончания игры может выполняться больше кода.
Вот и все! Проверьте это снова, и вы должны увидеть что-то вроде этого:
Примечание об источниках
Вы могли заметить комментарий к строкам 136-137 при загрузке фоновой музыки, в котором указан источник музыки и ссылка на лицензию Creative Commons. Это было сделано потому, что этого требовал создатель этого звука. В лицензионных требованиях говорилось, что для использования звука необходимо указать как правильное указание авторства, так и ссылку на лицензию.
Вот несколько источников музыки, звука и изображений, в которых вы можете найти полезный контент:
- OpenGameArt.org: звуки, звуковые эффекты, спрайты и другие изображения.
- Kenney.nl: звуки, звуковые эффекты, спрайты и другие иллюстрации.
- Gamer Art 2D: спрайты и другие арты
- CC Mixter: звуки и звуковые эффекты
- Freesound: звуки и звуковые эффекты
Когда вы создаете свои игры и используете загруженный контент, такой как изображения, музыка или код из других источников, убедитесь, что вы соблюдаете условия лицензирования этих источников.
Заключение
Из этого руководства вы узнали, чем программирование игр pygame отличается от стандартного процедурного программирования. Вы также узнали, как:
- Реализовать циклы событий
- Рисовать предметы на экране
- Воспроизводить звуковые эффекты и музыку
- Обрабатывать пользовательский ввод
Для этого вы использовали подмножество модулей pygame, включая модули display, mixer и music, time, image, event, и key. Вы также использовали несколько классов pygame, в том числе Rect, Surface, Sound и Sprite. Но это только царапает поверхность того, что в pygame можно сделать! Ознакомьтесь с официальной документацией pygame для получения полного списка доступных модулей и классов.
Вы можете найти весь код, графические и звуковые файлы для этой статьи, нажав на ссылку ниже:
Спасибо за прочтение данной статьи!
A-143, 9th Floor, Sovereign Corporate Tower, Sector-136, Noida, Uttar Pradesh — 201305
feedback@geeksforgeeks.org