My collision detection isn't working and way to laggy

Question:

So here’s just the base function:

    def xCollision(self):
        for tile in tiles:
            if tile.colliderect(self.rect):
                if self.x_vel >= 1:
                    self.rect.right = tile.left
                elif self.x_vel <= -1:
                    self.rect.left = tile.right

    def yCollision(self):
        for tile in tiles:
            if tile.colliderect(self.rect):
                if self.y_vel >= 1:
                    self.rect.bottom = tile.top
                elif self.y_vel <= -1:
                    self.rect.top = tile.bottom

This is inside the Player class.

Now, when i try this my frames go down to around 4 fps and it also isn’t working.
About it not working it has to do something with the self.rect.something = tile.something because when i replace it with print() it prints whatever.

Here is the entire code for reference, And yes i just started out so don’t think its gonna be any good ok?

import pygame
import random
import math
import time




# Initialize Pygame
pygame.init()
pygame.mixer.init()

# Set the size of the window
size = (900, 600)
screen = pygame.display.set_mode(size)

# Set the title of the window
pygame.display.set_caption("Classes testing")

clock = pygame.time.Clock()
fps = 60

pygame.mixer.music.load('PewPewProject/Retro Platform.wav')
pygame.mixer.music.set_volume(0.1)
pygame.mixer.music.play(-1)

pop_sound = pygame.mixer.Sound("PewPewProject/vine-boom.wav")
pop_sound.set_volume(0.5)




level = [
    ['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1'],
    ['1','E','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','E','0','0','0','1'],
    ['1','0','0','0','1','1','1','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','0','0','1','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','1','0','1','0','0','0','0','0','0','0','0','E','0','0','0','0','1'],
    ['1','0','0','1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','E','1','1','1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','E','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','1','1','1','0','1','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','1','0','0','0','1','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','1','0','1','1','1','0','E','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','1','0','1','S','1','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','1','0','0','0','1','0','0','0','0','0','1'],
    ['1','0','0','E','0','0','0','0','0','1','1','1','1','1','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','1'],
    ['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1'],
    ]


tilex, tiley = [0, 0]
tilex_offset, tiley_offset = [0, 0]
tile_size = 50
tilemap = pygame.image.load('PewPewProject/tilemap.png')
tiles = []
collision_tiles = []
enemy_spawns = []



def Build_Level():
    global tiley,tilex
    tiley = -1
    for rows in level:
        tilex = -1
        tiley += 1
        for tile in rows:
            tilex += 1
            if tile == '1':  
                tile_rect = pygame.draw.rect(screen, (100, 100, 100), (tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size))
                tiles.append(tile_rect)
            elif tile == '0':
                pass
            elif tile == 'E':
                enemy_spawns.append([tilex, tiley])
            elif tile == 'S':
                pygame.draw.rect(screen, (50, 255, 100), (tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size))
            elif tile == 'R':
                pygame.draw.rect(screen, (255, 50, 50), (tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size))
            else:
                print("Wrong Map Input")






def random_except(start, end, exception_start, exception_end):
    num = random.randint(start, end)
    while num >= exception_start and num <= exception_end:
        num = random.randint(start, end)
    return num





