Search code examples
pythonpygameframeworkscollision-detection

pygame collision detection not working as expected


I am coding my own framework building upon pygame and i added some functionality for movement and collisions. The framework looks like this:

import pygame

def check_collisions(obj1, objects):
    collisions = []
    for obj in objects:
        if obj.colliderect(obj1):
            collisions.append(obj)
    return collisions

def blit_center(surface, object, display):
    display.blit(surface, [object.x/surface.get_width(), object.y/surface.get_height()])

class PhysicsObject(object):
    def __init__(self, x, y, width, height, vel_x=0, vel_y=0):
        self.width = width
        self.height = height
        self.x = x
        self.y = y
        self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
        self.velocity = pygame.Vector2(vel_x, vel_y)
    
    def move(self, environment):
        collisions = {
            'top': False,
            'bottom': False,
            'left': False,
            'right': False,
        }

        self.x += self.velocity.x
        self.rect.x = int(self.x)
        collisions_checked = check_collisions(self.rect, environment)
        for obj in collisions_checked:
            if self.velocity.x > 0:
                self.rect.right = obj.left
                collisions['right'] = True
            elif self.velocity.x < 0:
                self.rect.left = obj.right
                collisions['left'] = True
        
        self.y += self.velocity.y
        self.rect.y = int(self.y)
        collisions_checked = check_collisions(self.rect, environment)
        for obj in collisions_checked:
            if self.velocity.y > 0:
                self.rect.bottom = obj.top
                collisions['bottom'] = True
            elif self.velocity.y < 0:
                self.rect.top = obj.bottom
                collisions['top'] = True
        return collisions

            

class Entity(PhysicsObject):
    def __init__(self, x, y, width, height, vel_x=0, vel_y=0):
        super().__init__(x, y, width, height, vel_x, vel_y)
        self.surf = pygame.Surface((self.width, self.height))
        self.surf.fill('red')
        self.colorkey = (0, 0, 0)
        self.surf.set_colorkey(self.colorkey)

    def set_image(self, image):
        if image is str:
            self.surf = pygame.image.load(image)
        else:
            self.surf = image
    
    def set_color(self, color):
        self.surf.fill(color)
    
    def set_size(self, width=None, height=None):
        if width is not None:
            self.width = int(width)
        if height is not None:
            self.height = int(height)

    def display(self, display):
        display.blit(self.surf, [self.x-self.width/2, self.y-self.height/2])

now in my test file i wanted to test this functionality but when i move my player into the obstacle it doesnt stop. It just moves through it. why is this? and how can i fix this.

also my test script looks like this:

import pygame
from pygame.locals import *
import sys
from gameFrame import *

pygame.init()

display_size = [500, 500]
display = pygame.display.set_mode(display_size)
clock = pygame.time.Clock()

player = Entity(32, 32, 32, 32)
obstacle = Entity(200, 200, 200, 32)

while True: # Main gameloop
    for event in pygame.event.get():
        if event.type == pygame.QUIT: # Prevent frozen program
            pygame.quit()
            sys.exit()
    display.fill([70, 120, 255]) # Make Background
    keys = pygame.key.get_pressed()

    player.velocity.xy = 0, 0
    if keys[K_w]:
        player.velocity.y = -5
    if keys[K_s]:
        player.velocity.y = 5
    if keys[K_a]:
        player.velocity.x = -5
    if keys[K_d]:
        player.velocity.x = 5

    player.display(display)
    obstacle.display(display)
    player.move([obstacle.rect])

    pygame.display.update() # Update the display
    clock.tick(60)

Solution

  • It is not enough to constrain the position of the rectangle (self.rect.bottom, self.rect.left). If you change self.rect.bottom or self.rect.left you must set self.x as well, since self.rect.x is set again by self.x in the next frame:

    class PhysicsObject(object):
        # [...]
    
        def move(self, environment):
            collisions = {'top': False, 'bottom': False, 'left': False, 'right': False,}
    
            self.x += self.velocity.x
            self.rect.x = int(self.x)
            collisions_checked = check_collisions(self.rect, environment)
            for obj in collisions_checked:
                if self.velocity.x > 0:
                    self.rect.right = obj.left
                    self.x = self.rect.x                      # <---
                    collisions['right'] = True
                elif self.velocity.x < 0:
                    self.rect.left = obj.right
                    self.x = self.rect.x                      # <---
                    collisions['left'] = True
            
            self.y += self.velocity.y
            self.rect.y = int(self.y)
            collisions_checked = check_collisions(self.rect, environment)
            for obj in collisions_checked:
                if self.velocity.y > 0:
                    self.rect.bottom = obj.top
                    self.y = self.rect.y                      # <---
                    collisions['bottom'] = True
                elif self.velocity.y < 0:
                    self.rect.top = obj.bottom
                    self.y = self.rect.y                      # <---
                    collisions['top'] = True
            return collisions