Search code examples
pythonpython-3.xpygamegame-physics

Pygame low frame rate on simple game


I'm making a sandbox game/simulation which looks like powdergame from danball.com except way simpler.

My game lags when there is a certain amount of squares that are spawned

don't pay attention to the comments

import pygame
import time
import random

pygame.init()

clock = pygame.time.Clock()

fps = 120

wnx = 800
wny = 600

black = (0,0,0)
grey = (80,80,80)
white = (255,255,255)
black_transparent = (255,255,255,128)
red = (255,0,0)
BACKGROUNDCOLOR = (40,40,40)

#__ Elements __

sand = (255,160,50)
rock = (125,125,125)
bsand = (255,180,150)
brock = (180,180,180)
dirt = (110, 45, 0)
bdirt = (200, 90, 0)
water = (0, 150, 255)
bwater = (25, 200, 255)
wall = (100,100,100)
bwall = (140,140,140)

erase = False

Onbutton = False
color = sand
cubec = sand

wn = pygame.display.set_mode((wnx, wny))
wn.fill(white)

def cursor(cux,cuy,cuw):
    boxc = pygame.draw.rect(wn, black, [cux, cuy, cuw, cuw], 1)                    


def message(Font,Size,colort,xt,yt,text):
    font = pygame.font.SysFont('freepixelregular', Size, True)
    text = font.render(text, True, colort)
    wn.blit(text, (xt, yt))



def cube(cx,cy,cw,ch,cubec):
    pygame.draw.rect(wn, cubec, [cx, cy, cw, ch])


def floor(fx,fy,fw,fh):
    pygame.draw.rect(wn, grey, [fx, fy, fw, fh])
    pygame.draw.line(wn, black, (150,504), (800, 504), 10)

def sidebar(sx,sy,sw,sh):
    pygame.draw.rect(wn, grey, [0, 0, 150, 600])
    pygame.draw.line(wn, black, (154,0), (154, 500), 10)


def button(bx, by, bw, bh, text, abcol, bcol, colorchange):

    global color

    mouse = pygame.mouse.get_pos()
    click = pygame.mouse.get_pressed()


    if bx+bw > mouse[0] > bx and by+bh > mouse[1] > by:
        Onbutton = True
        pygame.draw.rect(wn, abcol, [bx, by, bw, bh])
        if click[0] == 1 and colorchange != None:
            color = colorchange

    else:
        pygame.draw.rect(wn, bcol, [bx, by, bw, bh])
        Onbutton = False





    font = pygame.font.SysFont('freepixelregular', 25,True)
    text = font.render(text, True, black)
    wn.blit(text, (bx + (bw/14), by + (bh/4)))


