Класс Rect. Его роль, свойства и методы

При разработке игр часто требуется определять границы персонажей, окружающих предметов, определять моменты их столкновений. Во всех этих задачах в PyGame используется специальный объект:

Rect (сокращение от Rectangle – прямоугольник)

Обратите внимание, Rect – это не класс для рисования прямоугольников, это класс для операций с прямоугольными областями и к рисованию он не имеет прямого отношения.

Например, в PyGame каждая поверхность имеет метод:

Surface.get_rect()

который возвращает класс Rect с размерами этого слоя. И в качестве начального примера, рассмотрим вот такую простую программу:

import pygame
 
pygame.init()
 
W = 600
H = 400
 
sc = pygame.display.set_mode((W, H))
pygame.display.set_caption("Класс Rect")
pygame.display.set_icon(pygame.image.load("app.bmp"))
 
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
 
FPS = 60        # число кадров в секунду
clock = pygame.time.Clock()
 
hero = pygame.Surface((40, 50))
hero.fill(BLUE)
rect = hero.get_rect()
print(rect)
 
sc.fill(WHITE)
sc.blit(hero, (100, 50))
pygame.display.update()
 
while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
 
    clock.tick(FPS)

Смотрите, у нас здесь определена поверхность hero (герой), которая имеет размеры 40x50 пикселей. Затем, с помощью операции:

rect = hero.get_rect()

мы получаем ссылку на размеры этой области в виде экземпляра класса Rect. Причем, этот класс имеет следующие весьма полезные свойства:

Например, мы можем обратиться к атрибуту center и узнать координаты центра нашего героя:

print(rect.center)

Мало того, метод get_rect имеет ряд дополнительных, полезных именованных параметров. Например, для определения заданного начального положения координат верхнего левого угла, нужно указать такой параметр:

rect = hero.get_rect(topleft=(200, 50))

Или, чтобы определить координаты прямоугольника по центру клиентской области, можно записать следующую команду:

rect = hero.get_rect(center = (W//2, H//2))

или так:

rect = hero.get_rect(centerx = W//2, centery = H//2)

Теперь, мы можем расположить героя точно в центре окна:

sc.blit(hero, rect)

Обратите внимание, мы здесь передаем в метод blit объект Rect для позиционирования слоя hero, а не кортеж координат (x, y). Так тоже можно делать и blit автоматически возьмет из Rect координату верхнего левого угла, а остальные параметры проигнорирует.

Помимо свойств класс Rect содержит и несколько полезных методов. Например:

  • Rect.move(x, y) – возвращает новый прямоугольник со смещениями x, y;
  • Rect.move_ip(x, y) – меняет координаты текущего прямоугольника со смещениями x, y;
  • Rect.clip(Rect) – обрезает границы прямоугольника по указанным размерам переданного прямоугольника;
  • Rect.union(Rect) – возвращает новый прямоугольник с результатом объединения двух прямоугольников в один;
  • Rect.union_ip – объединяет два прямоугольника в один;
  • Rect.fit(Rect) – возвращает новый прямоугольник, смещенный и измененный по размеру переданного прямоугольника;
  • Rect.contains(Rect) – проверяет: содержится ли один прямоугольник внутри другого.

Это лишь некоторые методы класса Rect. Полный их список можно посмотреть на странице официальной документации:

https://www.pygame.org/docs/ref/rect.html

И обратите внимание на суффикс _ip у одноименных методов. Он указывает, что изменения следует выполнять для текущего экземпляра класса, не создавая нового. Если же этот суффикс отсутствует, то метод создает новый объект класса Rect с соответствующими изменениями его свойств.

Например, давайте создадим в программе два объекта Rect:

rect1 = pygame.Rect((0, 0, 30, 30))
rect2 = pygame.Rect((30, 30, 30, 30))

И выполним с ними следующие операции:

rect2.move_ip(20, 20)
print(rect2)
rect3 = rect2.union(rect1)
print(rect3)

Результат работы показан на рисунке ниже:

По аналогии работают и другие методы класса Rect.

Давайте для примера, используя класс Rect, напишем программу подпрыгивания нашего героя при нажатии на кнопку пробел (lesson 6.hero_jump.py: https://github.com/selfedu-rus/pygame):

import pygame
 
pygame.init()
 
W = 600
H = 400
 
sc = pygame.display.set_mode((W, H))
pygame.display.set_caption("Класс Rect")
pygame.display.set_icon(pygame.image.load("app.bmp"))
 
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
 
FPS = 60        # число кадров в секунду
clock = pygame.time.Clock()
 
ground = H-70
jump_force = 20     # сила прыжка
move = jump_force+1
 
hero = pygame.Surface((40, 50))
hero.fill(BLUE)
rect = hero.get_rect(centerx=W//2)
rect.bottom = ground
 
while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE and ground == rect.bottom:
                move = -jump_force
 
    if move <= jump_force:
        if rect.bottom + move < ground:
            rect.bottom += move
            if move < jump_force:
                move += 1
        else:
            rect.bottom = ground
            move = jump_force+1
 
    sc.fill(WHITE)
    sc.blit(hero, rect)
    pygame.display.update()
 
    clock.tick(FPS)

Здесь мы определили вспомогательные переменные:

  • ground – высота земли;
  • jump_force – сила прыжка;
  • move – текущая вертикальная скорость.

Затем, ставим героя по центру горизонтали и на уровень земли, используя свойство rect.bottom. Обратите внимание, меняя это свойство, автоматически изменятся и свойства x, y – координаты верхнего левого угла. То есть, мы фактически смещаем прямоугольник вниз до уровня земли. Далее, нажимая на пробел, устанавливается значение move = -jump_force – герой прыгает вверх с начальной скоростью jump_force. В главном цикле идет проверка: если скорость move меньше или равна jump_force, то мы смещаем героя на эту величину и уменьшаем скорость на единицу (move += 1). И делаем эти операции, пока герой снова не окажется на уровне земли. Тогда его скорость искусственно устанавливается равной move = jump_force+1, чтобы условие прыжка не срабатывало и он остановился.

Этот пример показывает как можно использовать класс Rect для реализации простой анимации. Мало того, мы можем указать в методе pygame.display.update() перерисовывать не всю область окна, а только ту часть, где происходит подпрыгивание героя:

rect_update = pygame.Rect(rect.x, 0, rect.width, ground)

и, затем:

pygame.display.update(rect_update)

Смотрите, теперь кажется, что герой как бы пытается выпрыгнуть из ямы. Я специально показал этот эффект для лучшего понимания работы метода update. Конечно, в нашем случае нужно до главного цикла полностью прорисовать окно:

sc.fill(WHITE)
pygame.display.update()

а уже потом ограничивать область рисования.