Класс Rect в Pygame. Использование с поверхностями
Еще одни ключевым классом в 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
метод blit
поверхностей обретает новые свойства. Если третьим аргументом в blit
передать экземпляр Rect
, то из него возьмутся не координаты, а размеры. Именно на их ширину и высоту будут перерисованы пиксели на поверхности.
В примерах с анимацией мы в цикле заливаем всю поверхность фоновым цветом, чтобы "затереть" ранее прорисованную на нее графику. Это неэкономный расход ресурсов, так как в действительности на поверхности может изменяться лишь небольшой участок. Перепишем пример выше следующим образом:
import pygame import sys GREEN = (200, 255, 200) WHITE = (255, 255, 255) sc = pygame.display.set_mode((300, 300)) bg = pygame.Surface((300, 300)) bg.fill(GREEN) sc.blit(bg, (0, 0)) surf = pygame.Surface((100, 100)) surf.fill(WHITE) rect = surf.get_rect() while 1: for i in pygame.event.get(): if i.type == pygame.QUIT: sys.exit() sc.blit(bg, rect, rect) rect.x += 1 sc.blit(surf, rect) pygame.display.update() pygame.time.delay(20)
Здесь создается дополнительная поверхность bg, играющая роль фона. В цикле и она, и движущийся квадрат прорисовываются на главной поверхности sc. При этом bg перерисовывается не целиком, а только ее область, которая начинается в координатах верхнего левого угла rect и простирается на размерность rect. В результате ранее нарисованный surf затирается. После изменения координаты surf появляется уже в новом месте.
Перепишем программу с двумя ракетами из предыдущего урока, используя экземпляры 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)) surf_left = pygame.Surface((WIN_WIDTH//2, WIN_HEIGHT)) # 400x600 surf_right = pygame.Surface((WIN_WIDTH//2, WIN_HEIGHT)) # 400x600 surf_left.fill(WHITE) # правая останется черной rect_left = surf_left.get_rect() # x=0, y=0, width=400, height=600 rect_right = surf_right.get_rect(topleft=(WIN_WIDTH//2, 0)) # x=400, y=0, width=400, height=600 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) elif active_right: surf_right.fill(BLACK) rocket_right.fly() sc.blit(surf_right, rect_right) pygame.display.update() pygame.time.delay(20)
В программе мы создаем экземпляры Rect
с помощью метода get_rect
поверхностей. Однако могли бы это сделать непосредственно от класса:
rect_left = pygame.Rect((0, 0), (WIN_WIDTH//2, WIN_HEIGHT)) rect_right = pygame.Rect((WIN_WIDTH//2, 0), (WIN_WIDTH//2, WIN_HEIGHT))
Метод collidepoint()
объекта Rect
проверяет, находится ли точка, координаты которой были переданы в качестве аргумента, в пределах прямоугольника, к которому применяется метод. Здесь точкой являются координаты клика мыши. Если клик происходит в левом прямоугольнике, то в True
устанавливается одна переменная, если в правом – то другая.
Практическая работа
В функцию pygame.display.update()
можно передать аргумент-прямоугольник (экземпляр Rect
). Он будет играть ту же роль, что и третий аргумент в методе blit()
, ‒ задавать ширину и высоту перерисовываемой поверхности. Аналогично можно сделать так, чтобы на каждой итерации главного цикла while
обновлялась только часть окна.
Напишите программу, где в центре окна находится круг, изменяющий свой цвет на каждой итерации цикла. Если нажимается клавиша 1, продолжать изменяться должен цвет только верхней половины круга. Если 2 – только нижней. Нажатие цифры 3 должно возобновлять обновление всей поверхности.