def main():


    number = 0

    toggle_fast = False
    erase = False


    cubex = [0] * number
    cubey = [0] * number
    cubec = [0] * number
    cubew = 10  #cube size
    cubeh = cubew


    floory = 500

    gravity = (cubew*-1)


    clickt = False

    exit = False



    while not exit:



        #________________ QUIT ________________________________________

        for event in pygame.event.get():

            if event.type == pygame.QUIT:
                exit = True

            if event.type == pygame.KEYDOWN:

                if event.key == pygame.K_SPACE:
                    toggle_fast = not toggle_fast
                if event.key == pygame.K_v:
                    erase = not erase


        #_____________________ Click / spawn cube / erase cube _____________________________

            mouse = pygame.mouse.get_pos()
            click = pygame.mouse.get_pressed()

            if toggle_fast == False:

                if event.type == pygame.MOUSEBUTTONDOWN:
                    if mouse[1] < floory and mouse[0] >= 154:



                        cubex.append(round((mouse[0]/cubew),0)*cubew)
                        cubey.append(round((mouse[1]/cubew),0)*cubew)
                        cubec.append(color)





            if click[0] == 1 and toggle_fast == True:
                print(erase)
                if mouse[1] < floory and mouse[0] >= 154:



                    cubex.append(round((mouse[0]/cubew),0)*cubew)
                    cubey.append(round((mouse[1]/cubew),0)*cubew)
                    cubec.append(color)




        #_____________________ GRAVITY _____________________________

        for i in range(len(cubex)):
            cubeR = pygame.Rect(cubex[i], cubey[i] + cubew, cubew, cubeh)
            cisect = [j for j in range(len(cubey)) if j != i and cubeR.colliderect(pygame.Rect(cubex[j], cubey[j], cubew, cubeh))]
            watercheck = [j for j in range(len(cubey)) if j != i and cubec[i] != (0, 150, 255) and cubec[j] == (0, 150, 255) and cubeR.colliderect(pygame.Rect(cubex[j], cubey[j], cubew, cubeh))]

            if not any(cisect) and not (cubey[i] + cubew) >= floory:
                if not cubec[i] == (100,100,100):
                    cubey[i] -= gravity



            #for j in range(len(cubex):
            #   if any(watercheck):
            #       if not (cubey[i] + cubew) >= floory or any(cisect):
        #               oldposy = CUBEINFO[i][1] 
        #               oldposx = CUBEINFO[i][0]

        #               CUBEINFO.append(oldposx, oldposy, (0, 150, 255))

                        #cubex.append(oldposx)
                        #cubey.append(oldposy)
                        #cubec.append((0, 150, 255))



        #________water physics___________   

            cubeRxr = pygame.Rect(cubex[i] - cubew, cubey[i], cubew, cubeh)
            cubeRxl = pygame.Rect(cubex[i] + cubew, cubey[i], cubew, cubeh)
            cubeRdiagr = pygame.Rect(cubex[i] + 10, cubey[i] + 10, cubew, cubeh)
            cubeRdiagl = pygame.Rect(cubex[i] - 10, cubey[i] + 10, cubew, cubeh)
            cisectx = [j for j in range(len(cubex)) if j != i and cubeRxr.colliderect(pygame.Rect(cubex[j], cubey[j], cubew, cubeh))]
            cisectxl = [j for j in range(len(cubex)) if j != i and cubeRxl.colliderect(pygame.Rect(cubex[j], cubey[j], cubew, cubeh))]
            cisectdr = [j for j in range(len(cubex)) if j != i and cubeRdiagr.colliderect(pygame.Rect(cubex[j], cubey[j], cubew, cubeh))] 
            cisectdl = [j for j in range(len(cubex)) if j != i and cubeRdiagl.colliderect(pygame.Rect(cubex[j], cubey[j], cubew, cubeh))]



            if cubec[i] == (0, 150, 255):

                if (cubey[i] + cubew) >= floory or any(cisect):  # on ground


                    if not (cubex[i] + cubew) >= 800 and not cubex[i] <= 164:


                        if any(cisectx) and not any(cisectxl):                                              #going right because of right wall
                            cubex[i] += 10
                        elif any(cisectxl) and not any(cisectx):                                                #going left because of left wall
                            cubex[i] -= 10

                        elif any(cisectx) and any(cisectxl):
                            cubex[i] += 0

                        elif any(cisect) or (cubey[i] + cubew) >= floory:

                            negative = [-10, 10]
                            cubex[i] += random.choice(negative)

                        elif any(cisect) and not any(cisectdl) and not any(cisectdr):
                            negative = [-10, 10]
                            cubex[i] += random.choice(negative) 




        #____________________ Element _____________________________

        #_____________________ DRAW _____________________________

        wn.fill(BACKGROUNDCOLOR)

        floor(0,floory,800,100)
        sidebar(0, 0, 150, 600)

        for i in range(len(cubex)):
            cube(cubex[i], cubey[i], cubew, cubeh, cubec[i])

        cursor(round((mouse[0]/cubew),0)*cubew, round((mouse[1]/cubew),0)*cubew, cubew,)

        button(20, 40, 50, 40, 'RCK', brock, rock, (125,125,125))
        button(20, 85, 50, 40, 'SND', bsand, sand, (255,160,50))
        button(20, 130, 50, 40, 'DRT', bdirt, dirt, (110, 45, 2))
        button(20, 175, 50, 40, 'WTR', bwater, water, (0, 150, 255))
        button(20, 220, 50, 40, 'WLL', bwall, wall, (100,100,100))

        #(Font,Size,colort,xt,yt,message):
        message(None, 20, black, 10,400,('ERASE:'+str(erase)))

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

main()

pygame.quit()
quit()

Maybe it is because of the fact that the squares positions (refered as cubex,cubey) are in separate lists or something?

im just starting out with python so it can be a silly error

thanks for the help!


