Классы Sprite и Group
В программировании игр спрайтом называют объект, который предстает перед пользователем в виде анимированного изображения и в большинстве случаев предполагает взаимодействие с ним. Другими словами, все что в игре не является фоном, а интерактивным объектом-картинкой – это спрайт.
Хотя каждый спрайт может быть уникальным, у всех есть нечто общее, что в pygame вынесено в отдельный класс Sprite
, находящийся в модуле pygame.sprite
.
На базе этого класса следует создавать собственные классы спрайтов и уже от них объекты. Таким образом, класс pygame.sprite.Sprite
играет роль своего рода абстрактного класса. Хотя таковым не является, можно создавать объекты непосредственно от Sprite
.
В модуле pygame.sprite
кроме класса Sprite
есть класс Group
и родственные ему, которые предназначены для объединения спрайтов в группы. Это позволяет вызывать один метод группы, который, например, обновит состояние всех спрайтов, входящих в эту группу.
Почти все предопределенные методы класса pygame.sprite.Sprite
касаются добавления экземпляра в группу, удаления из нее, проверки вхождения. Только метод update()
затрагивает поведение самого спрайта, этот метод следует переопределить в производном от Sprite
классе.
Рассмотрим, как это работает. В примерах кода ниже сначала одна, а потом и множество машинок перемещаются сверху вниз. Каждая такая машинка – объект-спрайт, созданный от класса Car, который является дочерним от Sprite
.
В конструкторе производного от Sprite
класса необходимо вызвать конструктор родительского класса, а также обзавестись экземплярами Surface
и Rect
, имена которых должны быть соответственно self.image
и self.rect
. Так надо, чтобы с экземплярами класса могли работать методы группы. В остальном вы можете добавлять любые атрибуты.
Как создается поверхность (а также прямоугольная область), неважно. В примере ниже это делается с помощью функции load()
. Однако в конструктор может передаваться уже подготовленный экземпляр Surface
.
from random import randint import pygame as pg import sys W = 400 H = 400 WHITE = (255, 255, 255) class Car(pg.sprite.Sprite): def __init__(self, x, filename): pg.sprite.Sprite.__init__(self) self.image = pg.image.load(filename).convert_alpha() self.rect = self.image.get_rect(center=(x, 0)) sc = pg.display.set_mode((W, H)) # координата x будет случайна car1 = Car(randint(1, W), 'car1.png') while 1: for i in pg.event.get(): if i.type == pg.QUIT: sys.exit() sc.fill(WHITE) sc.blit(car1.image, car1.rect) pg.display.update() pg.time.delay(20) # машинка ездит сверху вниз if car1.rect.y < H: car1.rect.y += 2 else: car1.rect.y = 0
В данном случае мы изменяем свойства экземпляра за пределами класса. Правильней будет делать это в методе update()
:
... class Car(pg.sprite.Sprite): def __init__(self, x, filename): pg.sprite.Sprite.__init__(self) self.image = pg.image.load(filename).convert_alpha() self.rect = self.image.get_rect(center=(x, 0)) def update(self): if self.rect.y < H: self.rect.y += 2 else: self.rect.y = 0 sc = pg.display.set_mode((W, H)) # координата x будет случайна car1 = Car(randint(1, W), 'car1.png') while 1: for i in pg.event.get(): if i.type == pg.QUIT: sys.exit() sc.fill(WHITE) sc.blit(car1.image, car1.rect) pg.display.update() pg.time.delay(20) car1.update()
Теперь представим, что у нас не одна машинка, а три:
... car1 = Car(randint(1, W), 'car1.png') car2 = Car(randint(1, W), 'car2.png') car3 = Car(randint(1, W), 'car3.png') while 1: for i in pg.event.get(): if i.type == pg.QUIT: sys.exit() sc.fill(WHITE) sc.blit(car1.image, car1.rect) sc.blit(car2.image, car2.rect) sc.blit(car3.image, car3.rect) pg.display.update() pg.time.delay(20) car1.update() car2.update() car3.update()
Если будет 100 машинок, придется 100 раз вызвать blit()
и update()
. Класс Group
решает эту проблему. Добавлять спрайты в группу можно методом add()
группы (по одной или все вместе).
У групп есть методы update()
и draw()
. Метод update()
группы вызывает методы update()
всех входящих в нее объектов. А метод draw()
выполняет метод blit()
. При этом в draw()
надо передать поверхность, на которой будет происходить отрисовка:
... cars = pg.sprite.Group() cars.add(Car(randint(1, W), 'car1.png'), Car(randint(1, W), 'car2.png')) cars.add(Car(randint(1, W), 'car3.png')) while 1: for i in pg.event.get(): if i.type == pg.QUIT: sys.exit() sc.fill(WHITE) cars.draw(sc) pg.display.update() pg.time.delay(20) cars.update()
Допустим, мы хотим, чтобы новые машинки появлялись постоянно и в разные моменты времени, двигались с разной скоростью, а выезд объекта за пределы экрана обозначал бы, что он исчезает.
Потребуется таймер, который устанавливается вызовом функции pygame.time.set_timer()
. В примере ниже через каждые 3 секунды будет генерироваться событие, значение поля type
которого совпадает с константой pygame.USEREVENT
. И как только это событие будет происходить, будет создаваться новый объект.
from random import randint import pygame as pg import sys pg.init() pg.time.set_timer(pg.USEREVENT, 3000) W = 400 H = 400 WHITE = (255, 255, 255) CARS = ('car1.png', 'car2.png', 'car3.png') # для хранения готовых машин-поверхностей CARS_SURF = [] # надо установить видео режим до вызова image.load() sc = pg.display.set_mode((W, H)) for i in range(len(CARS)): CARS_SURF.append(pg.image.load(CARS[i]).convert_alpha()) class Car(pg.sprite.Sprite): def __init__(self, x, surf, group): pg.sprite.Sprite.__init__(self) self.image = surf self.rect = self.image.get_rect(center=(x, 0)) # добавляем в группу self.add(group) # у машин будет разная скорость self.speed = randint(1, 3) def update(self): if self.rect.y < H: self.rect.y += self.speed else: # теперь не перебрасываем вверх, # а удаляем из всех групп self.kill() cars = pg.sprite.Group() # добавляем первую машину, которая появляется сразу Car(randint(1, W), CARS_SURF[randint(0, 2)], cars) while 1: for i in pg.event.get(): if i.type == pg.QUIT: sys.exit() elif i.type == pg.USEREVENT: Car(randint(1, W), CARS_SURF[randint(0, 2)], cars) sc.fill(WHITE) cars.draw(sc) pg.display.update() pg.time.delay(20) cars.update()
Метод kill()
спрайта удаляет его из всех групп, в которых он содержится. Есть метод remove()
, который удаляет только из указанных в качестве аргумента групп. У спрайтов также как у групп есть метод add()
. Только в данном случае ему передается не объект, а группа.
Практическая работа
В модуле pygame.sprite
есть ряд функций для проверки коллизий спрайтов. Одна из них spritecollideany()
проверяет, столкнулся ли конкретный спрайт с любым из спрайтов из группы. Функция принимает первым аргументом спрайт, чья коллизия проверяется, вторым – группу.
Измените программу выше так, чтобы машинки появлялись чаще. Добавьте спрайт, который "едет" навстречу всем другим и управляется стрелками влево и вправо на клавиатуре. Цель игры – не допустить столкновения. Если оно происходит, то программа завершается.
Курс с примерами решений практических работ:
pdf-версия