PyGame: учебник по программированию игр на Python
Фон и настройка
pygame
– это оболочка Python для SDL library, что означает Simple DirectMedia Layer. SDL обеспечивает кроссплатформенный доступ к базовым компонентам мультимедийного оборудования вашей системы, таким как звук, видео, мышь, клавиатура и джойстик. pygame
начал свою жизнь как замена остановившемуся PySDL project. Кросс-платформенный характер как SDL, так иpygame
означает, что вы можете писать игры и мультимедийные программы Python для любой платформы, которая их поддерживает!
Чтобы установитьpygame
на вашу платформу, используйте соответствующую команду pip
:
$ pip install pygame
Вы можете проверить установку, загрузив один из примеров, поставляемых с библиотекой:
$ python3 -m pygame.examples.aliens
Если появляется окно игры, значитpygame
установлен правильно! Если у вас возникнут проблемы, то Getting Started guide описывает некоторые известные проблемы и предостережения для всех платформ.
Основная программа PyGame
Когда вы запустите эту программу, вы увидите окно, которое выглядит так:
Давайте разберем этот код, раздел за разделом:
- 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
также включает несколько Python classes, которые инкапсулируют концепции, не зависящие от оборудования. Один из них –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
подключать свои абстракции к вашему конкретному оборудованию:
Библиотека pygame
определяет многие вещи помимо модулей и классов. Он также определяет некоторые local constants для таких вещей, как нажатия клавиш, движения мыши и атрибуты отображения. Вы ссылаетесь на эти константы, используя синтаксис pygame.<CONSTANT>
. Импортируя определенные константы изpygame.locals
, вы можете вместо этого использовать синтаксис <CONSTANT>
. Это сэкономит вам несколько нажатий клавиш и улучшит общую читабельность.
Настройка дисплея
Теперь вам нужно что-то нарисовать! Создайте screen как общий холст:
Вы создаете экран для использования, вызывая 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()
. Затем вы просматриваете этот список, проверяете каждый тип события и отвечаете соответственно:
Давайте внимательнее посмотрим на этот игровой цикл:
- 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
отдельно от экрана дисплея. Давайте посмотрим, как это работает:
После заполнения экрана белым цветом в строке 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
на экране:
Вызов.blit()
в строке 55 принимает два аргумента:
Surface
для рисования- Место для его рисования на источнике
Surface
Координаты (SCREEN_WIDTH/2, SCREEN_HEIGHT/2)
говорят вашей программе разместить surf
точно в центре экрана, но это не совсем так:
Причина, по которой изображение выглядит не по центру, заключается в том, что .blit()
помещает top-left corner изsurf
в указанное место. Если вы хотите, чтобы surf
был центрирован, вам нужно будет выполнить некоторые вычисления, чтобы сместить его вверх и влево. Вы можете сделать это, вычтя ширину и высоту surf
из ширины и высоты экрана, разделив каждую на 2, чтобы определить местонахождение центра, а затем передав эти числа в качестве аргументов в screen.blit()
:
Обратите внимание на вызов pygame.display.flip()
после вызова blit()
. Это обновляет весь экран со всем, что было нарисовано с момента последнего переворота. Без вызова .flip()
ничего не отображается.
Спрайты
В вашем игровом дизайне игрок начинает слева, а справа появляются препятствия. Вы можете представить все препятствия с помощью объектов Surface
, чтобы все было проще рисовать, но как узнать, где их нарисовать? Как узнать, столкнулось ли препятствие с игроком? Что происходит, когда препятствие летит за пределы экрана? Что делать, если вы хотите нарисовать фоновые изображения, которые также перемещаются? Что если вы хотите, чтобы ваши изображения были анимированными? Вы можете справиться со всеми этими и другими ситуациями с помощью sprites.
С точки зрения программирования sprite – это 2D-представление чего-либо на экране. По сути, это картина. pygame
предоставляет Sprite
class, который предназначен для хранения одного или нескольких графических представлений любого игрового объекта, который вы хотите отобразить на экране. Чтобы использовать его, вы создаете новый класс, расширяющий Sprite
. Это позволяет использовать его встроенные методы.
игроки
Вот как вы используете объекты Sprite
в текущей игре для определения игрока. Вставьте этот код после строки 18:
Сначала вы определяете Player
, расширяя pygame.sprite.Sprite
в строке 22. Затем.__init__()
использует.super()
для вызова метода.__init__()
дляSprite
.
Для получения дополнительной информации о том, почему это необходимо, вы можете прочитать Supercharge Your Classes With Python super().
Затем вы определяете и инициализируете .surf
, чтобы удерживать изображение для отображения, которое в настоящее время представляет собой белое поле. Вы также определяете и инициализируете .rect
, который вы будете использовать для рисования игрока позже. Чтобы использовать этот новый класс, вам нужно создать новый объект и изменить код для рисования. Разверните блок кода ниже, чтобы увидеть все это вместе:
Запустите этот код. Вы увидите белый прямоугольник примерно в середине экрана:
Как вы думаете, что произойдет, если вы измените строку 59 на screen.blit(player.surf, player.rect)
?
Попробуйте и посмотрите:
Когда вы передаете Rect
в.blit()
, он использует координаты верхнего левого угла для рисования поверхности. Вы будете использовать это позже, чтобы заставить вашего игрока двигаться!
Пользовательский ввод
До сих пор вы научились настраивать pygame
и рисовать объекты на экране. Теперь начинается самое интересное! Вы сделаете плеер управляемым с помощью клавиатуры.
Ранее вы видели, что pygame.event.get()
возвращает список событий в очереди событий, которую вы просматриваете на предмет типов событий KEYDOWN
. Ну, это не единственный способ читать нажатия клавиш . pygame
также предоставляет pygame.event.get_pressed()
, который возвращает dictionary, содержащий все текущие события KEYDOWN
в очереди.
Поместите это в свой игровой цикл сразу после цикла обработки событий. Это возвращает словарь, содержащий клавиши, нажимаемые в начале каждого кадра:
Затем вы пишете метод в Player
для приема этого словаря. Это будет определять поведение спрайта на основе нажатых клавиш. Вот как это может выглядеть:
K_UP
,K_DOWN
,K_LEFT
иK_RIGHT
соответствуют клавишам со стрелками на клавиатуре. Если словарная запись для этого ключа –True
, значит, этот ключ нажат, и вы перемещаете игрока .rect
в правильном направлении. Здесь вы используете .move_ip()
, что означает move in place, для перемещения текущего Rect
.
Затем вы можете вызывать .update()
каждый кадр, чтобы перемещать спрайт игрока в ответ на нажатия клавиш. Добавьте этот вызов сразу после вызова .get_pressed()
:
Теперь вы можете перемещать прямоугольник игрока по экрану с помощью клавиш со стрелками:
Вы можете заметить две небольшие проблемы:
- Прямоугольник игрока может двигаться очень быстро, если удерживать клавишу нажатой. Вы поработаете над этим позже.
- Прямоугольник игрока может сдвинуться с экрана. Давайте решим это сейчас.
Чтобы игрок оставался на экране, вам нужно добавить некоторую логику, чтобы определять, собирается ли rect
уйти с экрана. Для этого вы проверяете, не переместились ли координаты rect
за пределы экрана. Если это так, то вы даете команду программе переместить ее обратно к краю:
Здесь вместо использования .move()
вы просто меняете соответствующие координаты .top
,.bottom
,.left
или.right
напрямую. Проверьте это, и вы увидите, что прямоугольник игрока больше не может сдвинуться с экрана.
Теперь давайте добавим несколько врагов!
враги
Что за игра без врагов? Вы будете использовать методы, которые вы уже изучили, чтобы создать базовый класс врагов, а затем создадите множество из них, чтобы ваш игрок избегал их. Сначала импортируйте библиотеку random
:
Затем создайте новый класс спрайтов с именем Enemy
, следуя тому же шаблону, который вы использовали для Player
:
Между 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
.
Вот как это выглядит в коде:
Когда вы вызываете .kill()
,Sprite
удаляется из каждого Group
, которому он принадлежит. Это также удаляет ссылки на Sprite
, что позволяет сборщику мусора Python при необходимости освобождать память.
Теперь, когда у вас есть группа all_sprites
, вы можете изменить способ рисования объектов. Вместо вызова .blit()
только для Player
, вы можете перебирать все в all_sprites
:
Теперь все, что помещено в all_sprites
, будет отрисовываться с каждым кадром, будь то враг или игрок.
Есть только одна проблема … У тебя нет врагов! Вы можете создать кучу врагов в начале игры, но игра быстро станет скучной, когда все они покинут экран через несколько секунд. Вместо этого давайте рассмотрим, как поддерживать постоянный приток врагов в ходе игры.
Пользовательские события
Дизайн призывает врагов появляться через равные промежутки времени. Это означает, что через заданные интервалы вам нужно сделать две вещи:
- Создайте новый
Enemy
. - Добавьте его в
all_sprites
иenemies
.
У вас уже есть код, который обрабатывает случайные события. Цикл обработки событий предназначен для поиска случайных событий, происходящих в каждом кадре, и надлежащего обращения с ними. К счастью, pygame
не ограничивает вас использованием только тех типов событий, которые он определил. Вы можете определить свои собственные события для обработки по своему усмотрению.
Давайте посмотрим, как создать пользовательское событие, которое генерируется каждые несколько секунд. Вы можете создать собственное событие, назвав его:
pygame
определяет события внутри как целые числа, поэтому вам нужно определить новое событие с уникальным целым числом. Последнее событие pygame
резервов называется USEREVENT
, поэтому определение ADDENEMY = pygame.USEREVENT + 1
в строке 83 гарантирует его уникальность.
Затем вам нужно регулярно вставлять это новое событие в очередь на протяжении всей игры. Вот тут и пригодится модуль time
. Строка 84 запускает новое событие ADDENEMY
каждые 250 миллисекунд или четыре раза в секунду. Вы вызываете .set_timer()
вне игрового цикла, поскольку вам нужен только один таймер, но он будет срабатывать на протяжении всей игры.
Добавьте код для обработки вашего нового события:
Каждый раз, когда обработчик событий видит новое событие ADDENEMY
в строке 115, он создает Enemy
и добавляет его кenemies
иall_sprites
. Поскольку Enemy
находится вall_sprites
, он будет отрисовываться каждый кадр. Вам также необходимо вызвать enemies.update()
в строке 126, который обновляет все в enemies
, чтобы убедиться, что они перемещаются правильно:
Однако это не единственная причина, по которой существует группа только дляenemies
.
Обнаружение столкновений
Ваш игровой дизайн требует, чтобы игра заканчивалась всякий раз, когда враг сталкивается с игроком. Проверка на наличие столкновений является основной техникой программирования игры и обычно требует некоторой нетривиальной математики, чтобы определить, будут ли два спрайта перекрывать друг друга.
Здесь пригодится фреймворк вроде pygame
! Написание кода обнаружения столкновений утомительно, но pygame
имеет МНОГО collision detection methods, доступное для использования.
В этом руководстве вы будете использовать метод под названием .spritecollideany()
, который читается как «спрайт сталкивается с любым». Этот метод принимает в качестве параметров Sprite
иGroup
. Он просматривает каждый объект вGroup
и проверяет, пересекается ли его .rect
с.rectSprite
. Если да, то возвращается True
. В противном случае возвращается False
. Это идеально подходит для этой игры, поскольку вам нужно проверить, не сталкивается ли отдельный player
с одним из Group
изenemies
.
Вот как это выглядит в коде:
Строка 135 проверяет, столкнулся ли player
с каким-либо из объектов в enemies
. Если это так, то вызывается player.kill()
, чтобы удалить его из каждой группы, к которой он принадлежит. Поскольку визуализируются только объекты в all_sprites
,player
больше не будет отображаться. После того, как игрок был убит, вам также необходимо выйти из игры, поэтому вы устанавливаете running = False
для выхода из игрового цикла в строке 138.
Теперь у вас есть основные элементы игры:
Теперь давайте немного нарядим его, сделаем его более играбельным и добавим некоторые расширенные возможности, которые помогут ему выделиться.
Спрайт Изображения
Хорошо, у вас есть игра, но давайте будем честными … Это некрасиво. Игрок и враги – это просто белые блоки на черном фоне. Когда Pong был новым, это было по последнему слову техники, но он больше не работает. Давайте заменим все эти скучные белые прямоугольники более прохладными изображениями, которые сделают игру настоящей игрой.
Ранее вы узнали, что изображения на диске могут быть загружены в Surface
с некоторой помощью модуля image
. Для этого урока мы сделали небольшую струю для игрока и несколько ракет для врагов. Вы можете использовать этот рисунок, нарисовать свой собственный или загрузить несколько free game art assets для использования. Вы можете нажать на ссылку ниже, чтобы загрузить искусство, используемое в этом руководстве:
Sample Code:Click here to download the source code for the PyGame sample project, используемый в этом руководстве.
Изменение конструкторов объектов
Прежде чем использовать изображения для представления игрока и вражеских спрайтов, необходимо внести некоторые изменения в их конструкторы. Код ниже заменяет код, использованный ранее:
Давайте немного распакуем строку 31. pygame.image.load()
загружает образ с диска. Вы передаете ему путь к файлу. Он возвращает Surface
, а вызов .convert()
оптимизирует Surface
, ускоряя будущие вызовы .blit()
.
В строке 32 используется .set_colorkey()
, чтобы указать, что цвет pygame
будет отображаться как прозрачный. В этом случае вы выбираете белый, потому что это цвет фона изображения струи. Константа RLEACCEL – это необязательный параметр, который помогает быстрее отображать pygame
на дисплеях без ускорения. Это добавляется к оператору импорта pygame.locals
в строке 11.
Больше ничего не нужно менять. Изображение по-прежнему Surface
, только теперь на нем нарисовано изображение. Вы все еще используете его таким же образом.
Вот как выглядят похожие изменения в Enemy
:
Запуск программы сейчас должен показать, что это та же игра, что и у вас раньше, только теперь вы добавили красивую графику skins с изображениями. Но зачем останавливаться на том, чтобы заставить игрока и вражеских спрайтов выглядеть красиво? Давайте добавим несколько облаков, проходящих мимо, чтобы создать впечатление струи, летящей по небу.
Добавление фоновых изображений
Для фоновых облаков вы используете те же принципы, что и для Player
иEnemy
:
- Создайте класс
Cloud
. - Добавьте к нему изображение облака.
- Создайте метод
.update()
, который перемещаетcloud
в левую часть экрана. - Создайте настраиваемое событие и обработчик для создания новых объектов
cloud
через заданный интервал времени. - Добавьте вновь созданные объекты
cloud
в новыйGroup
с именемclouds
. - Обновите и нарисуйте
clouds
в своем игровом цикле.
Вот как выглядит Cloud
:
Это все должно выглядеть очень знакомым. Это почти то же самое, что и Enemy
.
Чтобы облака появлялись через определенные промежутки времени, вы будете использовать код создания события, аналогичный тому, который вы использовали для создания новых врагов. Поместите это прямо под событием создания врага:
Это говорит о том, что нужно подождать 1000 миллисекунд или одну секунду перед созданием следующего cloud
.
Затем создайте новыйGroup
для хранения каждого вновь созданного cloud
:
Затем добавьте обработчик для нового события ADDCLOUD
в обработчике событий:
Наконец, убедитесь, что clouds
обновляются каждый кадр:
Строка 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
перед началом игрового цикла:
Второй вызывает .tick()
, чтобы сообщить pygame
, что программа достигла конца кадра:
Аргумент, переданный .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()
, если вы хотите изменить значения по умолчанию:
pygame.mixer.init()
принимает a number of arguments, но в большинстве случаев значения по умолчанию работают нормально. Обратите внимание: если вы хотите изменить значения по умолчанию, вам нужно вызвать pygame.mixer.init()
перед вызовом pygame.init()
. В противном случае значения по умолчанию будут действовать независимо от ваших изменений.
После инициализации системы вы можете настроить свои звуки и фоновую музыку:
Lines 138 and 139 загружает фоновый звуковой клип и начинает его воспроизведение. Вы можете указать звуковому клипу зацикливаться и никогда не заканчиваться, задав именованный параметр loops=-1
.
Lines 143 to 145 загружает три звука, которые вы будете использовать для различных звуковых эффектов. Первые два – это повышающиеся и понижающиеся звуки, которые воспроизводятся, когда игрок движется вверх или вниз. Последний звук используется всякий раз, когда происходит столкновение. Вы также можете добавить другие звуки, такие как звук, когда создаетсяEnemy
, или последний звук, когда игра заканчивается.
Итак, как вы используете звуковые эффекты? Вы хотите воспроизвести каждый звук, когда происходит определенное событие. Например, когда корабль движется вверх, вы хотите сыграть move_up_sound
. Следовательно, вы добавляете вызов .play()
всякий раз, когда обрабатываете это событие. В дизайне это означает добавление следующих вызовов к .update()
для Player
:
Для столкновения между игроком и врагом вы воспроизводите звук, когда обнаруживаются столкновения:
Здесь вы сначала останавливаете любые другие звуковые эффекты, потому что при столкновении игрок больше не движется. Затем вы проигрываете звук столкновения и продолжаете исполнение оттуда.
Наконец, когда игра окончена, все звуки должны прекратиться. Это верно независимо от того, заканчивается ли игра из-за столкновения или пользователь выходит вручную. Для этого добавьте следующие строки в конце программы после цикла:
Технически эти последние несколько строк не требуются, так как программа заканчивается сразу после этого. Однако если позже вы решите добавить в игру вводный экран или экран выхода, то после завершения игры может появиться больше кода.
Это оно! Проверьте это снова, и вы должны увидеть что-то вроде этого:
Примечание к источникам
Возможно, вы заметили комментарий в строках 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 для получения полного списка доступных модулей и классов.
Вы можете найти весь код, графику и звуковые файлы для этой статьи, нажав на ссылку ниже:
Clone Repo: Click here to clone the repo you’ll use, чтобы узнать, как использовать PyGame в этом руководстве.
Основные модули пакета Pygame
Модуль | Назначение |
---|---|
pygame.cdrom | Доступ к CD-приводам и управление ими |
pygame.cursors | Загружает изображения курсора |
pygame.display | Доступ к дисплею |
pygame.draw | Рисует фигуры, линии и точки |
pygame.event | Управление внешними событиями |
pygame.font | Использует системные шрифты |
pygame.image | Загружает и сохраняет изображение |
pygame.joystick | Использует джойстики и аналогичные устройства |
pygame.key | Считывает нажатия клавиш с клавиатуры |
pygame.mixer | Загружает и воспроизводит мелодии |
pygame.mouse | Управляет мышью |
pygame.movie | Воспроизведение видеофайлов |
pygame.music | Работает с музыкой и потоковым аудио |
pygame.overlay | Доступ к расширенным видеоизображениям |
pygame | Содержит функции Pygame высокого уровня |
pygame.rect | Управляет прямоугольными областями |
pygame.sndarray | Манипулирует звуковыми данными |
pygame.sprite | Управление движущимися изображениями |
pygame.surface | Управляет изображениями и экраном |
pygame.surfarray | Манипулирует данными пикселей изображения |
pygame.time | модуль pygame для управления временем и частотой кадров |
pygame.transform | Изменение размера и перемещение изображений |
Рисование базовых элементов
модуль pygame.draw
pygame.draw.rect | нарисовать прямоугольную форму |
pygame.draw.polygon | фигуру с любым количеством сторон |
pygame.draw.circle | круг вокруг точки |
pygame.draw.ellipse | нарисовать круглую форму внутри прямоугольника |
pygame.draw.arc | нарисовать секцию эллипса |
pygame.draw.line | нарисовать сегмент прямой линии |
pygame.draw.lines | для рисования нескольких смежных отрезков |
pygame.draw.aaline | рисовать тонкую линию |
pygame.draw.aalines | нарисовать связанную последовательность сглаженных линий |
МЕТОДЫ РАБОТЫ С RECT
pygame.Rect.copy | Возвращает новый прямоугольник, имеющий ту же позицию и размер, что и оригинал. |
pygame.Rect.move | Возвращает новый прямоугольник, перемещаемый данным смещением. Аргументы x и y могут быть любым целочисленным значением, положительным или отрицательным. |
pygame.Rect.move_ip | То же, что и метод Rect.move (), но работает на месте. |
pygame.Rect.inflate | увеличивать или уменьшать размер прямоугольника, на месте |
pygame.Rect.inflate_ip | увеличивать или уменьшать размер прямоугольника, на месте |
pygame.Rect.clamp | перемещает прямоугольник внутри другого |
pygame.Rect.clamp_ip | перемещает прямоугольник внутри другого, на месте |
pygame.Rect.clip | обрезает прямоугольник внутри другого |
pygame.Rect.union | соединяет два прямоугольника в один |
pygame.Rect.union_ip | соединяет два прямоугольника в один, на месте |
pygame.Rect.unionall | объединение многих прямоугольников |
pygame.Rect.unionall_ip | объединение многих прямоугольников, на месте |
pygame.Rect.fit | изменить размер и переместить прямоугольник учмиывая соотношение сторон |
pygame.Rect.normalize | корректировать отрицательные размеры |
pygame.Rect.contains | проверить, находится ли один прямоугольник внутри другого |
pygame.Rect.collidepoint | проверить, находится ли точка внутри прямоугольника |
pygame.Rect.colliderect | тест, пересекаются ли два прямоугольника |
pygame.Rect.collidelist | проверить, пересекается ли хоть один прямоугольник в списке |
pygame.Rect.collidelistall | пересекаются ли все прямоугольники в списке |
pygame.Rect.collidedict | проверить, если один прямоугольник в словаре пересекается |
pygame.Rect.collidedictall | пересекаются ли все прямоугольники в словаре |
Объект event
Модуль pygame.event для обработки очереди событий
pygame.event.pump | Если вы не используете другие функции событий в своей игре, вы должны вызвать pygame.event.pump (), чтобы позволить pygame обрабатывать внутренние действия |
pygame.event.get | получает события из очереди |
pygame.event.poll | получить одно событие из очереди |
pygame.event.wait | ждёт одиночного события из очереди |
pygame.event.peek | проверить, ждут ли очереди события определённого типа |
pygame.event.clear | удалить все события из очереди |
pygame.event.event_name | возвращает имя для типа события. Строка находится в стиле WordCap |
pygame.event.set_blocked | проверяет, какие события не разрешены в очереди |
pygame.event.set_allowed | проверяет, какие события разрешены в очереди |
pygame.event.get_blocked | проверить, заблокирован ли тип события из очереди |
pygame.event.set_grab | проверяет совместное использование устройств ввода с другими приложениями |
pygame.event.get_grab | проверить, работает ли программа на устройствах ввода данных |
pygame.event.post | поместить новое событие в очередь |
pygame.event.Event | создать новый объект события |
pygame.event.EventType | Объект Python, представляющий событие SDL. Экземпляры пользовательских событий создаются с вызовом функции Event. Тип EventType не может быть напрямую вызван. Экземпляры EventType поддерживают назначение и удаление атрибутов. |
Pygame отслеживает все сообщения о событиях через очередь событий. Процедуры в этом модуле помогают управлять этой очередью событий. Входная очередь сильно зависит от модуля отображения (display) pygame. Если дисплей не был инициализирован и видеорежим не установлен, очередь событий не будет работать.
Существует множество способов доступа к очереди событий. Просто проверять существование событий, захватывать их непосредственно из стека.
Мышь
Модуль pygame.mouse для работы с мышью
pygame.mouse.get_pressed | получить состояние кнопок мыши |
pygame.mouse.get_pos | получить позицию курсора мыши |
pygame.mouse.get_rel | получить количество движений мыши |
pygame.mouse.set_pos | установить позицию курсора мыши |
pygame.mouse.set_visible | скрыть или показать курсор мыши |
pygame.mouse.get_focused | проверяет, принимает ли дисплей ввод мыши |
pygame.mouse.set_cursor | установить изображение для курсора мыши |
pygame.mouse.get_cursor | получить изображение для курсора мыши |
Функции мыши можно использовать для получения текущего состояния устройства мышь. Эти функции также могут изменять курсор мыши.