Solution

  • The only way to speed up your game is to avoid the continuously searching in the array of cubes.

    To achieve this you have to change the representation of your data. You have to think the problem from the other direction. Don't search a cube at a position, but "ask" a position if a cube is on it. Instead of storing the cubes in lists, create a 2 dimensional playground grid and associate a cube to a field in the grid.

    Create and object for a cube (the attribute '.dir' is for the water and explained later):

    class Cube:
        def __init__(self, color):
            self.color = color
            self.dir = 1
    

    Create the empty playground (each filed is initialized by None):

    cubew = 10  #cube size
    cubeh = cubew
    
    pg_rect = pygame.Rect(160, 0, 650, 500)
    pg_size = (pg_rect.width // cubew, pg_rect.height // cubeh)
    pg_grid = [[None for i in range(pg_size[1])] for j in range(pg_size[0])]
    

    On mouse click a cube can be added to the playground with ease:

    if event.type == pygame.MOUSEBUTTONDOWN:
        if mouse[1] < floory and mouse[0] >= 154: 
    
            i, j = ((mouse[0]-pg_rect.left) // cubew, (mouse[1]-pg_rect.top) // cubeh)
            if not pg_grid[i][j]:
                pg_grid[i][j] = Cube(color)
    

    To draw the cubes the entire field has to be traversed:

    for i in range(pg_size[0]):
        for j in range(pg_size[1]):
            if pg_grid[i][j]:
                pos = (pg_rect.left + i * cubew, pg_rect.top + j * cubeh)
                cube(*pos, cubew, cubeh, pg_grid[i][j].color)
    

    For the update of positions of the cubes (gravity, water), all the cubes have to be listed and the location of the cube in the filed has to be changed.

    cubes = [(i, j) for i in range(pg_size[0]) for j in range(pg_size[1]-1, 0, -1) if pg_grid[i][j]]
    for i, j in cubes:
    
        # [...]
    

    For gravity it has to be checked if the filed below the cube is "free" (None):

    fall_down = pg_grid[i][j].color != wall
    if fall_down and j < pg_size[1]-1 and pg_grid[i][j+1] == None:
    
        pg_grid[i][j+1] = pg_grid[i][j]
        pg_grid[i][j] = None
    

    And for the water effect it has to be checked if the filed beside the cube, which is identified by self.dir is "free", If it is "free", then the cube steps forward. Else its direction has to be changed:

    is_water = pg_grid[i][j].color == water
    if is_water:
    
        if pg_grid[i][j].dir < 0:
            if i <= 0 or pg_grid[i-1][j]:
                pg_grid[i][j].dir = 1
            else:
                pg_grid[i-1][j] = pg_grid[i][j]
                pg_grid[i][j] = None
    
        else:
            if i >= pg_size[0]-1 or pg_grid[i+1][j]:
                pg_grid[i][j].dir = -1
            else:
                pg_grid[i+1][j] = pg_grid[i][j]
                pg_grid[i][j] = None
    

    See the example, where I applied the changes to your original code:

    import pygame
    import time
    import random
    
    pygame.init()
    
    clock = pygame.time.Clock()
    
    fps = 120
    
    wnx = 800
    wny = 600
    
    black = (0,0,0)
    grey = (80,80,80)
    white = (255,255,255)
    black_transparent = (255,255,255,128)
    red = (255,0,0)
    BACKGROUNDCOLOR = (40,40,40)
    
    #__ Elements __
    
    sand = (255,160,50)
    rock = (125,125,125)
    bsand = (255,180,150)
    brock = (180,180,180)
    dirt = (110, 45, 0)
    bdirt = (200, 90, 0)
    water = (0, 150, 255)
    bwater = (25, 200, 255)
    wall = (100,100,100)
    bwall = (140,140,140)
    
    erase = False
    
    Onbutton = False
    color = sand
    cubec = sand
    
    wn = pygame.display.set_mode((wnx, wny))
    wn.fill(white)
    
    class Cube:
        def __init__(self, color):
            self.color = color
            self.dir = 1
    
    def cursor(cux,cuy,cuw):
        boxc = pygame.draw.rect(wn, black, [cux, cuy, cuw, cuw], 1)                    
    
    def message(Font,Size,colort,xt,yt,text):
        font = pygame.font.SysFont('freepixelregular', Size, True)
        text = font.render(text, True, colort)
        wn.blit(text, (xt, yt))
    
    def cube(cx,cy,cw,ch,cubec):
        pygame.draw.rect(wn, cubec, [cx, cy, cw, ch])
    
    def floor(fx,fy,fw,fh):
        pygame.draw.rect(wn, grey, [fx, fy, fw, fh])
        pygame.draw.line(wn, black, (150,504), (800, 504), 10)
    
    def sidebar(sx,sy,sw,sh):
        pygame.draw.rect(wn, grey, [0, 0, 150, 600])
        pygame.draw.line(wn, black, (154,0), (154, 500), 10)
    
    def button(bx, by, bw, bh, text, abcol, bcol, colorchange):
    
        global color
    
        mouse = pygame.mouse.get_pos()
        click = pygame.mouse.get_pressed()
    
        if bx+bw > mouse[0] > bx and by+bh > mouse[1] > by:
            Onbutton = True
            pygame.draw.rect(wn, abcol, [bx, by, bw, bh])
            if click[0] == 1 and colorchange != None:
                color = colorchange
        else:
            pygame.draw.rect(wn, bcol, [bx, by, bw, bh])
            Onbutton = False
    
        font = pygame.font.SysFont('freepixelregular', 25,True)
        text = font.render(text, True, black)
        wn.blit(text, (bx + (bw/14), by + (bh/4)))
    
    
    def main():
    
        number = 0
    
        toggle_fast = False
        erase = False
    
        cubew = 10  #cube size
        cubeh = cubew
    
        pg_rect = pygame.Rect(160, 0, 650, 500)
        pg_size = (pg_rect.width // cubew, pg_rect.height // cubeh)
        pg_grid = [[None for i in range(pg_size[1])] for j in range(pg_size[0])]
    
        floory = 500
        gravity = (cubew*-1)
        clickt = False
        exit = False
    
        while not exit:
            #________________ QUIT ________________________________________
            for event in pygame.event.get():
    
                if event.type == pygame.QUIT:
                    exit = True
    
                if event.type == pygame.KEYDOWN:
    
                    if event.key == pygame.K_SPACE:
                        toggle_fast = not toggle_fast
                    if event.key == pygame.K_v:
                        erase = not erase
            #_____________________ Click / spawn cube / erase cube _____________________________
    
                mouse = pygame.mouse.get_pos()
                click = pygame.mouse.get_pressed()
    
                if toggle_fast == False:
    
                    if event.type == pygame.MOUSEBUTTONDOWN:
                        if mouse[1] < floory and mouse[0] >= 154: 
    
                            i, j = ((mouse[0]-pg_rect.left) // cubew, (mouse[1]-pg_rect.top) // cubeh)
                            if not pg_grid[i][j]:
                                pg_grid[i][j] = Cube(color)
    
                if click[0] == 1 and toggle_fast == True:
                    print(erase)
                    if mouse[1] < floory and mouse[0] >= 154:
    
                            i, j = ((mouse[0]-pg_rect.left) // cubew, (mouse[1]-pg_rect.top) // cubeh)
                            if not pg_grid[i][j]:
                                pg_grid[i][j] = Cube(color)
    
            # update cubes
            cubes = [(i, j) for i in range(pg_size[0]) for j in range(pg_size[1]-1, 0, -1) if pg_grid[i][j]]
            for i, j in cubes:
    
                        fall_down = pg_grid[i][j].color != wall
                        is_water = pg_grid[i][j].color == water
    
                        if fall_down and j < pg_size[1]-1 and pg_grid[i][j+1] == None:
                            #_____________________ GRAVITY _____________________________
                            pg_grid[i][j+1] = pg_grid[i][j]
                            pg_grid[i][j] = None
    
                        elif is_water:
                            #________water physics___________ 
                            if pg_grid[i][j].dir < 0:
                                if i <= 0 or pg_grid[i-1][j]:
                                    pg_grid[i][j].dir = 1
                                else:
                                    pg_grid[i-1][j] = pg_grid[i][j]
                                    pg_grid[i][j] = None
    
                            else:
                                if i >= pg_size[0]-1 or pg_grid[i+1][j]:
                                    pg_grid[i][j].dir = -1   
                                else:
                                    pg_grid[i+1][j] = pg_grid[i][j]
                                    pg_grid[i][j] = None
    
            wn.fill(BACKGROUNDCOLOR)
    
            floor(0,floory,800,100)
            sidebar(0, 0, 150, 600)
    
            # draw cubes
            for i in range(pg_size[0]):
                for j in range(pg_size[1]):
                    if pg_grid[i][j]:
                        pos = (pg_rect.left + i * cubew, pg_rect.top + j * cubeh)
                        cube(*pos, cubew, cubeh, pg_grid[i][j].color)
    
            cursor(round((mouse[0]/cubew),0)*cubew, round((mouse[1]/cubew),0)*cubew, cubew,)
    
            button(20, 40, 50, 40, 'RCK', brock, rock, (125,125,125))
            button(20, 85, 50, 40, 'SND', bsand, sand, (255,160,50))
            button(20, 130, 50, 40, 'DRT', bdirt, dirt, (110, 45, 2))
            button(20, 175, 50, 40, 'WTR', bwater, water, (0, 150, 255))
            button(20, 220, 50, 40, 'WLL', bwall, wall, (100,100,100))
    
            #(Font,Size,colort,xt,yt,message):
            message(None, 20, black, 10,400,('ERASE:'+str(erase)))
    
            pygame.display.update()
            clock.tick(fps)
    
    main()
    
    pygame.quit()
    quit()