class Player:
    def __init__(self, health, speed, x, y, size, healthboost, collision_tiles):
        self.speed = speed
        self.x = x
        self.y = y
        self.y_vel = 0
        self.x_vel = 0
        self.size = size
        self.hitbox = pygame.draw.rect(screen, (230, 100, 100), (self.x, self.y, self.size, self.size))
        self.gunsurface = pygame.Surface((90, 25), pygame.SRCALPHA)
        self.healthboost = healthboost
        self.health = health * healthboost
        self.rotated_gunsurface = self.gunsurface
        self.gunsurface.fill((150, 150, 150))
        self.gunrect = self.gunsurface.get_rect()
        self.rect = self.hitbox
        self.projectiles = [Projectile(-100, 0, 0)]
        self.last_shot_time = 0
        self.cooldown = 0.5
        self.is_slowed = False

        self.alive = True
    

    def xCollision(self):
        for tile in tiles:
            if tile.colliderect(self.rect):
                if self.x_vel >= 1:
                    self.rect.right = tile.left
                elif self.x_vel <= -1:
                    self.rect.left = tile.right

    def yCollision(self):
        for tile in tiles:
            if tile.colliderect(self.rect):
                if self.y_vel >= 1:
                    self.rect.bottom = tile.top
                elif self.y_vel <= -1:
                    self.rect.top = tile.bottom
    

    def Render(self):
        self.rect = pygame.draw.rect(screen, (230, 100, 100), (self.x, self.y, self.size, self.size))
        self.Gun()

    def Alive(self):
        if self.health <= 0:
            self.alive = False



    def GUI(self):
        self.empty_health_bar = pygame.draw.rect(screen, (200, 20, 20), (screen.get_width()/4, 10,466, 25))
        self.health_bar = pygame.draw.rect(screen, (20, 200, 20), (screen.get_width()/4, 10, self.health*4.66/self.healthboost, 25))
        pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4, 10),(screen.get_width()/4+466, 10), 2)
        pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4, 35),(screen.get_width()/4+466, 35), 2)
        pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4, 10), (screen.get_width()/4, 35), 2)
        pygame.draw.line(screen, (0, 0, 0), (screen.get_width()/4+466, 10), (screen.get_width()/4+466, 35), 2)

    
    
    


    
    def Gun(self):
        cursor_x, cursor_y = pygame.mouse.get_pos()
        dx = cursor_x - (self.x + self.size)
        dy = cursor_y - (self.y + self.size)
        angle = math.atan2(dy, dx)
        self.rotated_gunsurface = pygame.transform.rotate(self.gunsurface, math.degrees(angle * -1))
        self.gunrect = self.rotated_gunsurface.get_rect(center = (self.x+self.size/2, self.y+self.size/2))
        screen.blit(self.rotated_gunsurface, (self.gunrect.x, self.gunrect.y))
    def Shoot(self):
        if pygame.key.get_pressed()[pygame.K_e]:
            current_time = time.time()
            if current_time - self.last_shot_time > self.cooldown:
                pop_sound.play()
                cursor_x, cursor_y = pygame.mouse.get_pos()
                dx = cursor_x - (self.x + self.size/2)
                dy = cursor_y - (self.y + self.size/2)
                angle = math.atan2(dy, dx)
                self.projectiles.append(Projectile(self.x+self.size/2, self.y+self.size/2, angle))
                self.last_shot_time = current_time
                self.cooldown = 0.1

    
    def Movement(self):
        global tiley_offset, tilex_offset
        keys = pygame.key.get_pressed()

        
        if self.x >= screen.get_width()-100-self.size: # Move to the RIGHT
            if keys[pygame.K_d]:
                tilex_offset -= self.speed
            if keys[pygame.K_a]:
                self.x_vel = -self.speed

            if not self.y <= 100:
                if keys[pygame.K_w]:
                    self.y_vel = -self.speed
            else:
                if keys[pygame.K_w]:
                    tiley_offset += self.speed
            
            if not self.y >= screen.get_height()-100-self.size:
                if keys[pygame.K_s]:
                    self.y_vel = self.speed
            else:
                if keys[pygame.K_s]:
                    tiley_offset -= self.speed



        elif self.x <= 100: # Move to the LEFT
            if keys[pygame.K_a]:
                tilex_offset += self.speed
            if keys[pygame.K_d]:
                self.x_vel = self.speed

            if not self.y <= 100:
                if keys[pygame.K_w]:
                    self.y_vel = -self.speed
            else:
                if keys[pygame.K_w]:
                    tiley_offset += self.speed

            if not self.y >= screen.get_height()-100-self.size:
                if keys[pygame.K_s]:
                    self.y_vel = self.speed
            else:
                if keys[pygame.K_s]:
                    tiley_offset -= self.speed
        

        elif self.y >= screen.get_height()-100-self.size: # Move DOWN
            if keys[pygame.K_s]:
                tiley_offset -= self.speed
            if keys[pygame.K_w]:
                self.y_vel = -self.speed
            if keys[pygame.K_a]:
                self.x_vel =- self.speed
            if keys[pygame.K_d]:
                self.x_vel = self.speed
        
        elif self.y <= 100: # Move UP
            if keys[pygame.K_w]:
                tiley_offset += self.speed
            if keys[pygame.K_s]:
                self.y_vel = self.speed
            if keys[pygame.K_a]:
                self.x_vel = -self.speed
            if keys[pygame.K_d]:
                self.x_vel = self.speed

        else: # Default Movement
            if keys[pygame.K_w]:
                self.y_vel = -self.speed
            if keys[pygame.K_s]:
                self.y_vel = self.speed
            if keys[pygame.K_a]:
                self.x_vel = -self.speed
            if keys[pygame.K_d]:
                self.x_vel = self.speed



        self.x += self.x_vel
        self.xCollision()
        self.y += self.y_vel
        self.yCollision()

       
        
        self.y_vel = 0
        self.x_vel = 0

        



class Projectile:
    def __init__(self, x, y, angle):
        self.x = x
        self.y = y
        self.angle = angle
        self.speed = 8
        self.hitbox = pygame.Rect(self.x, self.y, 5,5)

    
    def Move(self):
        self.x += math.cos(self.angle) * self.speed
        self.y += math.sin(self.angle) * self.speed
        
    def Render(self):
        self.hitbox = pygame.draw.circle(screen, (255, 255, 0), (int(self.x), int(self.y)), 7)
    




