Время на прочтение
6 мин
Количество просмотров 58K
Это первая часть серии руководств «Разработка игр с помощью 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.
В следующем руководстве этот шаблон будет использован как отправная точка для изучения процесса отрисовки объектов на экране и их движения.
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:UsersDEVANSH SHARMADesktopdownload.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 вместе с вами, читаю, собираю и записываю информацию опытных программистов.
Quick start¶
Welcome to pygame! Once you’ve got pygame installed (pip install pygame-ce
or
pip3 install pygame-ce
for most people), the next question is how to get a game
loop running. Pygame, unlike some other libraries, gives you full control of program
execution. That freedom means it is easy to mess up in your initial steps.
Here is a good example of a basic setup (opens the window, updates the screen, and handles events)—
# Example file showing a basic pygame "game loop" import pygame # pygame setup pygame.init() screen = pygame.display.set_mode((1280, 720)) clock = pygame.time.Clock() running = True while running: # poll for events # pygame.QUIT event means the user clicked X to close your window for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # fill the screen with a color to wipe away anything from last frame screen.fill("purple") # RENDER YOUR GAME HERE # flip() the display to put your work on screen pygame.display.flip() clock.tick(60) # limits FPS to 60 pygame.quit()
Here is a slightly more fleshed out example, which shows you how to move something
(a circle in this case) around on screen—
# Example file showing a circle moving on screen import pygame # pygame setup pygame.init() screen = pygame.display.set_mode((1280, 720)) clock = pygame.time.Clock() running = True dt = 0 player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2) while running: # poll for events # pygame.QUIT event means the user clicked X to close your window for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # fill the screen with a color to wipe away anything from last frame screen.fill("purple") pygame.draw.circle(screen, "red", player_pos, 40) keys = pygame.key.get_pressed() if keys[pygame.K_w]: player_pos.y -= 300 * dt if keys[pygame.K_s]: player_pos.y += 300 * dt if keys[pygame.K_a]: player_pos.x -= 300 * dt if keys[pygame.K_d]: player_pos.x += 300 * dt # flip() the display to put your work on screen pygame.display.flip() # limits FPS to 60 # dt is delta time in seconds since last frame, used for framerate- # independent physics. dt = clock.tick(60) / 1000 pygame.quit()
For more in depth reference, check out the Tutorials
section below, check out a video tutorial (I’m a fan of this one), or reference the API
documentation by module.
Documents¶
- Readme
-
Basic information about pygame: what it is, who is involved, and where to find it.
- File Path Function Arguments
-
How pygame handles file system paths.
- Pygame Logos
-
The logos of Pygame in different resolutions.
- LGPL License
-
This is the license pygame is distributed under.
It provides for pygame to be distributed with open source and commercial software.
Generally, if pygame is not changed, it can be used with any type of program.
Tutorials¶
- Introduction to Pygame
-
An introduction to the basics of pygame.
This is written for users of Python and appeared in volume two of the Py magazine. - Import and Initialize
-
The beginning steps on importing and initializing pygame.
The pygame package is made of several modules.
Some modules are not included on all platforms. - How do I move an Image?
-
A basic tutorial that covers the concepts behind 2D computer animation.
Information about drawing and clearing objects to make them appear animated. - Chimp Tutorial, Line by Line
-
The pygame examples include a simple program with an interactive fist and a chimpanzee.
This was inspired by the annoying flash banner of the early 2000s.
This tutorial examines every line of code used in the example. - Sprite Module Introduction
-
Pygame includes a higher level sprite module to help organize games.
The sprite module includes several classes that help manage details found in almost all games types.
The Sprite classes are a bit more advanced than the regular pygame modules,
and need more understanding to be properly used. - Surfarray Introduction
-
Pygame used the NumPy python module to allow efficient per pixel effects on images.
Using the surface arrays is an advanced feature that allows custom effects and filters.
This also examines some of the simple effects from the pygame example, arraydemo.py. - Camera Module Introduction
-
Pygame, as of 1.9, has a camera module that allows you to capture images,
watch live streams, and do some basic computer vision.
This tutorial covers those use cases. - Newbie Guide
-
A list of thirteen helpful tips for people to get comfortable using pygame.
- Making Games Tutorial
-
A large tutorial that covers the bigger topics needed to create an entire game.
- Display Modes
-
Getting a display surface for the screen.
- 한국어 튜토리얼 (Korean Tutorial)
-
빨간블록 검은블록
- Tutorial de Pygame — Ejemplo del Chimpancé, Línea Por Línea
-
Los ejemplos de pygame incluyen un sencillo programa con un puño interactivo y un chimpancé.
Está inspirado en el molesto banner de flash de principios de la década de 2000.
Este tutorial examina cada línea de código utilizada en el ejemplo.
Reference¶
- Index
-
A list of all functions, classes, and methods in the pygame package.
- pygame.BufferProxy
-
An array protocol view of surface pixels
- pygame.Color
-
Color representation.
- pygame.cursors
-
Loading and compiling cursor images.
- pygame.display
-
Configure the display surface.
- pygame.draw
-
Drawing simple shapes like lines and ellipses to surfaces.
- pygame.event
-
Manage the incoming events from various input devices and the windowing platform.
- pygame.examples
-
Various programs demonstrating the use of individual pygame modules.
- pygame.font
-
Loading and rendering TrueType fonts.
- pygame.freetype
-
Enhanced pygame module for loading and rendering font faces.
- pygame.gfxdraw
-
Anti-aliasing draw functions.
- pygame.image
-
Loading, saving, and transferring of surfaces.
- pygame.joystick
-
Manage the joystick devices.
- pygame.key
-
Manage the keyboard device.
- pygame.locals
-
Pygame constants.
- pygame.mixer
-
Load and play sounds
- pygame.mouse
-
Manage the mouse device and display.
- pygame.mixer.music
-
Play streaming music tracks.
- pygame
-
Top level functions to manage pygame.
- pygame.PixelArray
-
Manipulate image pixel data.
- pygame.Rect
-
Flexible container for a rectangle.
- pygame.scrap
-
Native clipboard access.
- pygame.sndarray
-
Manipulate sound sample data.
- pygame.sprite
-
Higher level objects to represent game images.
- pygame.Surface
-
Objects for images and the screen.
- pygame.surfarray
-
Manipulate image pixel data.
- pygame.tests
-
Test pygame.
- pygame.time
-
Manage timing and framerate.
- pygame.transform
-
Resize and move images.
- pygame C API
-
The C api shared amongst pygame extension modules.
- Search Page
-
Search pygame documents by keyword.
Edit on GitHub
#статьи
- 15 июл 2022
-
0
Учимся программировать через разработку игр. Сегодня напишем знакомую всем «Змейку» — вспомним правила игры и реализуем их на Python.
Иллюстрация: Оля Ежак для Skillbox Media
Изучает Python, его библиотеки и занимается анализом данных. Любит путешествовать в горах.
Pygame — популярная библиотека для создания игр под различные устройства на Windows, macOS, Linux или Android. Она помогает разработчику не только описать геймплей, но и работать с клавиатурой, мышью, акселерометром, звуком и видео.
Первая версия Pygame была представлена Питом Шиннерсом в октябре 2000 года. За 22 года вокруг библиотеки сложилось большое комьюнити, а о работе с ней написано несколько десятков книг. Последняя стабильная версия на июль 2022 года — 2.1.2.
Давайте разберёмся в том, как устроена Pygame, и напишем свою первую игру — классическую «Змейку» на Python, которую студенты часто берут для курсовой работы по программированию.
Pygame — не самостоятельная библиотека. На самом деле это обёртка для библиотеки SDL, Simple DirectMedia Layer. Именно SDL позволяет задействовать любые внешние устройства — например, мышь или клавиатуру. А Pygame делает работу с ними удобной для Python-разработчика.
Установить Pygame просто. Для этого воспользуемся терминалом или командной строкой и командой pip:
pip install pygame
Если во время установки возникают ошибки, то можно воспользоваться официальной документацией. В ней описаны особенности установки на различные системы, а также пути решения распространённых проблем.
Библиотека Pygame состоит из конструкций на языке Python и включает в себя несколько модулей. Модули позволяют получить доступ к определённому устройству и содержат методы для работы с ним. Например, модуль display позволяет работать с экраном, а joystick — считывать движения с джойстика.
После того как вы импортировали Pygame, необходимо инициировать библиотеку с помощью команды pygame.int(). Это поможет нам использовать любые методы любых функций, включённых в библиотеку модулей. Без инициализации код может потерять кросс-платформенность и не запускаться в другой системе.
Помимо модулей, Pygame включает несколько классов Python, которые работают с концепциями, не зависящими от аппаратного обеспечения. Одна из таких концепций — Surface. Surface, можно сказать, определяет прямоугольную область, на которой можно рисовать. Если переносить на практику, то этот класс позволяет создать игровое поле. Он широко используется при работе с Pygame, и мы тоже поработаем с ним при создании «Змейки».
В Pygame вся информация выводится на игровое поле, которому в коде соответствует класс display. Игровое поле может быть полноэкранным или занимать часть экрана. Display создаётся с помощью функции .set_mode(), которая возвращает Surface, представляющий видимую часть окна. Именно эту область вы будете передавать в функции рисования, такие как pygame.draw.circle(), а содержимое этого Surface будет выводиться на дисплей при вызове pygame.display.flip(). Звучит сложно, но на практике будет проще. Оба класса мы будем использовать при создании «Змейки».
Работать с изображениями в Pygame можно двумя способами: создавать их с нуля на экране или использовать изображения с диска. И тот и другой тип можно перезаписывать, загружать и сохранять в различных форматах — например, в PNG и JPG.
Изображения — не самостоятельные компоненты: они загружаются в объекты Surface, которые можно изменять и выводить на дисплеи различными способами.
Выше мы писали, что объекты Surface представлены прямоугольниками, подобно многим другим объектам в Pygame, в том числе изображениям и окнам. Прямоугольники используются настолько активно, что существует даже специальный класс Rect, предназначенный для работы только с ними. Объекты Rect используются для различных задач: создания фигур игрока и врагов, управления взаимодействиями между ними и так далее.
Перед тем как начать писать код, необходимо подумать о том, какие правила мы должны учитывать при создании игры. Давайте составим для «Змейки» базовые правила, которые влияют на геймплей:
- цель игры — набрать как можно больше очков за счёт увеличения длины змейки:
- при поглощении специального объекта на игровом экране длина змейки увеличивается на один блок;
- игрок начинает движение с левой стороны экрана;
- игрок может двигаться влево, вправо, вверх или вниз;
- игрок не может двигаться за пределы экрана, при столкновении с границами игра заканчивается;
- игра заканчивается, когда змейка врезается сама в себя;
- ещё игра заканчивается, когда пользователь закрывает окно.
Несмотря на то что «Змейка» — простая игра, нам предстоит написать довольно много кода. Мы разбили этапы создания игры на последовательные шаги, каждый из которых реализует отдельную часть геймплея.
Запускаем Python
Писать код на Python лучше всего в специальном редакторе. Есть несколько вариантов:
- Воспользоваться специализированными IDE: IntelliJ IDEA или Visual Studio Code. Мы рекомендуем именно этот способ — например, весь код для этой статьи мы писали в Visual Studio Code.
- Использовать терминал на macOS или Linux или воспользоваться командной строкой в Windows. Для этого предварительно потребуется установить Python в систему. Мы подробно писали об этом в отдельном материале.
После установки и запуска Python загружаем библиотеку Pygame:
pip install pygame
Теперь у нас всё готово к работе над игрой. Для дальнейшего написания кода необходимо создать один пустой Python-файл.
Создаём игровое поле
Чтобы создать окно с игрой с помощью Pygame, необходимо использовать функцию display.set_mode() и передать в неё желаемый размер окна в пикселях. Также необходимо использовать методы init() и quit() для инициализации библиотеки в начале кода и её деинициализации в конце кода.
Метод update() используется для обновления содержимого экрана. Существует ещё метод flip(), который работает аналогично update(). Разница в том, что метод update() обновляет только внесённые изменения, а метод flip() перерисовывает экран целиком. Но если в метод update() не передавать никакие параметры, то также обновится весь экран.
import pygame pygame.init() dis=pygame.display.set_mode((500,400)) #Задаём размер игрового поля. pygame.display.update() pygame.quit() quit()
Если сейчас запустить этот код, то экран игры сразу же закроется. Это связано с тем, что код сразу переходит к следующей строчке pygame.quit(), отключающей библиотеку и наше игровое поле. Чтобы избежать этого, необходимо воспользоваться циклом while — он не позволит игровому экрану закрыться:
import pygame pygame.init() dis=pygame.display.set_mode((500,400)) pygame.display.update() pygame.display.set_caption('Змейка от Skillbox') #Добавляем название игры. game_over=False #Создаём переменную, которая поможет нам контролировать статус игры — завершена она или нет. Изначально присваиваем значение False, то есть игра продолжается. while not game_over: for event in pygame.event.get(): print(event) #Выводить в терминал все произошедшие события. pygame.quit() quit()
Кроме этого, мы добавили в код ещё две сущности: название игры и функцию для отслеживания игровых событий. Чтобы у окна с игрой появилось название, мы используем pygame.display.set_caption(») (название пишем в кавычках). А функция event.get() возвращает в терминал все события, которые происходят с игрой.
Запустим код и посмотрим, что получилось:
Теперь игровое окно не закрывается само по себе. Однако и закрыть его мы тоже не сможем — если нажать на кнопку «Выход», ничего не произойдёт. Исправляем это с помощью кода: добавляем событие QUIT, закрывающее окно.
import pygame pygame.init() dis=pygame.display.set_mode((500, 400)) pygame.display.update() pygame.display.set_caption('Змейка от Skillbox') game_over=False while not game_over: for event in pygame.event.get(): if event.type==pygame.QUIT: game_over=True pygame.quit() quit()
Теперь кнопка выхода работает как надо. Если нажать на неё, то окно с игрой закроется.
Создаём змейку
Для начала инициализируем переменные, которые задают цвет. Мы будем использовать их, чтобы присвоить цвет экрану, самой змейке и еде. В Pygame используется стандартная RGB-схема, то есть любой цвет представляет собой комбинацию красного, зелёного и синего цветов, интенсивность которых мы можем менять.
Наша змейка — прямоугольник, поэтому мы воспользуемся функцией создания прямоугольников draw.rect(). Она позволяет задать размер и цвет прямоугольника.
import pygame pygame.init() dis=pygame.display.set_mode((500, 400)) pygame.display.update() pygame.display.set_caption('Змейка от Skillbox') game_over=False while not game_over: for event in pygame.event.get(): if event.type==pygame.QUIT: game_over=True pygame.quit() quit()
Запустим код и посмотрим на результат.
Всё получилось. Ближе к центру экрана появился синий квадрат, который и будет нашей змейкой.
Описываем движения змейки
Управлять перемещением змейки можно с помощью специального класса Pygame KEYDOWN. Класс позволяет использовать четыре стандартных события, получая их с клавиавтуры: K_UP, K_DOWN, K_LEFT и K_RIGHT — они соответствуют движениям змейки вверх, вниз, влево и вправо. Срабатывание любого события из класса KEYDOWN приводит к изменению положения змейки. Зададим шаг этого движения в 10 пикселей.
Кроме того, мы должны создать две переменные для хранения значений координат первой клетки нашей змейки по осям x и y. Назовём их x1_change и y1_change.
import pygame pygame.init() white = (255, 255, 255) black = (0, 0, 0) red = (255, 0, 0) dis = pygame.display.set_mode((800, 600)) pygame.display.set_caption('Змейка от Skillbox') game_over = False x1 = 300 #Указываем начальное значение положения змейки по оси х. y1 = 300 #Указываем начальное значение положения змейки по оси y. x1_change = 0 #Создаём переменную, которой в цикле while будут присваиваться значения изменения положения змейки по оси х. y1_change = 0 #создаём переменную, которой в цикле while будут присваиваться значения изменения положения змейки по оси y. clock = pygame.time.Clock() while not game_over: for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True if event.type == pygame.KEYDOWN: #Добавляем считывание направления движений с клавиатуры. if event.key == pygame.K_LEFT: x1_change = -10 #Указываем шаг изменения положения змейки в 10 пикселей. y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = 10 y1_change = 0 elif event.key == pygame.K_UP: y1_change = -10 x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = 10 x1_change = 0 x1 += x1_change #Записываем новое значение положения змейки по оси х. y1 += y1_change #Записываем новое значение положения змейки по оси y. dis.fill(white) pygame.draw.rect(dis, black, [x1, y1, 10, 10]) pygame.display.update() clock.tick(30) pygame.quit() quit()
Теперь змейка двигается по игровому полю:
Учитываем препятствия — границы игрового поля
Если змейка попадает на границу экрана, то игрок терпит поражение, а игра заканчивается. Чтобы закодить это правило, можно воспользоваться оператором if, который определяет координаты x и y для змейки и анализирует, выходят ли они за границы игрового поля. Добавим необходимый код.
import pygame import time pygame.init() white = (255, 255, 255) black = (0, 0, 0) red = (255, 0, 0) dis_width = 800 #Зададим размер игрового поля через две переменные. dis_height = 600 dis = pygame.display.set_mode((dis_width, dis_width)) pygame.display.set_caption('Змейка от Skillbox') game_over = False x1 = dis_width/2 #Стартовое положение змейки по осям рассчитывается через переменные, указывающие размер игрового экрана. y1 = dis_height/2 snake_block=10 #Укажем в переменной стандартную величину сдвига положения змейки при нажатии на клавиши. x1_change = 0 y1_change = 0 clock = pygame.time.Clock() snake_speed=15 #Ограничим скорость движения змейки. font_style = pygame.font.SysFont(None, 50) def message(msg,color): #Создадим функцию, которая будет показывать нам сообщения на игровом экране. mesg = font_style.render(msg, True, color) dis.blit(mesg, [dis_width/2, dis_height/2]) while not game_over: for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = snake_block y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = snake_block x1_change = 0 if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0: game_over = True #Явно укажем, что если координаты змейки выходят за рамки игрового поля, то игра должна закончиться. x1 += x1_change y1 += y1_change dis.fill(white) pygame.draw.rect(dis, black, [x1, y1, snake_block, snake_block]) pygame.display.update() clock.tick(snake_speed) message("Вы проиграли :(",red) #Сообщение, которое появляется при проигрыше. В нашем случае — при выходе змейки за пределы игрового поля. pygame.display.update() time.sleep(2) pygame.quit() quit()
Теперь, если змейка достигнет края экрана, игра закончится, а на дисплее появится сообщение о проигрыше:
Добавляем еду для змейки
Теперь добавим «еду». Используем библиотеку random, чтобы она появлялась в случайном месте на игровом поле. Когда наша змейка будет проходить через еду, то её длина будет увеличиваться. Это мы добавим на следующем шаге. Кроме того, дадим возможность игроку выйти из игры или начать игру заново после проигрыша.
import pygame import time import random pygame.init() white = (255, 255, 255) black = (0, 0, 0) red = (255, 0, 0) blue = (0, 0, 255) dis_width = 800 dis_height = 600 dis = pygame.display.set_mode((dis_width, dis_height)) pygame.display.set_caption('Змейка от Skillbox') clock = pygame.time.Clock() snake_block = 10 snake_speed = 15 font_style = pygame.font.SysFont(None, 30) def message(msg, color): mesg = font_style.render(msg, True, color) dis.blit(mesg, [dis_width/10, dis_height/3]) def gameLoop(): #Описываем всю игровую логику в одной функции. game_over = False game_close = False x1 = dis_width / 2 y1 = dis_height / 2 x1_change = 0 y1_change = 0 foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 #Создаём переменную, которая будет указывать расположение еды по оси х. foody = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 #Создаём переменную, которая будет указывать расположение еды по оси y. while not game_over: while game_close == True: dis.fill(white) message("Вы проиграли! Нажмите Q для выхода или C для повторной игры", red) pygame.display.update() for event in pygame.event.get(): if event.type == pygame.KEYDOWN: if event.key == pygame.K_q: game_over = True game_close = False if event.key == pygame.K_c: gameLoop() for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = snake_block y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = snake_block x1_change = 0 if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0: game_close = True x1 += x1_change y1 += y1_change dis.fill(white) pygame.draw.rect(dis, blue, [foodx, foody, snake_block, snake_block]) pygame.draw.rect(dis, black, [x1, y1, snake_block, snake_block]) pygame.display.update() pygame.quit() quit() gameLoop()
Теперь при запуске игры кроме самой змейки будет показана еда. В нашем случае — в виде чёрного квадрата.
А если выполнить условие для завершения игры, то появится сообщение с предложением выйти из игры или начать её заново:
Увеличиваем длину змейки
Дополним наш код, чтобы длина змейки увеличивалась при поглощении еды. Для этого нам понадобится список, в котором будет храниться текущая длина змейки. Учтём ещё важный момент из правил: при столкновении головы змейки с её телом игра завершается.
import pygame import time import random pygame.init() white = (255, 255, 255) yellow = (255, 255, 102) black = (0, 0, 0) red = (213, 50, 80) green = (0, 255, 0) blue = (50, 153, 213) dis_width = 800 dis_height = 600 dis = pygame.display.set_mode((dis_width, dis_height)) pygame.display.set_caption('Змейка от Skillbox') clock = pygame.time.Clock() snake_block = 10 snake_speed = 15 font_style = pygame.font.SysFont("bahnschrift", 25) #Укажем название шрифта и его размер для системных сообщений, например, при завершении игры. score_font = pygame.font.SysFont("comicsansms", 35) #Укажем шрифт и его размер для отображения счёта. Это мы реализуем очень скоро. def our_snake(snake_block, snake_list): for x in snake_list: pygame.draw.rect(dis, black, [x[0], x[1], snake_block, snake_block]) def message(msg, color): mesg = font_style.render(msg, True, color) dis.blit(mesg, [dis_width / 6, dis_height / 3]) def gameLoop(): game_over = False game_close = False x1 = dis_width / 2 y1 = dis_height / 2 x1_change = 0 y1_change = 0 snake_List = [] #Создаём список, в котором будем хранить показатель текущей длины змейки. Length_of_snake = 1 foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0 while not game_over: while game_close == True: dis.fill(blue) message("Вы проиграли! Нажмите Q для выхода или C для повторной игры", red) pygame.display.update() for event in pygame.event.get(): if event.type == pygame.KEYDOWN: if event.key == pygame.K_q: game_over = True game_close = False if event.key == pygame.K_c: gameLoop() for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = snake_block y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = snake_block x1_change = 0 if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0: game_close = True x1 += x1_change y1 += y1_change dis.fill(blue) pygame.draw.rect(dis, green, [foodx, foody, snake_block, snake_block]) snake_Head = [] #Создаём список, в котором будет храниться показатель длины змейки при движениях. snake_Head.append(x1) #Добавляем значения в список при изменении по оси х. snake_Head.append(y1) #Добавляем значения в список при изменении по оси y. snake_List.append(snake_Head) if len(snake_List) > Length_of_snake: del snake_List[0] #Удаляем первый элемент в списке длины змейки, чтобы она не увеличивалась сама по себе при движениях. for x in snake_List[:-1]: if x == snake_Head: game_close = True our_snake(snake_block, snake_List) pygame.display.update() if x1 == foodx and y1 == foody: #Указываем, что в случаях, если координаты головы змейки совпадают с координатами еды, еда появляется в новом месте, а длина змейки увеличивается на одну клетку. foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0 Length_of_snake += 1 clock.tick(snake_speed) pygame.quit() quit() gameLoop()
Дополнительно мы изменили цвет игрового поля и змейки, увеличив их контрастность.
Добавляем отображение счёта
Добавим отображение счёта текущей игры. Для этого создадим функцию Your_score. Она будет отображать длину змейки, вычитая из неё 1 (ведь 1 — это начальный размер змейки, и это не является достижением игрока).
def Your_score(score): value = score_font.render("Ваш счёт: " + str(score), True, yellow) dis.blit(value, [0, 0])
И отдельно пропишем правило определения длины змейки, вычитая из текущей длины змейки единицу.
Your_score(Length_of_snake - 1)
Теперь на игровом поле будет отображаться текущий счёт:
Можно считать, что наша работа над «Змейкой» закончена. Мы полностью реализовали геймплей, который запланировали на старте работы.
Наш код полностью и без комментариев:
import pygame import time import random pygame.init() white = (255, 255, 255) yellow = (255, 255, 102) black = (0, 0, 0) red = (213, 50, 80) green = (0, 255, 0) blue = (50, 153, 213) dis_width = 800 dis_height = 600 dis = pygame.display.set_mode((dis_width, dis_height)) pygame.display.set_caption('Змейка от Skillbox') clock = pygame.time.Clock() snake_block = 10 snake_speed = 15 font_style = pygame.font.SysFont("bahnschrift", 25) score_font = pygame.font.SysFont("comicsansms", 35) def Your_score(score): value = score_font.render("Ваш счёт: " + str(score), True, yellow) dis.blit(value, [0, 0]) def our_snake(snake_block, snake_list): for x in snake_list: pygame.draw.rect(dis, black, [x[0], x[1], snake_block, snake_block]) def message(msg, color): mesg = font_style.render(msg, True, color) dis.blit(mesg, [dis_width / 6, dis_height / 3]) def gameLoop(): game_over = False game_close = False x1 = dis_width / 2 y1 = dis_height / 2 x1_change = 0 y1_change = 0 snake_List = [] Length_of_snake = 1 foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0 while not game_over: while game_close == True: dis.fill(blue) message("Вы проиграли! Нажмите Q для выхода или C для повторной игры", red) Your_score(Length_of_snake - 1) pygame.display.update() for event in pygame.event.get(): if event.type == pygame.KEYDOWN: if event.key == pygame.K_q: game_over = True game_close = False if event.key == pygame.K_c: gameLoop() for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = snake_block y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = snake_block x1_change = 0 if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0: game_close = True x1 += x1_change y1 += y1_change dis.fill(blue) pygame.draw.rect(dis, green, [foodx, foody, snake_block, snake_block]) snake_Head = [] snake_Head.append(x1) snake_Head.append(y1) snake_List.append(snake_Head) if len(snake_List) > Length_of_snake: del snake_List[0] for x in snake_List[:-1]: if x == snake_Head: game_close = True our_snake(snake_block, snake_List) Your_score(Length_of_snake - 1) pygame.display.update() if x1 == foodx and y1 == foody: foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0 Length_of_snake += 1 clock.tick(snake_speed) pygame.quit() quit() gameLoop()
Узнать об особенностях работы с Pygame и возможностях библиотеки можно в официальной документации. Углубиться в разработку и попробовать другие игры можно благодаря специализированным книгам:
- «Учим Python, делая крутые игры» Эла Свейгарта;
- «Beginning Game Development with Python and Pygame: From Novice to Professional» Уилла Макгугана;
- «Program Arcade Games: With Python and Pygame» Пола Винсента Крэйвена.
Когда я начал изучать компьютерное программирование в конце прошлого тысячелетия, это было связано с моим желанием писать компьютерные игры. Я пытался понять, как писать игры на каждом языке и на каждой изученной платформе, включая Python. Так я открыл для себяpygame
и научился использовать его для написания игр и других графических программ. В то время мне очень хотелось получить учебник поpygame
.
К концу этой статьи вы сможете:
-
Нарисуйте предметы на вашем экране
-
Воспроизведение звуковых эффектов и музыки
-
Обрабатывать пользовательский ввод
-
Реализация циклов событий
-
Опишите, чем игровое программирование отличается от стандартного процедурного программирования на Python
Вы можете получить весь код в этой статье, чтобы следовать:
Фон и настройка
pygame
— это оболочка Python дляSDL library, что означаетSimple DirectMedia Layer. SDL обеспечивает кроссплатформенный доступ к базовым компонентам мультимедийного оборудования вашей системы, таким как звук, видео, мышь, клавиатура и джойстик. pygame
начал свою жизнь как замена остановившемусяPySDL project. Кросс-платформенный характер как SDL, так иpygame
означает, что вы можете писать игры и мультимедийные программы Python для любой платформы, которая их поддерживает!
Чтобы установитьpygame
на вашу платформу, используйте соответствующую командуpip
:
Вы можете проверить установку, загрузив один из примеров, поставляемых с библиотекой:
$ python3 -m pygame.examples.aliens
Если появляется окно игры, значитpygame
установлен правильно! Если у вас возникнут проблемы, тоGetting Started guide описывает некоторые известные проблемы и предостережения для всех платформ.
Основная программа PyGame
Прежде чем переходить к деталям, давайте взглянем на базовую программуpygame
. Эта программа создает окно, заполняет фон белым и рисует синий круг в середине:
1 # Simple pygame program
2
3 # Import and initialize the pygame library
4 import pygame
5 pygame.init()
6
7 # Set up the drawing window
8 screen = pygame.display.set_mode([500, 500])
9
10 # Run until the user asks to quit
11 running = True
12 while running:
13
14 # Did the user click the window close button?
15 for event in pygame.event.get():
16 if event.type == pygame.QUIT:
17 running = False
18
19 # Fill the background with white
20 screen.fill((255, 255, 255))
21
22 # Draw a solid blue circle in the center
23 pygame.draw.circle(screen, (0, 0, 255), (250, 250), 75)
24
25 # Flip the display
26 pygame.display.flip()
27
28 # Done! Time to quit.
29 pygame.quit()
Когда вы запустите эту программу, вы увидите окно, которое выглядит так:
Давайте разберем этот код, раздел за разделом:
-
Lines 4 and 5 импортирует и инициализирует библиотеку
pygame
. Без этих строк нетpygame
. -
Line 8 устанавливает окно отображения вашей программы. Вы предоставляете либо список, либо кортеж, который определяет ширину и высоту создаваемого окна. Эта программа использует список для создания квадратного окна с 500 пикселями на каждой стороне.
-
Lines 11 and 12 настраиваетgame loop для контроля завершения программы. Об этом вы узнаете позже в этом уроке.
-
Lines 15 to 17 просматривает и обрабатываетevents в игровом цикле. Вы придете к событиям чуть позже. В этом случае обрабатывается только событие
pygame.QUIT
, которое происходит, когда пользователь нажимает кнопку закрытия окна. -
Line 20 заполняет окно сплошным цветом.
screen.fill()
принимает либо список, либо кортеж, определяющий значения RGB для цвета. Поскольку(255, 255, 255)
предоставлен, окно заполняется белым цветом. -
Line 23 рисует круг в окне, используя следующие параметры:
-
screen
: окно, в котором нужно рисовать -
(0, 0, 255)
: кортеж, содержащий значения цвета RGB -
(250, 250)
: кортеж, определяющий координаты центра круга -
75
: радиус нарисованного круга в пикселях
-
-
Line 26 обновляет содержимое дисплея на экране. Без этого звонка в окне ничего не появляется!
-
Line 29 выходит из
pygame
. Это происходит только после завершения цикла.
Этоpygame
версия «Hello, World». Теперь давайте углубимся в концепции, лежащие в основе этого кода.
PyGame Concepts
Посколькуpygame
и библиотека SDL переносимы на разные платформы и устройства, они оба должны определять абстракции и работать с ними для различных аппаратных реалий. Понимание этих концепций и абстракций поможет вам спроектировать и разработать свои собственные игры.
Инициализация и Модули
Библиотекаpygame
— этоcomposed of a number of Python constructs, которая включает несколько разныхmodules. Эти модули обеспечивают абстрактный доступ к конкретному оборудованию в вашей системе, а также унифицированные методы для работы с этим оборудованием. Например,display
обеспечивает единообразный доступ к вашему видео дисплею, аjoystick
позволяет абстрактно управлять вашим джойстиком.
После импорта библиотекиpygame
в приведенном выше примере первое, что вы сделали, — этоinitialize PyGame с использованиемpygame.init()
. Эта функцияcalls the separate init()
functions всех включенных модулейpygame
. Поскольку эти модули являются абстракциями для конкретного оборудования, этот шаг инициализации необходим для того, чтобы вы могли работать с одним и тем же кодом в Linux, Windows и Mac.
Дисплеи и поверхности
В дополнение к модулямpygame
также включает несколько Pythonclasses, которые инкапсулируют концепции, не зависящие от оборудования. Один из них —Surface
, который, по сути, определяет прямоугольную область, на которой вы можете рисовать. ОбъектыSurface
используются во многих контекстах вpygame
. Позже вы увидите, как загрузить изображение вSurface
и отобразить его на экране.
Вpygame
все просматривается на одном созданном пользователемdisplay
, который может быть окном или полноэкранным. Отображение создается с использованием.set_mode()
, который возвращаетSurface
, представляющий видимую часть окна. Именно этотSurface
вы передаете в функции рисования, такие какpygame.draw.circle()
, и содержимое этогоSurface
выводится на дисплей, когда вы вызываетеpygame.display.flip()
.
Изображения и Rects
Ваша базовая программаpygame
нарисовала фигуру прямо на экранеSurface
, но вы также можете работать с изображениями на диске. image
module позволяет создавать изображенияload иsave в различных популярных форматах. Изображения загружаются в объектыSurface
, которыми затем можно управлять и отображать различными способами.
Как упоминалось выше, объектыSurface
представлены прямоугольниками, как и многие другие объекты вpygame
, такие как изображения и окна. Прямоугольники настолько широко используются, что существуетspecial Rect
class только для их обработки. Вы будете использовать объекты и изображенияRect
в своей игре, чтобы рисовать игроков и врагов и управлять столкновениями между ними.
Ладно, достаточно теории. Давайте разработаем и напишем игру!
Базовый дизайн игры
Прежде чем вы начнете писать какой-либо код, всегда полезно иметь какой-то дизайн. Так как это обучающая игра, давайте разработаем для нее и базовый геймплей:
-
Цель игры — избежать препятствий:
-
Плеер запускается с левой стороны экрана.
-
Препятствия входят случайным образом справа и движутся влево по прямой линии.
-
-
Игрок может двигаться влево, вправо, вверх или вниз, чтобы избежать препятствий.
-
Плеер не может отойти от экрана.
-
Игра заканчивается либо тогда, когда игрок сталкивается с препятствием, либо когда пользователь закрывает окно.
Когда он описывал программные проекты,former colleague of mine говорил: «Вы не знаете, что делаете, пока не узнаете, чего не делаете». Имея это в виду, вот некоторые вещи, которые не будут рассмотрены в этом руководстве:
-
Нет нескольких жизней
-
Нет счета
-
Нет возможности атаки игрока
-
Нет продвигающихся уровней
-
Нет боссов
Вы можете сами попробовать добавить эти и другие функции в свою программу.
Давайте начнем!
Импорт и инициализация PyGame
После импортаpygame
вам также потребуется его инициализировать. Это позволяетpygame
подключать свои абстракции к вашему конкретному оборудованию:
1 # Import the pygame module
2 import pygame
3
4 # Import pygame.locals for easier access to key coordinates
5 # Updated to conform to flake8 and black standards
6 from pygame.locals import (
7 K_UP,
8 K_DOWN,
9 K_LEFT,
10 K_RIGHT,
11 K_ESCAPE,
12 KEYDOWN,
13 QUIT,
14 )
15
16 # Initialize pygame
17 pygame.init()
Библиотекаpygame
определяет многие вещи помимо модулей и классов. Он также определяет некоторыеlocal constants для таких вещей, как нажатия клавиш, движения мыши и атрибуты отображения. Вы ссылаетесь на эти константы, используя синтаксисpygame.<CONSTANT>
. Импортируя определенные константы изpygame.locals
, вы можете вместо этого использовать синтаксис<CONSTANT>
. Это сэкономит вам несколько нажатий клавиш и улучшит общую читабельность.
Настройка дисплея
Теперь вам нужно что-то нарисовать! Создайтеscreen как общий холст:
1 # Import the pygame module
2 import pygame
3
4 # Import pygame.locals for easier access to key coordinates
5 # Updated to conform to flake8 and black standards
6 from pygame.locals import (
7 K_UP,
8 K_DOWN,
9 K_LEFT,
10 K_RIGHT,
11 K_ESCAPE,
12 KEYDOWN,
13 QUIT,
14 )
15
16 # Initialize pygame
17 pygame.init()
18
19 # Define constants for the screen width and height
20 SCREEN_WIDTH = 800
21 SCREEN_HEIGHT = 600
22
23 # Create the screen object
24 # The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT
25 screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
Вы создаете экран для использования, вызываяpygame.display.set_mode()
и передавая кортеж или список с желаемой шириной и высотой. В этом случае размер окна составляет 800×600, что определяется константамиSCREEN_WIDTH
иSCREEN_HEIGHT
в строках 20 и 21. Это возвращаетSurface
, который представляет внутренние размеры окна. Это часть окна, которой вы можете управлять, в то время как ОС контролирует границы окна и строку заголовка.
Если вы запустите эту программу сейчас, вы увидите, что окно ненадолго всплывет, а затем сразу исчезнет при выходе из программы. Не моргайте, иначе вы можете пропустить это! В следующем разделе вы сосредоточитесь на основном игровом цикле, чтобы гарантировать, что ваша программа завершает работу только при правильном вводе.
Настройка игрового цикла
В каждой игре от Pong до Fortnite используетсяgame loop для управления игровым процессом. Игровой цикл выполняет четыре очень важных вещи:
-
Обрабатывает пользовательский ввод
-
Обновляет состояние всех игровых объектов
-
Обновляет дисплей и аудио выход
-
Поддерживает скорость игры
Каждый цикл игрового цикла называетсяframe, и чем быстрее вы сможете выполнять действия в каждом цикле, тем быстрее будет работать ваша игра. Кадры продолжают появляться до тех пор, пока не будет выполнено какое-либо условие для выхода из игры. В вашем дизайне есть два условия, которые могут завершить игровой цикл:
-
Игрок сталкивается с препятствием. (Вы узнаете об обнаружении столкновений позже.)
-
Игрок закрывает окно.
Первое, что делает игровой цикл, это обрабатывает пользовательский ввод, чтобы позволить игроку перемещаться по экрану. Поэтому вам нужен какой-то способ для захвата и обработки различных входных данных. Вы делаете это с помощью системы событийpygame
.
Обработка событий
Нажатие клавиш, движения мыши и даже движения джойстика являются одними из способов, которыми пользователь может обеспечить ввод. Результатом всего пользовательского ввода является созданиеevent. События могут происходить в любое время и часто (но не всегда) происходят вне программы. Все события вpygame
помещаются в очередь событий, к которой затем можно получить доступ и управлять ими. Работа с событиями обозначаетсяhandling них, а код для этого называетсяevent handler.
Каждое событие вpygame
имеет связанное с ним событиеtype. Для вашей игры типы событий, на которых вы сосредоточитесь, — это нажатия клавиш и закрытие окна. События нажатия клавиш имеют тип событияKEYDOWN
, а событие закрытия окна имеет типQUIT
. Различные типы событий могут также иметь другие данные, связанные с ними. Например, тип событияKEYDOWN
также имеет переменную с именемkey
, чтобы указать, какая клавиша была нажата.
Вы получаете доступ к списку всех активных событий в очереди, вызываяpygame.event.get()
. Затем вы просматриваете этот список, проверяете каждый тип события и отвечаете соответственно:
27 # Variable to keep the main loop running
28 running = True
29
30 # Main loop
31 while running:
32 # Look at every event in the queue
33 for event in pygame.event.get():
34 # Did the user hit a key?
35 if event.type == KEYDOWN:
36 # Was it the Escape key? If so, stop the loop.
37 if event.key == K_ESCAPE:
38 running = False
39
40 # Did the user click the window close button? If so, stop the loop.
41 elif event.type == QUIT:
42 running = False
Давайте внимательнее посмотрим на этот игровой цикл:
-
Line 28 устанавливает управляющую переменную для игрового цикла. Чтобы выйти из цикла и игры, вы устанавливаете
running = False
. Игровой цикл начинается в строке 29. -
Line 31 запускает обработчик событий, просматривая все события в очереди событий. Если событий нет, список пуст, и обработчик ничего не сделает.
-
Lines 35 to 38 проверяет, является ли текущий
event.type
событиемKEYDOWN
. Если это так, то программа проверяет, какая клавиша была нажата, глядя на атрибутevent.key
. Если ключ является ключом[.kbd .key-escape]#Esc #, обозначеннымK_ESCAPE
, то он выходит из игрового цикла, устанавливаяrunning = False
. -
Lines 41 and 42 выполняет аналогичную проверку для типа события с именем
QUIT
. Это событие происходит только тогда, когда пользователь нажимает кнопку закрытия окна. Пользователь также может использовать любое другое действие операционной системы, чтобы закрыть окно.
Когда вы добавите эти строки в предыдущий код и запустите его, вы увидите окно с пустым или черным экраном:
Окно не исчезнет, пока вы не нажмете клавишу[.kbd .key-escape]#Esc # или иным образом не вызовете событиеQUIT
, закрыв окно.
Рисование на экране
В примере программы вы рисовали на экране с помощью двух команд:
-
screen.fill()
для заливки фона -
pygame.draw.circle()
, чтобы нарисовать круг
Теперь вы узнаете о третьем способе рисования на экране: с помощьюSurface
.
Напомним, чтоSurface
— это прямоугольный объект, на котором вы можете рисовать, как чистый лист бумаги. Объектscreen
— этоSurface
, и вы можете создавать свои собственные объектыSurface
отдельно от экрана дисплея. Давайте посмотрим, как это работает:
44 # Fill the screen with white
45 screen.fill((255, 255, 255))
46
47 # Create a surface and pass in a tuple containing its length and width
48 surf = pygame.Surface((50, 50))
49
50 # Give the surface a color to separate it from the background
51 surf.fill((0, 0, 0))
52 rect = surf.get_rect()
После заполнения экрана белым цветом в строке 45 создается новыйSurface
в строке 48. ЭтотSurface
имеет ширину 50 пикселей, высоту 50 пикселей и назначенsurf
. На этом этапе вы относитесь к нему так же, как кscreen
. Итак, на линии 51 вы заполняете его черным. Вы также можете получить доступ к его базовомуRect
, используя.get_rect()
. Он сохраняется какrect
для дальнейшего использования.
Используя.blit()
и.flip()
Просто создать новыйSurface
недостаточно, чтобы увидеть его на экране. Для этого вам нужноblitSurface
на другойSurface
. Терминblit
означаетBlock Transfer, а.blit()
— это то, как вы копируете содержимое одногоSurface
в другой. Вы можете только.blit()
от одногоSurface
к другому, но поскольку экран — это просто еще одинSurface
, это не проблема. Вот как вы рисуетеsurf
на экране:
54 # This line says "Draw surf onto the screen at the center"
55 screen.blit(surf, (SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
56 pygame.display.flip()
Вызов.blit()
в строке 55 принимает два аргумента:
-
Surface
для рисования -
Место для его рисования на источнике
Surface
Координаты(SCREEN_WIDTH/2, SCREEN_HEIGHT/2)
говорят вашей программе разместитьsurf
точно в центре экрана, но это не совсем так:
Причина, по которой изображение выглядит не по центру, заключается в том, что.blit()
помещаетtop-left corner изsurf
в указанное место. Если вы хотите, чтобыsurf
был центрирован, вам нужно будет выполнить некоторые вычисления, чтобы сместить его вверх и влево. Вы можете сделать это, вычтя ширину и высотуsurf
из ширины и высоты экрана, разделив каждую на 2, чтобы определить местонахождение центра, а затем передав эти числа в качестве аргументов вscreen.blit()
:
54 # Put the center of surf at the center of the display
55 surf_center = (
56 (SCREEN_WIDTH-surf.get_width())/2,
57 (SCREEN_HEIGHT-surf.get_height())/2
58 )
59
60 # Draw surf at the new coordinates
61 screen.blit(surf, surf_center)
62 pygame.display.flip()
Обратите внимание на вызовpygame.display.flip()
после вызоваblit()
. Это обновляет весь экран со всем, что было нарисовано с момента последнего переворота. Без вызова.flip()
ничего не отображается.
Спрайты
В вашем игровом дизайне игрок начинает слева, а справа появляются препятствия. Вы можете представить все препятствия с помощью объектовSurface
, чтобы все было проще рисовать, но как узнать, где их нарисовать? Как узнать, столкнулось ли препятствие с игроком? Что происходит, когда препятствие летит за пределы экрана? Что делать, если вы хотите нарисовать фоновые изображения, которые также перемещаются? Что если вы хотите, чтобы ваши изображения были анимированными? Вы можете справиться со всеми этими и другими ситуациями с помощьюsprites.
С точки зрения программированияsprite — это 2D-представление чего-либо на экране. По сути, это картина. pygame
предоставляетSprite
class, который предназначен для хранения одного или нескольких графических представлений любого игрового объекта, который вы хотите отобразить на экране. Чтобы использовать его, вы создаете новый класс, расширяющийSprite
. Это позволяет использовать его встроенные методы.
игроки
Вот как вы используете объектыSprite
в текущей игре для определения игрока. Вставьте этот код после строки 18:
20 # Define a Player object by extending pygame.sprite.Sprite
21 # The surface drawn on the screen is now an attribute of 'player'
22 class Player(pygame.sprite.Sprite):
23 def __init__(self):
24 super(Player, self).__init__()
25 self.surf = pygame.Surface((75, 25))
26 self.surf.fill((255, 255, 255))
27 self.rect = self.surf.get_rect()
Сначала вы определяетеPlayer
, расширяяpygame.sprite.Sprite
в строке 22. Затем.__init__()
использует.super()
для вызова метода.__init__()
дляSprite
. Для получения дополнительной информации о том, почему это необходимо, вы можете прочитатьSupercharge Your Classes With Python super().
Затем вы определяете и инициализируете.surf
, чтобы удерживать изображение для отображения, которое в настоящее время представляет собой белое поле. Вы также определяете и инициализируете.rect
, который вы будете использовать для рисования игрока позже. Чтобы использовать этот новый класс, вам нужно создать новый объект и изменить код для рисования. Разверните блок кода ниже, чтобы увидеть все это вместе:
Запустите этот код. Вы увидите белый прямоугольник примерно в середине экрана:
Как вы думаете, что произойдет, если вы измените строку 59 наscreen.blit(player.surf, player.rect)
? Попробуйте и посмотрите:
55 # Fill the screen with black
56 screen.fill((0, 0, 0))
57
58 # Draw the player on the screen
59 screen.blit(player.surf, player.rect)
60
61 # Update the display
62 pygame.display.flip()
Когда вы передаетеRect
в.blit()
, он использует координаты верхнего левого угла для рисования поверхности. Вы будете использовать это позже, чтобы заставить вашего игрока двигаться!
Пользовательский ввод
До сих пор вы научились настраиватьpygame
и рисовать объекты на экране. Теперь начинается самое интересное! Вы сделаете плеер управляемым с помощью клавиатуры.
Ранее вы видели, чтоpygame.event.get()
возвращает список событий в очереди событий, которую вы просматриваете на предмет типов событийKEYDOWN
. Ну, это не единственный способ читать нажатия клавиш. pygame
также предоставляетpygame.event.get_pressed()
, который возвращаетdictionary, содержащий все текущие событияKEYDOWN
в очереди.
Поместите это в свой игровой цикл сразу после цикла обработки событий. Это возвращает словарь, содержащий клавиши, нажимаемые в начале каждого кадра:
54 # Get the set of keys pressed and check for user input
55 pressed_keys = pygame.key.get_pressed()
Затем вы пишете метод вPlayer
для приема этого словаря. Это будет определять поведение спрайта на основе нажатых клавиш. Вот как это может выглядеть:
29 # Move the sprite based on user keypresses
30 def update(self, pressed_keys):
31 if pressed_keys[K_UP]:
32 self.rect.move_ip(0, -5)
33 if pressed_keys[K_DOWN]:
34 self.rect.move_ip(0, 5)
35 if pressed_keys[K_LEFT]:
36 self.rect.move_ip(-5, 0)
37 if pressed_keys[K_RIGHT]:
38 self.rect.move_ip(5, 0)
K_UP
,K_DOWN
,K_LEFT
иK_RIGHT
соответствуют клавишам со стрелками на клавиатуре. Если словарная запись для этого ключа —True
, значит, этот ключ нажат, и вы перемещаете игрока.rect
в правильном направлении. Здесь вы используете.move_ip()
, что означаетmove in place, для перемещения текущегоRect
.
Затем вы можете вызывать.update()
каждый кадр, чтобы перемещать спрайт игрока в ответ на нажатия клавиш. Добавьте этот вызов сразу после вызова.get_pressed()
:
52 # Main loop
53 while running:
54 # for loop through the event queue
55 for event in pygame.event.get():
56 # Check for KEYDOWN event
57 if event.type == KEYDOWN:
58 # If the Esc key is pressed, then exit the main loop
59 if event.key == K_ESCAPE:
60 running = False
61 # Check for QUIT event. If QUIT, then set running to false.
62 elif event.type == QUIT:
63 running = False
64
65 # Get all the keys currently pressed
66 pressed_keys = pygame.key.get_pressed()
67
68 # Update the player sprite based on user keypresses
69 player.update(pressed_keys)
70
71 # Fill the screen with black
72 screen.fill((0, 0, 0))
Теперь вы можете перемещать прямоугольник игрока по экрану с помощью клавиш со стрелками:
Вы можете заметить две небольшие проблемы:
-
Прямоугольник игрока может двигаться очень быстро, если удерживать клавишу нажатой. Вы поработаете над этим позже.
-
Прямоугольник игрока может сдвинуться с экрана. Давайте решим это сейчас.
Чтобы игрок оставался на экране, вам нужно добавить некоторую логику, чтобы определять, собирается лиrect
уйти с экрана. Для этого вы проверяете, не переместились ли координатыrect
за пределы экрана. Если это так, то вы даете команду программе переместить ее обратно к краю:
25 # Move the sprite based on user keypresses
26 def update(self, pressed_keys):
27 if pressed_keys[K_UP]:
28 self.rect.move_ip(0, -5)
29 if pressed_keys[K_DOWN]:
30 self.rect.move_ip(0, 5)
31 if pressed_keys[K_LEFT]:
32 self.rect.move_ip(-5, 0)
33 if pressed_keys[K_RIGHT]:
34 self.rect.move_ip(5, 0)
35
36 # Keep player on the screen
37 if self.rect.left < 0:
38 self.rect.left = 0
39 if self.rect.right > SCREEN_WIDTH:
40 self.rect.right = SCREEN_WIDTH
41 if self.rect.top <= 0:
42 self.rect.top = 0
43 if self.rect.bottom >= SCREEN_HEIGHT:
44 self.rect.bottom = SCREEN_HEIGHT
Здесь вместо использования.move()
вы просто меняете соответствующие координаты.top
,.bottom
,.left
или.right
напрямую. Проверьте это, и вы увидите, что прямоугольник игрока больше не может сдвинуться с экрана.
Теперь давайте добавим несколько врагов!
враги
Что за игра без врагов? Вы будете использовать методы, которые вы уже изучили, чтобы создать базовый класс врагов, а затем создадите множество из них, чтобы ваш игрок избегал их. Сначала импортируйте библиотекуrandom
:
4 # Import random for random numbers
5 import random
Затем создайте новый класс спрайтов с именемEnemy
, следуя тому же шаблону, который вы использовали дляPlayer
:
55 # Define the enemy object by extending pygame.sprite.Sprite
56 # The surface you draw on the screen is now an attribute of 'enemy'
57 class Enemy(pygame.sprite.Sprite):
58 def __init__(self):
59 super(Enemy, self).__init__()
60 self.surf = pygame.Surface((20, 10))
61 self.surf.fill((255, 255, 255))
62 self.rect = self.surf.get_rect(
63 center=(
64 random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
65 random.randint(0, SCREEN_HEIGHT),
66 )
67 )
68 self.speed = random.randint(5, 20)
69
70 # Move the sprite based on speed
71 # Remove the sprite when it passes the left edge of the screen
72 def update(self):
73 self.rect.move_ip(-self.speed, 0)
74 if self.rect.right < 0:
75 self.kill()
МеждуEnemy
иPlayer
есть четыре заметных различия:
-
On lines 62 to 67, вы обновляете
rect
, чтобы он был случайным местом вдоль правого края экрана. Центр прямоугольника находится за пределами экрана. Он расположен в некотором месте между 20 и 100 пикселями от правого края и где-то между верхним и нижним краями. -
On line 68, вы определяете
.speed
как случайное число от 5 до 20. Это указывает, как быстро этот враг движется к игроку. -
On lines 73 to 76, вы определяете
.update()
. Это не требует никаких аргументов, поскольку враги двигаются автоматически. Вместо этого.update()
перемещает врага в левую часть экрана на.speed
, определенное при его создании. -
On line 74, вы проверяете, ушел ли противник за пределы экрана. Чтобы убедиться, что
Enemy
полностью за пределами экрана и не исчезнет просто так, пока он все еще виден, вы убедитесь, что правая сторона.rect
прошла за левую часть экрана. Когда противник находится за кадром, вы вызываете.kill()
, чтобы предотвратить его дальнейшую обработку.
Итак, что делает.kill()
? Чтобы понять это, вы должны знать оSprite Groups.
Спрайт группы
Еще один очень полезный класс, который предоставляетpygame
, — этоSprite Group
. Это объект, содержащий группу объектовSprite
. Так зачем его использовать? Разве вы не можете вместо этого просто отслеживать свои объектыSprite
в списке? Что ж, вы можете, но преимущество использованияGroup
заключается в методах, которые он предоставляет. Эти методы помогают определить, столкнулся ли какой-либоEnemy
сPlayer
, что значительно упрощает обновление.
Давайте посмотрим, как создавать группы спрайтов. Вы создадите два разных объектаGroup
:
-
Первые
Group
будут удерживать каждыеSprite
в игре. -
Второй
Group
будет содержать только объектыEnemy
.
Вот как это выглядит в коде:
82 # Create the 'player'
83 player = Player()
84
85 # Create groups to hold enemy sprites and all sprites
86 # - enemies is used for collision detection and position updates
87 # - all_sprites is used for rendering
88 enemies = pygame.sprite.Group()
89 all_sprites = pygame.sprite.Group()
90 all_sprites.add(player)
91
92 # Variable to keep the main loop running
93 running = True
Когда вы вызываете.kill()
,Sprite
удаляется из каждогоGroup
, которому он принадлежит. Это также удаляет ссылки наSprite
, что позволяет сборщику мусора Python при необходимости освобождать память.
Теперь, когда у вас есть группаall_sprites
, вы можете изменить способ рисования объектов. Вместо вызова.blit()
только дляPlayer
, вы можете перебирать все вall_sprites
:
117 # Fill the screen with black
118 screen.fill((0, 0, 0))
119
120 # Draw all sprites
121 for entity in all_sprites:
122 screen.blit(entity.surf, entity.rect)
123
124 # Flip everything to the display
125 pygame.display.flip()
Теперь все, что помещено вall_sprites
, будет отрисовываться с каждым кадром, будь то враг или игрок.
Есть только одна проблема … У тебя нет врагов! Вы можете создать кучу врагов в начале игры, но игра быстро станет скучной, когда все они покинут экран через несколько секунд. Вместо этого давайте рассмотрим, как поддерживать постоянный приток врагов в ходе игры.
Пользовательские события
Дизайн призывает врагов появляться через равные промежутки времени. Это означает, что через заданные интервалы вам нужно сделать две вещи:
-
Создайте новый
Enemy
. -
Добавьте его в
all_sprites
иenemies
.
У вас уже есть код, который обрабатывает случайные события. Цикл обработки событий предназначен для поиска случайных событий, происходящих в каждом кадре, и надлежащего обращения с ними. К счастью,pygame
не ограничивает вас использованием только тех типов событий, которые он определил. Вы можете определить свои собственные события для обработки по своему усмотрению.
Давайте посмотрим, как создать пользовательское событие, которое генерируется каждые несколько секунд. Вы можете создать собственное событие, назвав его:
78 # Create the screen object
79 # The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT
80 screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
81
82 # Create a custom event for adding a new enemy
83 ADDENEMY = pygame.USEREVENT + 1
84 pygame.time.set_timer(ADDENEMY, 250)
85
86 # Instantiate player. Right now, this is just a rectangle.
87 player = Player()
pygame
определяет события внутри как целые числа, поэтому вам нужно определить новое событие с уникальным целым числом. Последнее событиеpygame
резервов называетсяUSEREVENT
, поэтому определениеADDENEMY = pygame.USEREVENT + 1
в строке 83 гарантирует его уникальность.
Затем вам нужно регулярно вставлять это новое событие в очередь на протяжении всей игры. Вот тут и пригодится модульtime
. Строка 84 запускает новое событиеADDENEMY
каждые 250 миллисекунд или четыре раза в секунду. Вы вызываете.set_timer()
вне игрового цикла, поскольку вам нужен только один таймер, но он будет срабатывать на протяжении всей игры.
Добавьте код для обработки вашего нового события:
100 # Main loop
101 while running:
102 # Look at every event in the queue
103 for event in pygame.event.get():
104 # Did the user hit a key?
105 if event.type == KEYDOWN:
106 # Was it the Escape key? If so, stop the loop.
107 if event.key == K_ESCAPE:
108 running = False
109
110 # Did the user click the window close button? If so, stop the loop.
111 elif event.type == QUIT:
112 running = False
113
114 # Add a new enemy?
115 elif event.type == ADDENEMY:
116 # Create the new enemy and add it to sprite groups
117 new_enemy = Enemy()
118 enemies.add(new_enemy)
119 all_sprites.add(new_enemy)
120
121 # Get the set of keys pressed and check for user input
122 pressed_keys = pygame.key.get_pressed()
123 player.update(pressed_keys)
124
125 # Update enemy position
126 enemies.update()
Каждый раз, когда обработчик событий видит новое событиеADDENEMY
в строке 115, он создаетEnemy
и добавляет его кenemies
иall_sprites
. ПосколькуEnemy
находится вall_sprites
, он будет отрисовываться каждый кадр. Вам также необходимо вызватьenemies.update()
в строке 126, который обновляет все вenemies
, чтобы убедиться, что они перемещаются правильно:
Однако это не единственная причина, по которой существует группа только дляenemies
.
Обнаружение столкновений
Ваш игровой дизайн требует, чтобы игра заканчивалась всякий раз, когда враг сталкивается с игроком. Проверка на наличие столкновений является основной техникой программирования игры и обычно требует некоторой нетривиальной математики, чтобы определить, будут ли два спрайта перекрывать друг друга.
Здесь пригодится фреймворк вродеpygame
! Написание кода обнаружения столкновений утомительно, ноpygame
имеет МНОГОcollision detection methods, доступное для использования.
В этом руководстве вы будете использовать метод под названием.spritecollideany()
, который читается как «спрайт сталкивается с любым». Этот метод принимает в качестве параметровSprite
иGroup
. Он просматривает каждый объект вGroup
и проверяет, пересекается ли его.rect
с.rect
Sprite
. Если да, то возвращаетсяTrue
. В противном случае возвращаетсяFalse
. Это идеально подходит для этой игры, поскольку вам нужно проверить, не сталкивается ли отдельныйplayer
с одним изGroup
изenemies
.
Вот как это выглядит в коде:
130 # Draw all sprites
131 for entity in all_sprites:
132 screen.blit(entity.surf, entity.rect)
133
134 # Check if any enemies have collided with the player
135 if pygame.sprite.spritecollideany(player, enemies):
136 # If so, then remove the player and stop the loop
137 player.kill()
138 running = False
Строка 135 проверяет, столкнулся лиplayer
с каким-либо из объектов вenemies
. Если это так, то вызываетсяplayer.kill()
, чтобы удалить его из каждой группы, к которой он принадлежит. Поскольку визуализируются только объекты вall_sprites
,player
больше не будет отображаться. После того, как игрок был убит, вам также необходимо выйти из игры, поэтому вы устанавливаетеrunning = False
для выхода из игрового цикла в строке 138.
Теперь у вас есть основные элементы игры:
Теперь давайте немного нарядим его, сделаем его более играбельным и добавим некоторые расширенные возможности, которые помогут ему выделиться.
Спрайт Изображения
Хорошо, у вас есть игра, но давайте будем честными … Это некрасиво. Игрок и враги — это просто белые блоки на черном фоне. КогдаPong был новым, это было по последнему слову техники, но он больше не работает. Давайте заменим все эти скучные белые прямоугольники более прохладными изображениями, которые сделают игру настоящей игрой.
Ранее вы узнали, что изображения на диске могут быть загружены вSurface
с некоторой помощью модуляimage
. Для этого урока мы сделали небольшую струю для игрока и несколько ракет для врагов. Вы можете использовать этот рисунок, нарисовать свой собственный или загрузить несколькоfree game art assets для использования. Вы можете нажать на ссылку ниже, чтобы загрузить искусство, используемое в этом руководстве:
Изменение конструкторов объектов
Прежде чем использовать изображения для представления игрока и вражеских спрайтов, необходимо внести некоторые изменения в их конструкторы. Код ниже заменяет код, использованный ранее:
7 # Import pygame.locals for easier access to key coordinates
8 # Updated to conform to flake8 and black standards
9 # from pygame.locals import *
10 from pygame.locals import (
11 RLEACCEL,
12 K_UP,
13 K_DOWN,
14 K_LEFT,
15 K_RIGHT,
16 K_ESCAPE,
17 KEYDOWN,
18 QUIT,
19 )
20
21 # Define constants for the screen width and height
22 SCREEN_WIDTH = 800
23 SCREEN_HEIGHT = 600
24
25
26 # Define the Player object by extending pygame.sprite.Sprite
27 # Instead of a surface, use an image for a better-looking sprite
28 class Player(pygame.sprite.Sprite):
29 def __init__(self):
30 super(Player, self).__init__()
31 self.image = pygame.image.load("jet.png").convert()
32 self.image.set_colorkey((255, 255, 255), RLEACCEL)
33 self.rect = self.image.get_rect()
Давайте немного распакуем строку 31. pygame.image.load()
загружает образ с диска. Вы передаете ему путь к файлу. Он возвращаетSurface
, а вызов.convert()
оптимизируетSurface
, ускоряя будущие вызовы.blit()
.
В строке 32 используется.set_colorkey()
, чтобы указать, что цветpygame
будет отображаться как прозрачный. В этом случае вы выбираете белый, потому что это цвет фона изображения струи. КонстантаRLEACCEL — это необязательный параметр, который помогает быстрее отображатьpygame
на дисплеях без ускорения. Это добавляется к оператору импортаpygame.locals
в строке 11.
Больше ничего не нужно менять. Изображение по-прежнемуSurface
, только теперь на нем нарисовано изображение. Вы все еще используете его таким же образом.
Вот как выглядят похожие изменения вEnemy
:
59 # Define the enemy object by extending pygame.sprite.Sprite
60 # Instead of a surface, use an image for a better-looking sprite
61 class Enemy(pygame.sprite.Sprite):
62 def __init__(self):
63 super(Enemy, self).__init__()
64 self.surf = pygame.image.load("missile.png").convert()
65 self.surf.set_colorkey((255, 255, 255), RLEACCEL)
66 # The starting position is randomly generated, as is the speed
67 self.rect = self.surf.get_rect(
68 center=(
69 random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
70 random.randint(0, SCREEN_HEIGHT),
71 )
72 )
73 self.speed = random.randint(5, 20)
Запуск программы сейчас должен показать, что это та же игра, что и у вас раньше, только теперь вы добавили красивую графикуskins с изображениями. Но зачем останавливаться на том, чтобы заставить игрока и вражеских спрайтов выглядеть красиво? Давайте добавим несколько облаков, проходящих мимо, чтобы создать впечатление струи, летящей по небу.
Добавление фоновых изображений
Для фоновых облаков вы используете те же принципы, что и дляPlayer
иEnemy
:
-
Создайте класс
Cloud
. -
Добавьте к нему изображение облака.
-
Создайте метод
.update()
, который перемещаетcloud
в левую часть экрана. -
Создайте настраиваемое событие и обработчик для создания новых объектов
cloud
через заданный интервал времени. -
Добавьте вновь созданные объекты
cloud
в новыйGroup
с именемclouds
. -
Обновите и нарисуйте
clouds
в своем игровом цикле.
Вот как выглядитCloud
:
83 # Define the cloud object by extending pygame.sprite.Sprite
84 # Use an image for a better-looking sprite
85 class Cloud(pygame.sprite.Sprite):
86 def __init__(self):
87 super(Cloud, self).__init__()
88 self.surf = pygame.image.load("cloud.png").convert()
89 self.surf.set_colorkey((0, 0, 0), RLEACCEL)
90 # The starting position is randomly generated
91 self.rect = self.surf.get_rect(
92 center=(
93 random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
94 random.randint(0, SCREEN_HEIGHT),
95 )
96
97 # Move the cloud based on a constant speed
98 # Remove the cloud when it passes the left edge of the screen
99 def update(self):
100 self.rect.move_ip(-5, 0)
101 if self.rect.right < 0:
102 self.kill()
Это все должно выглядеть очень знакомым. Это почти то же самое, что иEnemy
.
Чтобы облака появлялись через определенные промежутки времени, вы будете использовать код создания события, аналогичный тому, который вы использовали для создания новых врагов. Поместите это прямо под событием создания врага:
116 # Create custom events for adding a new enemy and a cloud
117 ADDENEMY = pygame.USEREVENT + 1
118 pygame.time.set_timer(ADDENEMY, 250)
119 ADDCLOUD = pygame.USEREVENT + 2
120 pygame.time.set_timer(ADDCLOUD, 1000)
Это говорит о том, что нужно подождать 1000 миллисекунд или одну секунду перед созданием следующегоcloud
.
Затем создайте новыйGroup
для хранения каждого вновь созданногоcloud
:
125 # Create groups to hold enemy sprites, cloud sprites, and all sprites
126 # - enemies is used for collision detection and position updates
127 # - clouds is used for position updates
128 # - all_sprites is used for rendering
129 enemies = pygame.sprite.Group()
130 clouds = pygame.sprite.Group()
131 all_sprites = pygame.sprite.Group()
132 all_sprites.add(player)
Затем добавьте обработчик для нового событияADDCLOUD
в обработчике событий:
137 # Main loop
138 while running:
139 # Look at every event in the queue
140 for event in pygame.event.get():
141 # Did the user hit a key?
142 if event.type == KEYDOWN:
143 # Was it the Escape key? If so, then stop the loop.
144 if event.key == K_ESCAPE:
145 running = False
146
147 # Did the user click the window close button? If so, stop the loop.
148 elif event.type == QUIT:
149 running = False
150
151 # Add a new enemy?
152 elif event.type == ADDENEMY:
153 # Create the new enemy and add it to sprite groups
154 new_enemy = Enemy()
155 enemies.add(new_enemy)
156 all_sprites.add(new_enemy)
157
158 # Add a new cloud?
159 elif event.type == ADDCLOUD:
160 # Create the new cloud and add it to sprite groups
161 new_cloud = Cloud()
162 clouds.add(new_cloud)
163 all_sprites.add(new_cloud)
Наконец, убедитесь, чтоclouds
обновляются каждый кадр:
167 # Update the position of enemies and clouds
168 enemies.update()
169 clouds.update()
170
171 # Fill the screen with sky blue
172 screen.fill((135, 206, 250))
Строка 172 обновляет исходныйscreen.fill()
, чтобы заполнить экран приятным небесно-голубым цветом. Вы можете изменить этот цвет на что-то другое. Может быть, вы хотите инопланетный мир с пурпурным небом, ядовитую пустошь в неоново-зеленом или поверхность Марса в красном!
Обратите внимание, что каждый новыйCloud
иEnemy
добавляется кall_sprites
, а также кclouds
иenemies
. Это сделано потому, что каждая группа используется для отдельной цели:
-
Rendering выполняется с использованием
all_sprites
. -
Position updates выполняется с использованием
clouds
иenemies
. -
Collision detection выполняется с использованием
enemies
.
Вы создаете несколько групп, так что вы можете изменить способ перемещения или поведения спрайтов, не влияя на движение или поведение других спрайтов.
Скорость игры
Во время тестирования игры вы могли заметить, что враги двигаются немного быстро. Если нет, то это нормально, так как разные машины будут видеть разные результаты на этом этапе.
Причина этого в том, что игровой цикл обрабатывает кадры настолько быстро, насколько позволяет процессор и среда. Поскольку все спрайты перемещаются один раз за кадр, они могут перемещаться сотни раз в секунду. Количество кадров, обрабатываемых каждую секунду, называетсяframe rate, и правильное определение этого значения — разница между играбельной игрой и той, которую можно забыть.
Как правило, вы хотите максимально высокую частоту кадров, но для этой игры вам нужно немного замедлить ее, чтобы игра стала играбельной. К счастью, модульtime
содержитClock
, который предназначен именно для этой цели.
ИспользованиеClock
для установки воспроизводимой частоты кадров требует всего двух строк кода. Первый создает новыйClock
перед началом игрового цикла:
106 # Setup the clock for a decent framerate
107 clock = pygame.time.Clock()
Второй вызывает.tick()
, чтобы сообщитьpygame
, что программа достигла конца кадра:
188 # Flip everything to the display
189 pygame.display.flip()
190
191 # Ensure program maintains a rate of 30 frames per second
192 clock.tick(30)
Аргумент, переданный.tick()
, устанавливает желаемую частоту кадров. Для этого.tick()
вычисляет количество миллисекунд, которое должен занять каждый кадр, на основе желаемой частоты кадров. Затем он сравнивает это число с количеством миллисекунд, прошедших с момента последнего вызова.tick()
. Если прошло недостаточно времени,.tick()
задерживает обработку, чтобы гарантировать, что она никогда не превысит указанную частоту кадров.
Переход на меньшую частоту кадров приведет к увеличению времени в каждом кадре для вычислений, в то время как большая частота кадров обеспечивает более плавный (и, возможно, более быстрый) игровой процесс:
Поиграйте с этим номером, чтобы увидеть, что вам больше нравится!
Звуковые эффекты
Пока что вы сосредоточены на игровом процессе и визуальных аспектах вашей игры. Теперь давайте рассмотрим, как придать вашей игре слуховой вкус. pygame
предоставляетmixer
для обработки всех действий, связанных со звуком. Вы будете использовать классы и методы этого модуля для обеспечения фоновой музыки и звуковых эффектов для различных действий.
Названиеmixer
относится к тому факту, что модуль смешивает различные звуки в единое целое. Используя подмодульmusic
, вы можете передавать отдельные звуковые файлы в различных форматах, таких какMP3,Ogg иMod. Вы также можете использоватьSound
для удержания одного звукового эффекта для воспроизведения в форматах Ogg илиuncompressed WAV. Все воспроизведение происходит в фоновом режиме, поэтому, когда вы проигрываетеSound
, метод немедленно возвращается по мере воспроизведения звука.
Note:pygame
documentation указывает, что поддержка MP3 ограничена, а неподдерживаемые форматы могут вызвать сбои системы. Звуки, упомянутые в этой статье, были протестированы, и мы рекомендуем тщательно протестировать любые звуки перед выпуском вашей игры.
Как и в большинстве случаевpygame
, использованиеmixer
начинается с этапа инициализации. К счастью, этим уже занимаетсяpygame.init()
. Вам нужно только вызватьpygame.mixer.init()
, если вы хотите изменить значения по умолчанию:
106 # Setup for sounds. Defaults are good.
107 pygame.mixer.init()
108
109 # Initialize pygame
110 pygame.init()
111
112 # Set up the clock for a decent framerate
113 clock = pygame.time.Clock()
pygame.mixer.init()
принимаетa number of arguments, но в большинстве случаев значения по умолчанию работают нормально. Обратите внимание: если вы хотите изменить значения по умолчанию, вам нужно вызватьpygame.mixer.init()
перед вызовомpygame.init()
. В противном случае значения по умолчанию будут действовать независимо от ваших изменений.
После инициализации системы вы можете настроить свои звуки и фоновую музыку:
135 # Load and play background music
136 # Sound source: http://ccmixter.org/files/Apoxode/59262
137 # License: https://creativecommons.org/licenses/by/3.0/
138 pygame.mixer.music.load("Apoxode_-_Electric_1.mp3")
139 pygame.mixer.music.play(loops=-1)
140
141 # Load all sound files
142 # Sound sources: Jon Fincher
143 move_up_sound = pygame.mixer.Sound("Rising_putter.ogg")
144 move_down_sound = pygame.mixer.Sound("Falling_putter.ogg")
145 collision_sound = pygame.mixer.Sound("Collision.ogg")
Lines 138 and 139 загружает фоновый звуковой клип и начинает его воспроизведение. Вы можете указать звуковому клипу зацикливаться и никогда не заканчиваться, задав именованный параметрloops=-1
.
Lines 143 to 145 загружает три звука, которые вы будете использовать для различных звуковых эффектов. Первые два — это повышающиеся и понижающиеся звуки, которые воспроизводятся, когда игрок движется вверх или вниз. Последний звук используется всякий раз, когда происходит столкновение. Вы также можете добавить другие звуки, такие как звук, когда создаетсяEnemy
, или последний звук, когда игра заканчивается.
Итак, как вы используете звуковые эффекты? Вы хотите воспроизвести каждый звук, когда происходит определенное событие. Например, когда корабль движется вверх, вы хотите сыгратьmove_up_sound
. Следовательно, вы добавляете вызов.play()
всякий раз, когда обрабатываете это событие. В дизайне это означает добавление следующих вызовов к.update()
дляPlayer
:
26 # Define the Player object by extending pygame.sprite.Sprite
27 # Instead of a surface, use an image for a better-looking sprite
28 class Player(pygame.sprite.Sprite):
29 def __init__(self):
30 super(Player, self).__init__()
31 self.surf = pygame.image.load("jet.png").convert()
32 self.surf.set_colorkey((255, 255, 255), RLEACCEL)
33 self.rect = self.surf.get_rect()
34
35 # Move the sprite based on keypresses
36 def update(self, pressed_keys):
37 if pressed_keys[K_UP]:
38 self.rect.move_ip(0, -5)
39 move_up_sound.play()
40 if pressed_keys[K_DOWN]:
41 self.rect.move_ip(0, 5)
42 move_down_sound.play()
Для столкновения между игроком и врагом вы воспроизводите звук, когда обнаруживаются столкновения:
201 # Check if any enemies have collided with the player
202 if pygame.sprite.spritecollideany(player, enemies):
203 # If so, then remove the player
204 player.kill()
205
206 # Stop any moving sounds and play the collision sound
207 move_up_sound.stop()
208 move_down_sound.stop()
209 collision_sound.play()
210
211 # Stop the loop
212 running = False
Здесь вы сначала останавливаете любые другие звуковые эффекты, потому что при столкновении игрок больше не движется. Затем вы проигрываете звук столкновения и продолжаете исполнение оттуда.
Наконец, когда игра окончена, все звуки должны прекратиться. Это верно независимо от того, заканчивается ли игра из-за столкновения или пользователь выходит вручную. Для этого добавьте следующие строки в конце программы после цикла:
220 # All done! Stop and quit the mixer.
221 pygame.mixer.music.stop()
222 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
! Посмотритеofficial pygame
documentation для получения полного списка доступных модулей и классов.
Вы можете найти весь код, графику и звуковые файлы для этой статьи, нажав на ссылку ниже:
Не стесняйтесь оставлять комментарии ниже. Счастливого Pythoning!