Rect в PyGame — это прямоугольник из четырёх целых чисел: x, y, ширина и высота. Сам он ничего не рисует: хранит, где объект стоит и сколько места занимает. Через него двигают персонажа (rect.x += 5), прижимают к краям экрана (clamp_ip) и проверяют столкновения (colliderect). Спрайт — это класс с двумя полями, image и rect, а группа спрайтов рисует и обновляет их одной строкой и сама ищет пересечения через spritecollide. Ниже каждый метод с кодом и выводом, который я снял на pygame 2.6.1. Если у вас ещё нет окна и игрового цикла, начните с инструкции про первое окно в PyGame, а сюда вернитесь за движением и столкновениями.
Что такое Rect и зачем он каждому объекту игры

Создаётся прямоугольник вызовом pygame.Rect(x, y, width, height). Первые два числа — левый верхний угол, вторые два — размер. Всё остальное PyGame считает сам: правую границу, центр, середину нижней стороны. Примеры ниже работают на любой версии pygame 2; если библиотека ещё не стоит, есть короткая инструкция, как установить PyGame через pip.
import pygame
player = pygame.Rect(100, 50, 40, 60)
print(player) # <rect(100, 50, 40, 60)>
print(player.x, player.y, player.width, player.height) # 100 50 40 60
print(player.left, player.top, player.right, player.bottom) # 100 50 140 110
print(player.center, player.topleft, player.bottomright) # (120, 80) (100, 50) (140, 110)
Любой из этих атрибутов можно не только читать, но и присваивать. Напишете player.center = (320, 240) — прямоугольник переедет так, чтобы центр оказался в этой точке, а размер не изменится. Это удобнее, чем пересчитывать x и y руками.
| Атрибут | Что хранит | Типичное применение |
|---|---|---|
x, y (то же, что left, top) |
Левый верхний угол | Движение по осям |
right, bottom |
Правая и нижняя границы | Проверка выхода за экран |
width, height (w, h, size) |
Размер | Хитбокс, масштаб |
center, centerx, centery |
Центр | Поставить объект посередине |
topleft, topright, bottomleft, bottomright |
Углы парой чисел | Привязка к углу экрана |
midtop, midbottom, midleft, midright |
Середины сторон | Персонаж «стоит» на полу: midbottom |
Два свойства, о которые спотыкаются чаще всего. Первое: координаты Rect всегда целые. Документация говорит об этом одной строкой, а на практике это значит, что дробное движение молча обнуляется (разбираем ниже, в разделе про ошибки). Второе: right и bottom не входят в прямоугольник. У Rect(100, 50, 40, 60) правая граница 140, но точка с x = 140 уже снаружи. Поэтому collidepoint(140, 80) вернёт False.
pygame.Rect(0, 0, 0, 0) — законный объект, его часто заводят как заглушку, а потом задают size и center. Но пока размер нулевой, ни одна проверка столкновений с ним не сработает: так написано в справке по pygame.Rect, и мой прогон это подтвердил.
Как нарисовать прямоугольник: pygame.draw.rect
Сам по себе Rect невидим. Чтобы его показать, нужен pygame.draw.rect(surface, color, rect, width=0, border_radius=0). Параметр width при нуле заливает фигуру целиком, при положительном числе рисует только рамку такой толщины. Скруглённые углы через border_radius появились в pygame 2.0.
screen = pygame.display.set_mode((640, 480))
pygame.draw.rect(screen, (255, 0, 0), player) # заливка
pygame.draw.rect(screen, "white", player, width=2, border_radius=8) # рамка 2 px
pygame.draw.rect(screen, "green", (10, 10, 50, 20)) # можно кортежем
Цвет принимает и кортеж RGB, и строку с именем вроде "dodgerblue". Функция возвращает Rect с областью, которую изменила, поэтому её результат можно тут же сохранить в переменную. Рядом в том же модуле живут draw.circle, draw.line и draw.polygon, но для столкновений вам всё равно понадобится прямоугольник.
get_rect: прямоугольник из картинки или экрана
Когда у объекта есть изображение, Rect удобнее спросить у поверхности, чем собирать руками. image.get_rect() вернёт прямоугольник размера картинки в точке (0, 0), а именованные аргументы сразу расставят его по месту.
img = pygame.image.load("hero.png").convert_alpha() # или pygame.Surface((32, 48))
rect = img.get_rect(center=(320, 240))
print(rect) # <rect(304, 216, 32, 48)>
screen_rect = screen.get_rect()
print(screen_rect) # <rect(0, 0, 640, 480)>
Второй вызов, screen.get_rect(), даёт прямоугольник всего окна. Он нужен постоянно: центр экрана для стартовой позиции, границы для проверки «улетел ли объект», рамка для clamp_ip. Сохраните его в переменную один раз, вызывать каждый кадр незачем.
Движение и границы экрана: move, move_ip и clamp_ip
Двигать прямоугольник можно тремя способами, и разница между ними в том, меняется ли исходный объект.
rect.x += 5илиrect.y -= 3. Прямое присваивание, самый частый вариант в игровом цикле.new = rect.move(5, 0). Возвращает новый Rect, старый не трогает. Полезно, когда нужно проверить «а что будет, если сдвинуться», не сдвигаясь.rect.move_ip(5, 0). Суффикс_ipозначает in place: метод меняет сам объект и ничего не возвращает.
a = pygame.Rect(10, 10, 20, 20)
b = a.move(5, 0)
print(a, b) # <rect(10, 10, 20, 20)> <rect(15, 10, 20, 20)>
a.move_ip(5, 0)
print(a) # <rect(15, 10, 20, 20)>
a.topleft = (-30, 700) # улетел за экран
a.clamp_ip(screen_rect)
print(a) # <rect(0, 460, 20, 20)>
Последний фрагмент решает задачу «ограничить позицию rect пределами экрана» одной строкой. clamp_ip(screen_rect) вталкивает прямоугольник внутрь другого: если он вылез влево, x станет 0, если провалился вниз, bottom совпадёт с нижней границей окна. Никаких четырёх if с проверкой каждой стороны. Тот же приём без _ip вернёт копию, а исходник оставит на месте.
Обработку клавиш для движения удобно совместить с этим: читаете pygame.key.get_pressed(), меняете rect.x, в конце кадра вызываете clamp_ip. Сам цикл с событиями и clock.tick(60) разобран в статье про окно и игровой цикл, а если конструкция while running вызывает вопросы, загляните в разбор цикла while в Python.
Столкновения прямоугольников: colliderect, collidepoint и collidelist