class Enemy:
    def __init__(self, health, main_speed, tag, size, player, x, y):
        self.health = health
        self.main_speed = 3
        self.tag = tag
        self.size = size
        self.player = player
        self.speed = 3

        self.x = x
        self.y = y

        self.alive = True

    def Destroy(self):
        self.alive = False
    
    def Spawning(self):
        self.hitbox = pygame.draw.rect(screen, (100, 240, 100), (self.x + tilex_offset, self.y + tiley_offset, self.size, self.size))

    def Movement(self):
        dx = self.player.x - self.x
        dy = self.player.y - self.y
        angle = math.atan2(dy - tiley_offset, dx - tilex_offset)

        self.x += self.speed * math.cos(angle)
        self.y += self.speed * math.sin(angle)
        
    





def main():
    player = Player(100, 3, screen.get_width()/2-50, screen.get_height()/2-50, 50, 1, tiles)

    enemies = []
    def Spawn_enemies(num_enemies, player):
        for i in range(num_enemies):
            x, y = random.choice(enemy_spawns)
            enemy = Enemy(1, 1, i, 25, player, x*tile_size, y*tile_size)
            enemies.append(enemy)
            print(x, y)



    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
        # Main Loop
        screen.fill((100, 100, 200))

        # usage 
        spawn_enemies_chance = random.randint(0, 200)
        number_of_enemies = len(enemies)
        if spawn_enemies_chance == 1 and number_of_enemies <= 4:
            Spawn_enemies(1,player)
        Build_Level()
        # enemy
        for enemy in enemies:
            if enemy.alive:
                enemy.Movement()
                enemy.Spawning()

                for projectile in player.projectiles:
                    if enemy.hitbox.colliderect(projectile.hitbox):
                        enemy.health -= 1
                        pop_sound.play()
                        if enemy.health <= 0:
                            enemy.alive = False
                            enemies.remove(enemy)
                        player.projectiles.remove(projectile)

        keys = pygame.key.get_pressed()
        if keys[pygame.K_LSHIFT]: # Slow things down by 3 times
            player.is_slowed = True
            for enemy in enemies:
                enemy.speed = enemy.main_speed / 3
            for projectile in player.projectiles:
                projectile.speed = 2.6
            player.speed = 1
            player.cooldown = 1.5
            spawn_enemies_chance = random.randint(0, 1500)
        else:
            player.is_slowed = False
            for enemy in enemies:
                enemy.speed = enemy.main_speed
            for projectile in player.projectiles:
                projectile.speed = 8
            player.speed = 3
            player.cooldown = 0.5
            spawn_enemies_chance = random.randint(0, 500)



        # Get Hurt
        for enemy in enemies:
            if enemy.hitbox.colliderect(player):
                player.health -= 10
                enemies.remove(enemy)
                    
        
        
        # Projectiles
        for i, projectile in enumerate(player.projectiles):
            projectile.Move()
            projectile.Render()
            if projectile.x < 0 or projectile.x > screen.get_width() or projectile.y < 0 or projectile.y > screen.get_height():
                player.projectiles.pop(i)

        # player
        player.Alive()
        player.Movement()
        player.Shoot()
        player.Render()

        # Renders the UI ontop of everything
        player.GUI()
        

        pygame.display.update()
        clock.tick(fps)

    pygame.quit()



# DO NOT DISTURB!
if __name__ == "__main__":
    main()

I honestly don’t know why its not working but i think i know why its lagging.
It has to check for collision with every tile every single frame which causes some lag if you have a big map i belive, that’s just my thought tho.

Asked By: Xseis

||

Answers:

The actual problem is not the collision detection, but that Build_Level is called in the application loop. Therefore the tiles and enemy_spawns list contain more and more elements over time and your code gets slower and slower. To solve your problem, separate the function into two functions, build_level and draw_level. Call build_level before the application loop, but draw_level in the application loop:

def build_level():
    for tiley, rows in enumerate(level):
        for tilex, tile in enumerate(rows):
            if tile == '1':
                tile_rect = pygame.Rect(tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size)  
                tiles.append(tile_rect)
            elif tile == '0':
                pass
            elif tile == 'E':
                enemy_spawns.append([tilex, tiley])
            elif tile == 'S':
                pass
            elif tile == 'R':
                pass
            else:
                print("Wrong Map Input")
def draw_level():
    for tiley, rows in enumerate(level):
        for tilex, tile in enumerate(rows):
            color = None
            if tile == '1':
                color = (100, 100, 100)
            elif tile == 'S':
                color = (50, 255, 100)
            elif tile == 'R':
                color = (255, 50, 50)
            if color:
                pygame.draw.rect(screen, color, (tilex*tile_size+tilex_offset, tiley*tile_size+tiley_offset, tile_size, tile_size))
def main():
    # [...]

    build_level()

    running = True
    while running:
        # [...]

        draw_level()
Answered By: Rabbid76