简体   繁体   中英

Pygame lags with pygame rectangles added

I'm running a python game with approximately 2k rectangles being drawn on screen each frame. 游戏

The problem I have is that it runs on 12 fps and I have no idea how to fix that. When I remove all the rectangles it shifts to 100 fps. I'm not rendering all of them at once, but only the ones camera can see currently. How to fix this lag spike issue, is it because I'm using pygame rectangles or because I'm using them wrong?

here's the code

import pygame
black = (0,0,0)
pygame.init()
gameDisplay = pygame.display.set_mode((0,0),pygame.FULLSCREEN)
gameDisplay.fill(black)
gameDisplay.convert()
clock = pygame.time.Clock()
display_width = 1920
display_height = 1080

from opensimplex import OpenSimplex
tmp = OpenSimplex()

dimensions = [100,100]
size = 40

def mapping(x):
    y = (x + 1) * 10 + 40
    return y

class GroundCell:
    def __init__(self,x,y,dim):
        self.x = x
        self.y = y
        self.dim = dim

tempcells = []
allCells = []

for a in range(0,dimensions[0]):
    tempcells = []
    for b in range(0,dimensions[1]):
        tempcells.append(GroundCell(a*size,b*size,mapping(tmp.noise2d(a*0.11,b*0.11))))
    allCells.append(tempcells)

font = pygame.font.Font("freesansbold.ttf", 20)
while True:
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                pygame.quit()
                quit()
    for a in allCells:
        for b in a:
            if b.x in range(0,display_width) \
                and b.y in range(0,display_height) or\
                b.x+size in range(0,display_width) \
                and b.y+size in range(0,display_height) :
                pygame.draw.rect(gameDisplay,(b.dim,b.dim,b.dim),(b.x,b.y,size,size))

    fps = font.render('FPS: ' + str(int(clock.get_fps())), 1, (0, 0, 0))
    gameDisplay.blit(fps, (20, 20))

    pygame.display.update()
    clock.tick(120)
    gameDisplay.fill(black)

As @sloth explained, there are better ways to do the same, but if you want to know what the actual problems are:

  • You're not drawing 2000 rectangles, you're drawing 10.000 of them, because your dimension is 100x100

  • You're doing a check if the rectangle is visible in the worst possible way in terms of performance. Just check what happens if you leave the check and don't draw the rectangles. You'll see that the performance is improved but it's still far from 120fps. That's because for every rectangle you generate a list of numbers from 0 to the width of the screen and another list from zero to the height of the screen. You also do that twice. That means, on a 1920x1080 screen: (1920 * 10000) + (1920 * 10000) + (1080 * 10000) + (1080*10000) = 60000000 . So, 60 million checks. If you have 120fps, that means 60 million * 120 = 7.2 billion checks per second.

Just changing the check to something similar to if b.x+size < display_width and b.y+size < display_height and bx > 0 and by > 0: will already improve the performance.

That said, it's still 10000 rectangles, and it's still 120fps, which means 1200000 rectangles per second, done with basically no HW acceleration and with a high level language. Don't expect top performance.

Instead of drawing all those rects to the screen every tick, create a Surface with the noise once , and reuse it.

Some more notes:

Text rendering is expensive. If you use a lot of text rendering, better cache the surfaces created by Font.render . Note there's also the new freetype module in pygame, which is better than the font module in every way.

Your check if a rect is inside the screen is very strange. You could just use something like if 0 < bx < display_width ... or even use pygame's Rect class, which offers nice methods like contains() .

A good and fast way to create a Surface from arbitary data is to use numpy and pygame's surfarray module. Don't get intimidated, it isn't that hard to use.

Here's a running example based on your code:

import pygame
import numpy as np

black = (0,0,0)
pygame.init()
display_width = 1000
display_height = 1000
gameDisplay = pygame.display.set_mode((display_width, display_height))
gameDisplay.fill(black)
clock = pygame.time.Clock()

from opensimplex import OpenSimplex
tmp = OpenSimplex()

dimensions = [100,100]
size = 16

def mapping(x):
    y = (x + 1) * 10 + 40
    return y

# create an 2d array from the noise
def get_array():
    rgbarray = np.zeros((dimensions[0], dimensions[1]))
    for x in range(dimensions[0]):
        for y in range(dimensions[1]):
            c = int(mapping(tmp.noise2d(x*0.11, y*0.11)))
            # simple way to convert the color value to all three (r,g,b) channels
            rgbarray[x, y] = c | c << 8 | c << 16 
    return rgbarray

# create the array and copy it into a Surface
data = get_array()
surface = pygame.Surface((dimensions[0], dimensions[1]))
pygame.surfarray.blit_array(surface, data)

# scale the Surface to the desired size
scaled = pygame.transform.scale(surface, (dimensions[0]*size, dimensions[1]*size))

# simple way to cache font rendering
font = pygame.font.Font("freesansbold.ttf", 20)
cache = {}
def render(text):
    if not text in cache:
        cache[text] = font.render(text, 1, (0, 0, 0))
    return cache[text]

x = 0
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                running = False

    x-=1
    if x < -1000:
        x = 0

    gameDisplay.blit(scaled, (x, 0))
    fps = render('FPS: ' + str(int(clock.get_fps())))
    gameDisplay.blit(fps, (20, 20))
    pygame.display.update()
    clock.tick(120)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM