Разрабатываем игру на PyGame
Введение
Всем привет! Сегодня мы с вами попробруем на практике самую популярную библиотеку для создания игр на питоне - PyGame. С ее помощью мы создадим аналог классической аркады "астероиды".
Для этого мы научимся:
- создавать игровое окно и отображать фоновое изображение;
- создавать персонажа и реагировать на нажатия клавиш;
- добавлять астероиды, двигать их и реагировать на столкновения;
- выводить текст и анимацию взрывов.
Для начала работы нам понадобится установить питон (с сайта python.org) и библиотеку pygame. Для этого после установки питона нужно открыть терминал (командную строку) и выполнить команду pip install pygame
.
Если все прошло хорошо, можно открывать ваше любимое IDE (например, встроенный в питон IDLE) и приступать к работе. Лучше выделить для нашего проекта отдельную папку, внутри которой мы будем создавать скрипты и сохранять изображения.
Создаем игровое окно
Первое, что потребуется сделать - создать пустое игровое окно и сделать игровой цикл.
Игровой цикл - это сердце любой игры, в нем все игровые объекты меняют свои координаты и перерисовываются. Одна итерация такого цикла - это и есть кадр (известный вам по термину FPS - количество кадров секунду).
При создании окна нам потребуется указать его размер и название, и написать простой цикл обработки событий. Событие - это информация от операционной системы, например, о нажатиях клавиш на клавиатуре или мыши, необходимости перерисовать окно или нажатии на "крестик".
import pygame
pygame.init()
# создаем окно размера 800 на 600
screen = pygame.display.set_mode((800, 600))
# указываем название
pygame.display.set_caption("Asteroids")
# игровой цикл
while True:
# обрабатываем события
for e in pygame.event.get():
# если нажали на крестик
if e.type == pygame.QUIT:
# закрыть окно
raise SystemExit("QUIT")
# перерисовать окно
pygame.display.update()
Добавляем фоновое изображение
Теперь добавим фоновое изображение. Для этого нам как минимум понадобится картинка, ее можно поискать в интернете или взять нашу.
Изображение в примере должно лежать в папке с нашим скриптом и называться sky.jpg
.
import pygame
# импортируем функцию для масштабирования картинок
from pygame.transform import scale
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Asteroids")
# загружаем картинку из папки
big_sky = pygame.image.load("sky.jpg")
# масштабируем картинку под размер экрана
sky = scale(big_sky, (800, 600))
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
raise SystemExit("QUIT")
# рисуем картинку с небом на экране
screen.blit(sky, (0, 0))
pygame.display.update()
Добавляем игрока
Теперь добавим космический корабль. В PyGame, для любых перемещающихся объектов, которые могут взаимодействовать с другими удобно описать собственный класс, который будет наследоваться от встроенного класса Sprite
. Спрайты можно рисовать, двигать, уничтожать и удобно объединять в группы.
Класс - это описание некоторой сущности, по сути - чертеж объекта. В нем описывается, какие свойства будут у объекта и какие действия он будет уметь совершать.
Давайте попытаемся описать класс для космического корабля и нарисовать его. В классе нам потребуется описать конструктор (для инициализации стартовых параметров), функции для рисования и перемещения корабля. Кстати, нам снова понадобится прозрачная картинка для корабля, можно поискать по запросу starship 2d asset или взять нашу.
import pygame
from pygame.transform import scale
# описываем класс "космический корабль", расширяющий встроенный класс Sprite
class Spaceship(pygame.sprite.Sprite):
# конструктор - функция, в которую мы передаем начальные координаты
def __init__(self, x, y):
# инициализируем спрайт
pygame.sprite.Sprite.__init__(self)
# выбираем прямоугольную область размера 50 на 100
self.rect = pygame.Rect(x, y, 50, 100)
# загружаем картинку с кораблем
self.image = scale(pygame.image.load("ship.png"), (50, 100))
# задаем начальную скорость по оси x
self.xvel = 0
# функция рисования корабля
def draw(self, screen):
# рисуем корабль на экране на месте занимаемой им прямоугольной области
screen.blit(self.image, (self.rect.x, self.rect.y))
# функция перемещения, параметры - нажата ли стрелочки влево и вправо
def update(self, left, right):
# если нажата клавиша влево, уменьшаем скорость
if left:
self.xvel -= 3
# если нажата клавиша вправо, увеличиваем скорость
if right:
self.xvel += 3
# если ничего не нажато - тормозим
if not (left or right):
self.xvel = 0
# изменяем координаты на скорость
self.rect.x += self.xvel
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Asteroids")
sky = scale(pygame.image.load("sky.jpg"), (800, 600))
# создаем корабль в точке 400 400
ship = Spaceship(400, 400)
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
raise SystemExit("QUIT")
# рисуем небо
screen.blit(sky, (0, 0))
# просим корабль нарисоваться
ship.draw(screen)
pygame.display.update()
Настраиваем перемещение игрока
Несмотря на то, что мы описали функцию update
, наш корабль при нажатии на стрелочки все-равно не движется. Проблема в том, что мы это функцию нигде не вызываем!
Ниже мы опишем вызов этой функции при срабатывании системных событий нажатия на клавиши, и корабль должен начать перемещаться влево и вправо.
Попробуйте добавить возможность перемещения вверх и вних, а также ограничить возможность перемещения за границы.
import pygame
from pygame.transform import scale
class Spaceship(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.rect = pygame.Rect(x, y, 50, 100)
self.image = scale(pygame.image.load("ship.png"), (50, 100))
self.xvel = 0
def draw(self, screen):
screen.blit(self.image, (self.rect.x, self.rect.y))
def update(self, left, right):
if left:
self.xvel -= 3
if right:
self.xvel += 3
if not (left or right):
self.xvel = 0
self.rect.x += self.xvel
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Asteroids")
sky = scale(pygame.image.load("sky.jpg"), (800, 600))
# создаем корабль в точке 400 400
ship = Spaceship(400, 400)
# заведем переменные, чтобы помнить, какие клавиши нажаты
left = False
right = False
while True:
for e in pygame.event.get():
# если нажата клавиша - меняем переменную
if e.type == pygame.KEYDOWN and e.key == pygame.K_LEFT:
left = True
if e.type == pygame.KEYDOWN and e.key == pygame.K_RIGHT:
right = True
# если отпущена клавиша - меняем переменную
if e.type == pygame.KEYUP and e.key == pygame.K_LEFT:
left = False
if e.type == pygame.KEYUP and e.key == pygame.K_RIGHT:
right = False
if e.type == pygame.QUIT:
raise SystemExit("QUIT")
# рисуем небо
screen.blit(sky, (0, 0))
# перемещаем корабль
ship.update(left, right)
# просим корабль нарисоваться
ship.draw(screen)
pygame.display.update()
Астероиды
Теперь самое время добавить астероиды. Для них нам так же потребуется описать собственный класс, но в отличие от игрока, они не будут реагировать на клавиши, а всегда будут двигаться вниз.
Кстати, нам снова понадобится картинка, можно взять нашу.
import pygame
import random
from pygame.transform import scale
# создаем Астероид, расширяющий класс Спрайт
class Asteroid(pygame.sprite.Sprite):
# конструктор, в который передаются стартовые координаты
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
# загружаем картинку с астероидом и масштабируем под размер 50 на 50
self.image = scale(pygame.image.load("asteroid.png"), (50, 50))
# задаем прямоугольную область 50 на 50
self.rect = pygame.Rect(x, y, 50, 50)
# задаем скорость
self.yvel = 5
# функция рисования астероида
def draw(self, screen):
screen.blit(self.image, (self.rect.x, self.rect.y))
# функция перемещения астероида
def update(self):
self.rect.y += self.yvel
# если астероид за границей карты - он умирает
if self.rect.y > 900:
self.kill()
class Spaceship(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.rect = pygame.Rect(x, y, 50, 100)
self.image = scale(pygame.image.load("ship.png"), (50, 100))
self.xvel = 0
def draw(self, screen):
screen.blit(self.image, (self.rect.x, self.rect.y))
def update(self, left, right):
if left:
self.xvel -= 3
if right:
self.xvel += 3
if not (left or right):
self.xvel = 0
self.rect.x += self.xvel
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Asteroids")
sky = scale(pygame.image.load("sky.jpg"), (800, 600))
ship = Spaceship(400, 400)
left = False
right = False
# создадим группу спрайтов, в которой будут храниться все астероиды
asteroids = pygame.sprite.Group()
while True:
# с некоторой вероятность будем добавлять астероиды сверху экрана
if random.randint(1, 1000) > 900:
# выбираем координату по оси x
asteroid_x = random.randint(-100, 700)
# выбираем точку за верхней граничей экрана
asteroid_y = -100
# создаем астероид
asteroid = Asteroid(asteroid_x, asteroid_y)
# и добавляем его в группу
asteroids.add(asteroid)
for e in pygame.event.get():
if e.type == pygame.KEYDOWN and e.key == pygame.K_LEFT:
left = True
if e.type == pygame.KEYDOWN and e.key == pygame.K_RIGHT:
right = True
if e.type == pygame.KEYUP and e.key == pygame.K_LEFT:
left = False
if e.type == pygame.KEYUP and e.key == pygame.K_RIGHT:
right = False
if e.type == pygame.QUIT:
raise SystemExit("QUIT")
# рисуем небо
screen.blit(sky, (0, 0))
# перемещаем корабль
ship.update(left, right)
# просим корабль нарисоваться
ship.draw(screen)
# перемещаем и рисуем каждый астероид в группе
for asteroid in asteroids:
asteroid.update()
asteroid.draw(screen)
pygame.display.update()
Столкновения
Теперь попробуем добавить очки здоровья корабля, выводить их на экран и уменьшать при столкновениях с кораблем.
Попробуйте добавить закрытие игры или вывод надписи Game Over, когда жизнь опускается до нуля.
import pygame
import random
from pygame.transform import scale
class Asteroid(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = scale(pygame.image.load("asteroid.png"), (50, 50))
self.rect = pygame.Rect(x, y, 50, 50)
self.yvel = 5
def draw(self, screen):
screen.blit(self.image, (self.rect.x, self.rect.y))
def update(self):
self.rect.y += self.yvel
if self.rect.y > 900:
self.kill()
class Spaceship(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.rect = pygame.Rect(x, y, 50, 100)
self.image = scale(pygame.image.load("ship.png"), (50, 100))
self.xvel = 0
# добавим кораблю здоровье
self.life = 100
def draw(self, screen):
screen.blit(self.image, (self.rect.x, self.rect.y))
# добавим группу с астероидами в обновление координат корабля
def update(self, left, right, asteroids):
if left:
self.xvel -= 3
if right:
self.xvel += 3
if not (left or right):
self.xvel = 0
self.rect.x += self.xvel
# для каждого астероида
for asteroid in asteroids:
# если область, занимаемая астероидом пересекает область корабля
if self.rect.colliderect(asteroid.rect):
# уменьшаем жизнь
self.life -= 1
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Asteroids")
sky = scale(pygame.image.load("sky.jpg"), (800, 600))
ship = Spaceship(400, 400)
left = False
right = False
asteroids = pygame.sprite.Group()
# загрузим системный шрифт
pygame.font.init()
font = pygame.font.SysFont('Comic Sans MS', 30)
while True:
if random.randint(1, 1000) > 900:
asteroid_x = random.randint(-100, 700)
asteroid_y = -100
asteroid = Asteroid(asteroid_x, asteroid_y)
asteroids.add(asteroid)
for e in pygame.event.get():
if e.type == pygame.KEYDOWN and e.key == pygame.K_LEFT:
left = True
if e.type == pygame.KEYDOWN and e.key == pygame.K_RIGHT:
right = True
if e.type == pygame.KEYUP and e.key == pygame.K_LEFT:
left = False
if e.type == pygame.KEYUP and e.key == pygame.K_RIGHT:
right = False
if e.type == pygame.QUIT:
raise SystemExit("QUIT")
screen.blit(sky, (0, 0))
# добавим группу астероидов в параметры
ship.update(left, right, asteroids)
ship.draw(screen)
for asteroid in asteroids:
asteroid.update()
asteroid.draw(screen)
# выведем жизнь на экран белым цветом
life = font.render(f'HP: {ship.life}', False, (255, 255, 255))
screen.blit(life, (20, 20))
pygame.display.update()
Анимация столкновений
При столкновении астероида и корабля можно попробовать добавить анимацию взрывов на корабле.
Для этого можно взять раскадровку взрыва и разрезать ее на части, например, используя веб-сервис. Получившиеся кадры можно положить в папку explosion.
Кстати, полный код того, что у нас получилось всегда можно будет скачать на гитхабе.
import pygame
import random
from pygame.transform import scale
class Explosion(pygame.sprite.Sprite):
def __init__(self, x, y):
self.rect = pygame.Rect(x, y, 40, 40)
self.images = []
self.index = 0
for i in range(8):
image = scale(pygame.image.load(f"explosion/tile00{i}.png"), (40, 40))
self.images.append(image)
def draw(self, screen):
if self.index < 8:
screen.blit(self.images[self.index], (self.rect.x, self.rect.y))
self.index += 1
class Spaceship(pygame.sprite.Sprite):
def __init__(self, x, y):
self.rect = pygame.Rect(x, y, 50, 100)
self.image = scale(pygame.image.load("ship.png"), (50, 100))
self.xvel = 0
self.life = 100
self.explosions = []
def draw(self, screen):
screen.blit(self.image, (self.rect.x, self.rect.y))
for explosion in self.explosions:
explosion.draw(screen)
def update(self, left, right, asteroids):
if left:
self.xvel += -3
if right:
self.xvel += 3
if not (left or right):
self.xvel = 0
for asteroid in asteroids:
if self.rect.colliderect(asteroid.rect):
self.life -= 1
rx = random.randint(-5, 40)
ry = random.randint(-5, 40)
explosion = Explosion(self.rect.x + rx, self.rect.y + ry)
self.explosions.append(explosion)
self.rect.x += self.xvel
class Asteroid(pygame.sprite.Sprite):
def __init__(self, x, y):
self.image = scale(pygame.image.load("asteroid.png"), (50, 50))
self.rect = pygame.Rect(x, y, 50, 50)
self.yvel = 5
def draw(self, screen):
screen.blit(self.image, (self.rect.x, self.rect.y))
def update(self):
self.rect.y += self.yvel
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Asteroids")
timer = pygame.time.Clock()
sky = scale(pygame.image.load("sky.jpg"), (800, 600))
ship = Spaceship(400, 400)
left = False
right = False
asteroids = []
pygame.font.init()
font = pygame.font.SysFont('Comic Sans MS', 30)
while True:
if random.randint(1, 1000) > 900:
asteroid_x = random.randint(-100, 700)
asteroid_y = -100
asteroid = Asteroid(asteroid_x, asteroid_y)
asteroids.append(asteroid)
timer.tick(60)
for e in pygame.event.get():
if e.type == pygame.KEYDOWN and e.key == pygame.K_LEFT:
left = True
if e.type == pygame.KEYDOWN and e.key == pygame.K_RIGHT:
right = True
if e.type == pygame.KEYUP and e.key == pygame.K_LEFT:
left = False
if e.type == pygame.KEYUP and e.key == pygame.K_RIGHT:
right = False
if e.type == pygame.QUIT:
raise SystemExit("QUIT")
screen.blit(sky, (0, 0))
ship.update(left, right, asteroids)
ship.draw(screen)
for asteroid in asteroids:
asteroid.update()
asteroid.draw(screen)
textsurface = font.render(f'HP: {ship.life}', False, (255, 255, 255))
screen.blit(textsurface, (20, 20))
pygame.display.update()
2D RGP - перемещение персонажа в разные стороны и камера
По ссылке https://github.com/roctbb/rpg-template находится шаблон RPG игры на PyGame. В нем персонаж передвигается по лесу и собирает монеты.
В отличие от примера с астероидами в игру добавлены следующие изменения:
- объекты в игре в начале отрисовываются из карты;
- карта больше чем экран, при перемещении игрока перемещается камера;
- на карте есть проходимые и непроходимые объекты;
- анимация игрока - разная в разных направлениях.
Структура проекта:
images
- папка с картинками;game.py
- основной файл игры;game_platform.py
- описание вспомогательных объектов (вода, деревья, камни, монеты);player.py
- описание класса "игрок".