Так как каждый объект описан прямоугольником, проверка столкновения сводится к вопросу «пересекаются ли два прямоугольника». За это отвечает colliderect.
p = pygame.Rect(0, 0, 50, 50)
e = pygame.Rect(40, 40, 50, 50)
f = pygame.Rect(50, 0, 50, 50)
print(p.colliderect(e)) # True — перекрываются на 10 px
print(p.colliderect(f)) # False — только касаются границей
print(p.collidepoint(10, 10)) # True
print(p.collidepoint(50, 50)) # False — правая и нижняя границы снаружи
Второй результат многих удивляет: прямоугольники, которые стоят вплотную, по мнению PyGame не сталкиваются. Документация формулирует это как «любая часть пересекается, кроме общих границ». Для платформера это как раз то, что нужно: персонаж стоит на полу, и ложное столкновение с ним не срабатывает каждый кадр.
| Метод | Что проверяет | Что возвращает |
|---|---|---|
colliderect(other) |
Пересечение с одним Rect | True / False |
collidepoint(x, y) |
Точка внутри (например, курсор мыши над кнопкой) | True / False |
collidelist(list) |
Пересечение со списком Rect | Индекс первого совпадения или -1 |
collidelistall(list) |
То же, но все совпадения | Список индексов, пустой при промахе |
contains(other) |
Другой Rect целиком внутри | True / False |
clip(other) |
Область пересечения | Новый Rect (нулевой, если нет пересечения) |
enemies = [pygame.Rect(200, 200, 30, 30), pygame.Rect(30, 30, 30, 30), pygame.Rect(45, 45, 10, 10)]
print(p.collidelist(enemies)) # 1
print(p.collidelistall(enemies)) # [1, 2]
print(p.clip(e)) # <rect(40, 40, 10, 10)>
Пока врагов десяток, списка и collidelist хватает. Когда объекты начинают появляться и исчезать пачками, список превращается в ручную бухгалтерию: удалить пулю, удалить врага, не сломать цикл, который по этому же списку идёт. Ровно для этого в PyGame есть спрайты и группы.
Спрайты и группы: класс Sprite и Group
Спрайт в PyGame — это класс, наследник pygame.sprite.Sprite, у которого есть два обязательных атрибута: image и rect. Первый — что рисовать, второй — где. Группа pygame.sprite.Group хранит такие объекты и умеет два действия: update() вызывает одноимённый метод у каждого спрайта, draw(screen) делает blit каждого image в его rect.
class Enemy(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface((30, 30))
self.image.fill("red")
self.rect = self.image.get_rect(topleft=(x, y))
def update(self):
self.rect.y += 3
if self.rect.top > 480:
self.kill()
enemies = pygame.sprite.Group(Enemy(300, 350), Enemy(100, 100))
# в игровом цикле:
enemies.update()
enemies.draw(screen)
Три детали, без которых это не заработает. super().__init__() обязателен: без него объект нельзя положить в группу, PyGame упадёт с загадочной AttributeError: '_Sprite__g'. Метод называется именно update, и группа вызовет его с теми аргументами, которые вы передали в Group.update(). И self.kill() удаляет спрайт из всех групп сразу: враг, который улетел за нижний край, перестанет рисоваться и обновляться, а сам объект останется жив, если на него есть другие ссылки.
На практике заводят две группы: all_sprites для отрисовки всего и отдельные (enemies, bullets) для логики столкновений. Один спрайт спокойно живёт в нескольких группах. Так что персонаж из all_sprites и враги из enemies рисуются одной строкой, а сталкиваются только те, кому положено. Если слова «наследник» и super() звучат непривычно, классы стоит подтянуть раньше спрайтов: почти любые курсы по Python проходят их до графики.
Анимация спрайта: смена кадров по таймеру
Раз image — обычный атрибут, анимация сводится к его подмене. Загрузите кадры в список, а в update переключайте индекс по таймеру, раз в N миллисекунд. Если менять его каждый кадр, при 60 FPS персонаж «замигает».
class Hero(pygame.sprite.Sprite):
def __init__(self, frames):
super().__init__()
self.frames = frames # список Surface
self.frame = 0
self.image = frames[0]
self.rect = self.image.get_rect()
self.last = pygame.time.get_ticks()
self.delay = 120 # мс на кадр
def update(self):
now = pygame.time.get_ticks()
if now - self.last >= self.delay:
self.last = now
self.frame = (self.frame + 1) % len(self.frames)
self.image = self.frames[self.frame]
В моём прогоне при задержке 120 мс и вызовах каждые 60 мс индексы шли как [0, 1, 1, 2, 2, 3], то есть кадр сменялся через раз. Сами кадры для 2D-игры обычно рисуют в пиксельных редакторах; если хотите делать их сами, посмотрите курсы по пиксель-арту.
Столкновения спрайтов: spritecollide, groupcollide и маски
С группами проверка столкновений становится короче, чем с голыми Rect. Три функции модуля pygame.sprite закрывают почти все случаи.
hits = pygame.sprite.spritecollide(player, enemies, True) # игрок против группы
score += len(hits)
pairs = pygame.sprite.groupcollide(bullets, enemies, True, True) # группа против группы
if pygame.sprite.spritecollideany(player, enemies): # только да или нет
game_over = True
Третий аргумент, dokill, решает судьбу столкнувшихся: при True спрайты из группы удаляются сами, как будто вы вызвали kill() на каждом. У groupcollide таких флага два, по одному на каждую группу, а возвращает она словарь «спрайт первой группы → список спрайтов второй, которых он задел». spritecollideany отдаёт первый попавшийся спрайт или None и по документации работает быстрее, когда список столкнувшихся вам не нужен.
В моём тесте игрок 40 × 40 в точке (320, 400) и три врага после одного update(): spritecollide вернул двоих, в группе врагов остался один, и в all_sprites убитые тоже пропали. Это важно: dokill=True удаляет спрайт из всех групп, не только из той, что вы передали.
| Функция | Кого с кем | dokill | Возвращает |
|---|---|---|---|
spritecollide(sprite, group, dokill) |
Один против группы | 1 флаг | Список спрайтов группы |
groupcollide(g1, g2, dokill1, dokill2) |
Группа против группы | 2 флага | Словарь спрайт → список |
spritecollideany(sprite, group) |
Один против группы | нет | Спрайт или None |
У всех трёх есть необязательный аргумент collided: функция, которая решает, что считать столкновением. По умолчанию это collide_rect, то есть тот же colliderect. Два других варианта пригодятся, когда прямоугольники врут.
pygame.sprite.collide_rect_ratio(0.5)сравнивает уменьшенные копии Rect. Хитбокс становится мягче: враг задевает игрока, только когда зашёл глубоко, касание краем не считается.pygame.sprite.collide_maskсравнивает маски, то есть непрозрачные пиксели. Круглая пуля и круглый астероид по Rect столкнутся углами, по маске нет.
class Ship(pygame.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = pygame.Surface((40, 40), pygame.SRCALPHA)
pygame.draw.circle(self.image, "white", (20, 20), 20)
self.rect = self.image.get_rect(topleft=pos)
self.mask = pygame.mask.from_surface(self.image)
s1, s2 = Ship((0, 0)), Ship((34, 34))
print(pygame.sprite.collide_rect(s1, s2)) # True — углы Rect перекрылись
print(pygame.sprite.collide_mask(s1, s2)) # None — круги не касаются
hits = pygame.sprite.spritecollide(s1, group, False, pygame.sprite.collide_mask)
Маску можно не создавать руками: если атрибута mask нет, collide_mask построит её из image на лету. Но это дорого, и в справке по модулю pygame.sprite отдельно сказано: при смене картинки маску нужно пересоздать. Для анимированного спрайта это значит обновлять self.mask в том же update, где меняется кадр. В Godot или Unity за это отвечают готовые коллайдеры, и если PyGame для вас первая остановка, стоит заранее посмотреть, какие движки и языки есть дальше.
Собираем всё вместе: игра «Поймай звезду» на 70 строк

Ниже полный файл, который использует каждый приём из статьи: get_rect(midbottom=…) для старта, clamp_ip для границ, kill() для улетевших звёзд, spritecollide с dokill для счёта. Звёзды появляются по таймеру pygame.time.set_timer, чтобы не привязываться к номеру кадра.
import random
import pygame
WIDTH, HEIGHT = 640, 480
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((50, 20))
self.image.fill("dodgerblue")
self.rect = self.image.get_rect(midbottom=(WIDTH // 2, HEIGHT - 20))
self.speed = 6
def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.rect.x -= self.speed
if keys[pygame.K_RIGHT]:
self.rect.x += self.speed
self.rect.clamp_ip(screen.get_rect())
class Star(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((16, 16))
self.image.fill("gold")
self.rect = self.image.get_rect(
midtop=(random.randint(8, WIDTH - 8), -16)
)
self.speed = random.randint(3, 6)
def update(self):
self.rect.y += self.speed
if self.rect.top > HEIGHT:
self.kill()
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
font = pygame.font.Font(None, 36)
player = Player()
stars = pygame.sprite.Group()
all_sprites = pygame.sprite.Group(player)
score = 0
SPAWN = pygame.USEREVENT + 1
pygame.time.set_timer(SPAWN, 700)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == SPAWN:
star = Star()
stars.add(star)
all_sprites.add(star)
all_sprites.update()
score += len(pygame.sprite.spritecollide(player, stars, True))
screen.fill("black")
all_sprites.draw(screen)
screen.blit(font.render(f"Счёт: {score}", True, "white"), (10, 10))
pygame.display.flip()
clock.tick(60)
pygame.quit()
Сохраните как stars.py и запустите; если не помните, как это делается из редактора, есть инструкция про запуск файла Python в VS Code. Я прогнал файл на 400 кадров с зажатой стрелкой вправо: игрок упёрся в правый край на x = 590 (640 минус ширина 50), счёт рос ровно на число пойманных звёзд. Дальше игру легко расширить: замените Surface на картинки через pygame.image.load, добавьте группу пуль и groupcollide, повесьте на промахи spritecollideany с полом.
Семьдесят строк выше — это скелет. В настоящей игре к нему добавятся меню, уровни, звук и сборка в exe, но механика останется той же: у каждого объекта есть Rect, группы обновляют и рисуют, столкновения решают, что произошло.
Частые ошибки с Rect и спрайтами
Каждую строку таблицы я воспроизвёл на pygame 2.6.1, текст ошибки скопирован из терминала.
| Симптом | Причина | Что сделать |
|---|---|---|
Объект не двигается при rect.x += 0.3 |
Rect хранит только целые: 0 + 0.3 усекается до 0 каждый кадр | Храните позицию в отдельной переменной float (или pygame.Vector2) и записывайте в rect.x = round(pos_x) |
TypeError: rect argument is invalid в draw.rect |
Передан кортеж из трёх чисел или не Rect | Четыре числа: (x, y, w, h), либо готовый Rect |
TypeError: Invalid rect, all 4 fields must be numeric в colliderect |
В метод передана точка (x, y), а не прямоугольник |
Для точки нужен collidepoint |
AttributeError: 'Enemy' object has no attribute 'rect' |
В __init__ спрайта нет self.rect (или он назван иначе) |
Ровно self.image и self.rect, других имён группа не знает |
AttributeError: 'Enemy' object has no attribute '_Sprite__g' |
Забыт super().__init__() |
Первой строкой конструктора вызвать родительский __init__ |
| Столкновение никогда не срабатывает | Картинка рисуется по своим x, y, а Rect стоит на месте; либо у Rect нулевой размер |
Двигать только rect, рисовать через screen.blit(image, rect); проверить rect.size |
ValueError: invalid color name |
Опечатка в строковом имени цвета | Проверить имя или задать RGB-кортеж |
Первая ошибка самая коварная, потому что программа не падает. Скорость 0.3 пикселя в кадр выглядит разумно для плавного движения, а объект стоит как вкопанный. В моём прогоне десять прибавлений по 0.3 дали rect.x == 0, а через отдельную переменную — 3. Проверяйте это первым делом, когда «медленный» объект не едет.
Где освоить Python и разработку игр системно
Rect и спрайты закрывают механику 2D-игры, но дальше упираешься в то, что вокруг: классы и наследование, структура проекта, отладка. Это уже вопросы к самому языку. Если хотите пройти его по порядку, а не кусками, сравните курсы по Python: в каталоге есть и короткие интенсивы, и программы с проектами. Тем, кто целится именно в геймдев, подойдут курсы по разработке игр, а чем занимается разработчик игр в студии и сколько получает, разобрано в отдельном обзоре профессии.
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Профессия «Python-разработчик» Перейти на сайт курса | 157 335 ₽ | 5987 ₽/мес. | 10 месяцев | Обзор курса | |
| Fullstack-разработчик на Python Перейти на сайт курса | 175 800 ₽ | 7125 ₽/мес. | 21 месяц | Обзор курса | |
| ДО Профессия Python-разработчик Перейти на сайт курса | 149 001 ₽ | 3167 ₽/мес. | 10 месяцев | Обзор курса | |
| Автоматизированное тестирование на Python Перейти на сайт курса | 118 666 ₽ | 4108 ₽/мес. | 9 месяцев | Обзор курса | |
| Программирование на Python ПРО Перейти на сайт курса | 119 600 ₽ | 250 000 ₽/мес. | 17 месяцев | Обзор курса |
Больше программ — в полном каталоге курсов по Python




