При разработке
игр часто требуется определять границы персонажей, окружающих предметов,
определять моменты их столкновений. Во всех этих задачах в 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 и узнать координаты центра
нашего героя:
Мало того, метод
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)
Теперь, мы можем
расположить героя точно в центре окна:
Обратите
внимание, мы здесь передаем в метод 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()
а уже потом
ограничивать область рисования.