Search code examples
pythonvectorpygame

Pygame Calculate Angle of Reflection of Collision betweeen masks


I encountered an issue in my brick breaker game where the paddle has round edges and i want to reflect the ball with perfect angle, i was using angle and sin and cos for the direction, but i moved to vectors after a recommendation, i made a similar problem for the sake of simplicity. here is the code:

import sys

import pygame

pygame.init()

w, h = 1200, 800
screen = pygame.display.set_mode((w, h))
clock = pygame.Clock()

class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((10, 10))
        self.image.set_colorkey((0,0,0))
        pygame.draw.circle(self.image, (255, 0, 0), (5, 5), 3)
        self.rect = self.image.get_rect(topleft = (x, y) )
        self.mask = pygame.mask.from_surface(self.image)
        self.velocity = pygame.Vector2(0, -10)

    def update(self, circlex, circley, circle_mask):
        if self.rect.y < 0 or self.rect.y > 800:
            self.kill()
        if self.rect.x < 0 or self.rect.x > 1200:
            self.kill()

        self.rect.y += self.velocity.y
        offset = circlex - self.rect.x, circley - self.rect.y
        if p := self.mask.overlap(circle_mask, offset):
            v = pygame.Vector2(p)
            self.velocity.reflect_ip(v)


    def draw(self, screen):
        screen.blit(self.image, self.rect)


circle = pygame.Surface((400, 400))
pygame.draw.circle(circle, (255, 255, 255), (200, 200), 150)
circle_rect = circle.get_rect(topleft = (400, 100))
circle.set_colorkey((0, 0, 0))
circle_mask = pygame.mask.from_surface(circle)

gun = pygame.Surface((100, 40))
gun_rect = gun.get_rect(topleft = (200, 750))
gun.fill("red")

bullets = pygame.sprite.Group()

while True:
    mx, my = pygame.mouse.get_pos()
    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

        if e.type == pygame.MOUSEBUTTONDOWN:
            b = Bullet(mx, 750)
            bullets.add(b)

    screen.fill((30, 30, 30))


    gun_rect.centerx = mx
    screen.blit(gun, gun_rect)
    screen.blit(circle, circle_rect)

    bullets.update(circle_rect.x, circle_rect.y, circle_mask)
    bullets.draw(screen)

    pygame.display.flip()
    clock.tick(120)

the issue is the bullet is getting straight through the circle, if you could explain how vector works that would be great. vid


Solution

  • Change the position by both components of the direction vector

    self.rect.center += self.velocity
    

    Detect the collision of the circle with the bullet, not the other way around:

    offset = self.rect.x - circle_rect.x, self.rect.y - circle_rect.y
    if p := circle_mask.overlap(self.mask, offset):
    

    The reflection vector is the vector from the center of the circle the the hit position:

    v = pygame.Vector2(p) - (circle_rect.width/2, circle_rect.height/2)
    

    Complete update method:

    class Bullet(pygame.sprite.Sprite):
        # [...]
    
        def update(self, circle_rect, circle_mask):
            if self.rect.y < 0 or self.rect.y > 800:
                self.kill()
            if self.rect.x < 0 or self.rect.x > 1200:
                self.kill()
    
            self.rect.center += self.velocity
            offset = self.rect.x - circle_rect.x, self.rect.y - circle_rect.y
            if p := circle_mask.overlap(self.mask, offset):
                v = pygame.Vector2(p) - (circle_rect.width/2, circle_rect.height/2)
                self.velocity.reflect_ip(v)
                
    # [...]
    
    while True:
        # [...]
    
        bullets.update(circle_rect, circle_mask)
    
        # [...]