Как делать контроль столкновений

Ссылка на проект занятия (lesson 10.flowballs.zip): https://github.com/selfedu-rus/pygame

На предыдущем занятии мы с вами сделали небольшой макет игры, в которой шарики падали на землю. Здесь же разовьем это приложение, добавим телегу, которая будет ловить наши шары и начисляться соответствующие очки. То есть, у нас должна получиться практически полноценная игра.

Так вот, чтобы ловить шары телегой мы должны уметь определять момент столкновения шариков с тележкой. Для этого в классе

pygame.Rect

имеются следующие основные методы:

  • collidepoint(x, y) – проверка попадания точки в прямоугольник;
  • colliderect(Rect) – проверка пересечения двух прямоугольников;
  • collidelist(list) – проверка пересечения хотя бы с одним прямоугольником из списка прямоугольников list;
  • collidelistall(list) – проверка пересечения со всеми прямоугольниками из списка прямоугольников list.

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

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

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

telega = pygame.image.load('images/telega.png').convert_alpha()
t_rect = telega.get_rect(centerx=W//2, bottom=H-5)

Затем, перед главным циклом определим скорость перемещения телеги:

speed = 10

и ее непосредственное перемещение при нажатии на курсорные клавиши:

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        t_rect.x -= speed
        if t_rect.x < 0:
            t_rect.x = 0
    elif keys[pygame.K_RIGHT]:
        t_rect.x += speed
        if t_rect.x > W-t_rect.width:
            t_rect.x = W-t_rect.width

Далее, выполняем рисование всех объектов в окне программы:

    sc.blit(bg, (0, 0))
    balls.draw(sc)
    sc.blit(telega, t_rect)
    pygame.display.update()

Обратите внимание на очередность прорисовки. Сначала рисуется фон, затем, шары и в последнюю очередь тележка. Это важный момент, т.к. полагается, что телега должна заслонять шары.

Отлично, это мы сделали. Но шары пока еще не отлавливаются. По логике нашей игры при попадании шара в телегу игроку должны назначаться очки. Поэтому вначале переопределим коллекцию balls_data в виде списка словарей:

balls_data = ({'path': 'ball_bear.png', 'score': 100},
              {'path': 'ball_fox.png', 'score': 150},
              {'path': 'ball_panda.png', 'score': 200})

И, соответственно, изменим формирование коллекции balls_surf:

balls_surf = [pygame.image.load('images/'+data['path']).convert_alpha() for data in balls_data]

Теперь, в функции createBall при создании нового шара будем передавать не только его изображение, но и количество заработанных очков. Для этого в конструктор передадим еще один параметр balls_data[indx]['score']:

def createBall(group):
    indx = randint(0, len(balls_surf)-1)
    x = randint(20, W-20)
    speed = randint(1, 4)
 
    return Ball(x, speed, balls_surf[indx], balls_data[indx]['score'], group)

И, разумеется, этот же параметр нужно добавить в конструктор класса Ball:

    def __init__(self, x, speed, surf, score, group):
        pygame.sprite.Sprite.__init__(self)
        self.image = surf
        self.rect = self.image.get_rect(center=(x, 0))
        self.speed = speed
        self.score = score
        self.add(group)

Теперь у нас все готово для отслеживания столкновения шаров с телегой. В рамках данной программы мы это сделаем с помощью функции:

pygame.Rect.collidepoint( (x, y) )

где (x, y) – центр шара. То есть, как только центр шара оказывается в области телеги, то он считается пойманным:

Для отслеживания такого столкновения шаров с телегой объявим вспомогательную функцию:

def collideBalls():
    global game_score
    for ball in balls:
        if t_rect.collidepoint(ball.rect.center):
            game_score += ball.score
            ball.kill()

Мы здесь в цикле перебираем группу из падающих шаров balls и для каждого шара проверяем: попадает ли его центр в прямоугольник телеги t_rect. И если это так, то увеличиваем число очков и удаляем шар из группы.

Затем, в главный цикл игры добавляем вызов этой функции (последней строчкой):

collideBalls()

Все, мы сделали контроль столкновения и начисления очков при поимке того или иного шара телегой.

Осталось добавить последний штрих – отображать текущее число очков в игровом пространстве. Для этого, мы загрузим фоновое изображение для очков:

score = pygame.image.load('images/score_fon.png').convert_alpha()

определим шрифт для отображения цифр:

f = pygame.font.SysFont('arial', 30)

И, затем, в главном игровом цикле, отобразим всю эту информацию:

    sc.blit(bg, (0, 0))
    balls.draw(sc)
    sc.blit(score, (0, 0))
    sc_text = f.render(str(game_score), 1, (94, 138, 14))
    sc.blit(sc_text, (20, 10))
    sc.blit(telega, t_rect)
    pygame.display.update()

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

На следующем занятии мы добавим звуковые эффекты и завершим это творение.