1

My pygame game runs super slowly, about 2 fps. You are supposed to be able to move around with WASD and always be centered in the screen. I know it has something to do with my tilemap and the way I do my math, but I can't pin it down. Also, if there is a better way to keep the player in the center while moving the map behind it, I would love to know how to do that.

import pygame, sys, numpy
#create fps clock
clock = pygame.time.Clock()
#
MAPHEIGHT = 80
MAPWIDTH = 80
TILESIZE =  40
TILESONSCREENW = 13
TILESONSCREENH = 13
#set screen size
SCREENH = TILESONSCREENH*TILESIZE
SCREENW = TILESONSCREENW*TILESIZE
#create character vars
circleRad = 40
circleSpeed = 4
#create circle pos vars
circleX = 250
circleY = 250
#create keyboard button vars
rightP = False
leftP = False
upP = False
downP = False
#
playerOnTileS = pygame.Surface((MAPWIDTH*TILESIZE, MAPHEIGHT*TILESIZE))
#constants for the tilemap
GRASS = pygame.image.load("grass.png")
#tilemap
tilemap = [[GRASS for i in range(MAPHEIGHT)] for j in range(MAPWIDTH)]
#create window
DISPLAYSURF = pygame.display.set_mode((SCREENW, SCREENH))
#set window name
pygame.display.set_caption("Snowball Fight!")
#---------------------------------------------------
class Player:
    def __init__(self, playX, playY, size):
        self.playerX = playX
        self.playerY = playY
        self.size = size
        self.playerSurface = pygame.Surface((size, size))
        pygame.draw.rect(self.playerSurface, (19,135,67), (0,0,size, size))
#------------------------------------------------
    def update(self):
        playerOnTileS.blit(self.playerSurface, (self.playerX, self.playerY))
        DISPLAYSURF.blit(playerOnTileS, (SCREENW/2-self.playerX-self.size ,SCREENH/2-self.playerY-self.size))
#game loop
myPlayer = Player(0,0,circleRad)
while True:
    DISPLAYSURF.fill((0,0,0))
    for event in pygame.event.get():
        #if the user closed the window
        if event.type == pygame.QUIT:
            #close pygame
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_a:
                leftP = True
            if event.key == pygame.K_d:
                rightP = True
            if event.key == pygame.K_w:
                upP = True
            if event.key == pygame.K_s:
                downP = True
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_a:
                leftP = False
            if event.key == pygame.K_d:
                rightP = False
            if event.key == pygame.K_w:
                upP = False
            if event.key == pygame.K_s:
                downP = False
    if leftP:
        myPlayer.move(-circleSpeed,0,True)
    if rightP:
        myPlayer.move(circleSpeed,0,True)
    if downP:
        myPlayer.move(0,circleSpeed,True)
    if upP:
        myPlayer.move(0,-circleSpeed,True)
    for row in range(len(tilemap)):
        for column in range(len(tilemap[row])):
            playerOnTileS.blit(tilemap[row][column],(column*TILESIZE,row*TILESIZE))
    myPlayer.update()
    pygame.display.update()
    clock.tick(30)
rivques
  • 103
  • 7
  • 1
    The code is not runnable since Player has no method named move. Please paste the full version for us to test – Crivella Dec 12 '18 at 22:51
  • One potential problem has to do with the `playerOnTileS` surface you create. It's huge—3200W x 3200H. That size results in surface of 10,240,000 pixels (and each of those probably requires at least 3 or 4 bytes). If you want real help, you need to [edit] your question and add enough code so that others can run it and reproduce the problem. If possible, also include the `grass.png` image. If you can't add it directly to your question, then upload it to a website like [imgur](https://imgur.com) and then put a link to it in. – martineau Dec 12 '18 at 23:20
  • You don't need to draw the entire map, only the part that is visible on screen. It takes some (very minimal) math, but the speed-up ought to compensate for that – and then some. – Jongware Dec 12 '18 at 23:33
  • The nested `for` loops near the end which do 80x80 = 6,400 `playerOnTileS.blit()`s between ***each*** `display.update()` are probably a major reason why it's so slooooooooooow... – martineau Dec 13 '18 at 00:01
  • The nested for loops are indeed the culprit, but once you correct that (by drawing only what is visible) you can further optimize: https://stackoverflow.com/questions/6395923/any-way-to-speed-up-python-and-pygame – The4thIceman Dec 13 '18 at 01:34

1 Answers1

2

Images/pygame.Surfaces should usually be converted with the pygame.Surface.convert or convert_alpha methods. The performance of unconverted surfaces is abysmal. When I convert the grass image, I get nearly 30 FPS.

The next problem is the nested for loop. Python's function call overhead is rather big, so it would be a good idea to blit all the tiles onto one large background surface before the main loop starts and then blit this background surface onto the DISPLAYSURF once per frame to clear it. With that change I get pygame's apparent maximum FPS, that means clock.get_fps() jumps between 2000 and 1666.666 FPS.

# Ahead of the while loop.
playerOnTileS = pygame.Surface((MAPWIDTH*TILESIZE, MAPHEIGHT*TILESIZE))
for row in range(len(tilemap)):
    for column in range(len(tilemap[row])):
        playerOnTileS.blit(tilemap[row][column],(column*TILESIZE,row*TILESIZE))

You have to change the update method (by the way, better call it draw) and blit the player surface onto the DISPLAYSURF instead.

skrx
  • 19,980
  • 5
  • 34
  • 48