Класс Rect
Еще одни ключевым классом в Pygame является Rect
. Его экземпляры представляют собой прямоугольные области. Они не имеют графического представления в окне игры. Ценность класса заключается в свойствах и методах, позволяющих управлять размещением поверхностей, выполнять проверку их перекрытия и др.
Rect'ы можно передавать в функцию pygame.display.update()
. В этом случае будут обновляться только соответствующие им области.
Экземпляры Rect
создаются не только напрямую от класса. Однако начнем с этого варианта.
import pygame as pg import sys sc = pg.display.set_mode((400, 400)) rect1 = pg.Rect((0, 0, 30, 30)) rect2 = pg.Rect((30, 30, 30, 30)) print(rect1.bottomright) # (30, 30) print(rect2.bottomright) # (60, 60) rect2.move_ip(10, 10) print(rect2.topleft) # (40, 40) rect1.union_ip(rect2) print(rect1.width) # 70 while 1: for i in pg.event.get(): if i.type == pg.QUIT: sys.exit()
В конструктор класса Rect
передаются четыре числа – координаты x и y, ширина и высота. Мы создаем два квадрата со сторонами в 30 пикселей. Верхний левый угол первого находится в точке (0, 0), второго – (30, 30).
У объектов Rect
есть более десятка свойств, связанных с их координатами и размерами. Свойство bottomright
одно из них, в нем хранится координата нижнего правого угла. Понятно, что если второй квадрат начинается в точке (30, 30) и его сторона равна 30, то нижний правый угол будет в точке (60, 60).
Кроме свойств, у объектов Rect
есть множество методов. Метод move_ip()
смещает прямоугольную область по оси x (первый аргумент) и y (второй аргумент) на указанное количество пикселей. В данном случае если второй прямоугольник смещается на 10 пикселей по обоим осям, то его левый верхний угол окажется в точке (40, 40).
Метод union_ip()
присоединяет к тому прямоугольнику, к которому применяется, другой – который передается аргументом. Когда мы отодвинули второй прямоугольник на 10 пикселей, то область, заключающая в себе оба, уже будет шириной 70 пикселей, а не 60.
Методы, у которых есть суффикс _ip
, изменяют тот экземпляр Rect
, к которому применяются. Есть аналогичные методы без _ip
(например, move()
, union()
), которые возвращают новый экземпляр, т. е. старый остается без изменений.
В метод blit()
можно передавать не координаты места размещения Surface
, а экземпляр Rect
. Метод blit()
сам возьмет из Rect
координаты его верхнего левого угла:
import pygame as pg import sys GREEN = (200, 255, 200) YELLOW = (220, 200, 0) WHITE = (255, 255, 255) sc = pg.display.set_mode((300, 300)) sc.fill(GREEN) surf1 = pg.Surface((200, 200)) surf1.fill(YELLOW) surf2 = pg.Surface((100, 100)) surf2.fill(WHITE) rect = pg.Rect((70, 20, 0, 0)) surf1.blit(surf2, rect) sc.blit(surf1, rect) pg.display.update() while 1: for i in pg.event.get(): if i.type == pg.QUIT: sys.exit()
Мы размещаем желтую поверхность на зеленой, а белую – на желтой. В обоих случаях – в координатах (70, 20). Однако в каждом случае точка берется относительно своей родительской поверхности.
Еще один момент, на который надо обратить внимание. Прямоугольная область была определена нулевой размерностью. При этом поверхности отобразились соответственно своим собственным размерам. Это значит, что поверхности не располагаются внутри rect'ов. Они к ним никакого отношения не имеют. Из прямоугольников blit()
взял только координаты.
С другой стороны, экземпляры Rect
предназначены для хранения не только координат, но и размеров поверхностей. Размеры в основном нужны для проверки коллизий. В большинстве случаев сначала создается поверхность. Далее с нее снимается "маска", т. е. создается экземпляр Rect
, который будет иметь те же размеры, что и она. Все дальнейшее "взаимодействие" поверхности с другими объектами (размещение, проверка столкновений и вхождений) происходит через "связанный" с ней Rect
.
import pygame import sys GREEN = (200, 255, 200) WHITE = (255, 255, 255) sc = pygame.display.set_mode((300, 300)) surf = pygame.Surface((100, 100)) surf.fill(WHITE) rect = surf.get_rect() # создается экземпляр Rect print(surf.get_width()) # 100 print(rect.width) # 100 print(rect.x, rect.y) # 0 0 while 1: for i in pygame.event.get(): if i.type == pygame.QUIT: sys.exit() sc.fill(GREEN) sc.blit(surf, rect) pygame.display.update() rect.x += 1 pygame.time.delay(20)
Метод поверхности get_rect()
возвращает экземпляр Rect
, ширина и высота которого совпадают с таковыми поверхности. В примере метод get_width()
возвращает ширину поверхности, также выводится ширина прямоугольника (rect.width
), чтобы показать, что они равны.
Если в get_rect()
не передавать аргументы, то верхний левый угол экземпляра Rect
будет в точке (0, 0).
В цикле мы изменяем координату x прямоугольной области, после чего передаем уже измененный rect в метод blit()
. В результате поверхность будет двигаться.
Польза заключается в том, что нам не нужно вводить множество переменных для хранения координат и размеров. Вместо этого для каждой поверхности заводится свой Rect
, который включает необходимые значения и ряд полезных методов.
В get_rect()
можно передавать именованные аргументы, являющиеся свойствами Rect
, и устанавливать им значения. Например, surf.get_rect(topleft=(100, 50))
вернет прямоугольник, чей левый угол будет в точке (100, 50), а размер совпадать с размерами surf. Выражение surf.get_rect(centerx=100)
вернет прямоугольник, координата x центра которого будет иметь значение 100. При этом остальные координаты будут вычислены, исходя из размеров поверхности.
Перепишем программу с двумя ракетами из предыдущего урока, используя экземпляры Rect
.
import pygame import sys WIN_WIDTH = 800 WIN_HEIGHT = 600 WHITE = (255, 255, 255) BLACK = (0, 0, 0) class Rocket: width_rocket = 20 height_rocket = 50 def __init__(self, surface, color): self.surf = surface self.color = color self.x = surface.get_width()//2 - Rocket.width_rocket//2 self.y = surface.get_height() def fly(self): pygame.draw.rect(self.surf, self.color, (self.x, self.y, Rocket.width_rocket, Rocket.height_rocket)) self.y -= 3 if self.y < -Rocket.height_rocket: self.y = self.surf.get_height() sc = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT)) rect_left = pygame.Rect((0, 0), (WIN_WIDTH//2, WIN_HEIGHT)) rect_right = pygame.Rect((WIN_WIDTH//2, 0), (WIN_WIDTH//2, WIN_HEIGHT)) surf_left = pygame.Surface((rect_left.width, rect_left.height)) surf_left.fill(WHITE) surf_right = pygame.Surface((rect_right.width, rect_right.height)) sc.blit(surf_left, rect_left) sc.blit(surf_right, rect_right) rocket_left = Rocket(surf_left, BLACK) rocket_right = Rocket(surf_right, WHITE) pygame.display.update() active_left = False active_right = False while 1: for i in pygame.event.get(): if i.type == pygame.QUIT: sys.exit() elif i.type == pygame.MOUSEBUTTONUP: if rect_left.collidepoint(i.pos): active_left = True active_right = False elif rect_right.collidepoint(i.pos): active_right = True active_left = False if active_left: surf_left.fill(WHITE) rocket_left.fly() sc.blit(surf_left, rect_left) pygame.display.update(rect_left) elif active_right: surf_right.fill(BLACK) rocket_right.fly() sc.blit(surf_right, rect_right) pygame.display.update(rect_right) pygame.time.delay(20)
Главное, на что здесь следует обратить внимание, – вызов функции pygame.display.update()
с аргументом-прямоугольником. Таким образом, на каждой итерации главного цикла while
в памяти компьютера "перерисовывается" только часть окна, что экономит его ресурсы.
rect_left = pygame.Rect((0, 0), (WIN_WIDTH//2, WIN_HEIGHT)) rect_right = pygame.Rect((WIN_WIDTH//2, 0), (WIN_WIDTH//2, WIN_HEIGHT))
Создаются два экземпляра Rect
. Левый начинается в верхнем левом углу окна, правый – от цента по оси x, вверху по оси y.
surf_left = pygame.Surface((rect_left.width, rect_left.height)) surf_left.fill(WHITE) surf_right = pygame.Surface((rect_right.width, rect_right.height))
При создании экземпляров Surface
мы указываем такую же ширину и высоту как у соответствующих им прямоугольников.
sc.blit(surf_left, rect_left) sc.blit(surf_right, rect_right)
Левая и правая поверхности прорисовываются на главном окне. Координаты берутся из соответствующих экземпляров Rect
.
rocket_left = Rocket(surf_left, BLACK) rocket_right = Rocket(surf_right, WHITE)
Создаются два экземпляра нашего самописного класса Rocket. Конструктору надо передать поверхность и цвет.
active_left = False active_right = False
Переменные определяют, какую анимацию проигрывать. Пока не будет произведено кликов, то никакую.
elif i.type == pygame.MOUSEBUTTONUP: if rect_left.collidepoint(i.pos): active_left = True active_right = False elif rect_right.collidepoint(i.pos): active_right = True active_left = False
Метод collidepoint()
объекта Rect
проверяет, находится ли точка, координаты которой были переданы в качестве аргумента, в пределах прямоугольника, к которому применяется метод. Здесь точкой являются координаты клика мыши. Если клик происходит в левом прямоугольнике, то в True
устанавливается одна переменная, если в правом – то другая.
if active_left: surf_left.fill(WHITE) rocket_left.fly() sc.blit(surf_left, rect_left) pygame.display.update(rect_left) elif active_right: surf_right.fill(BLACK) rocket_right.fly() sc.blit(surf_right, rect_right) pygame.display.update(rect_right)
В зависимости от того, какая переменная в статусе True
, изменения происходят только на одной из двух дочерних поверхностей. Также как update()
метод blit()
вызывается на каждой итерации, иначе изменения не будут видны.
Практическая работа
Напишите программу, где в центре окна находится круг, изменяющий свой цвет на каждой итерации цикла. Если нажимается клавиша 1, продолжать изменяться должен цвет только верхней половины круга. Если 2 – только нижней. Нажатие цифры 3 должно возобновлять обновление всей поверхности.
Курс с примерами решений практических работ:
pdf-версия