Low frame rate, possibly because un-accelerated graphics

Question:

So I began creating a game a couple months ago using pygame, it has a top view similar to Starcraft, Age of empires etc… I have written around 1320 lines of code to create the basis of my game; however, I am experiencing issues with frame rate when blitting images and believe this is because I cannot use accelerated graphics with pygame. The way I am currently blitting images is by blitting all images ahead of time on a surface which I then subsurface to create a blit image of my entire screen. Is there a more effective way that i should be utilizing?

So my assumption is that would be huge mess to look through and I do not want to waste your guy’s time. Essentially any time I blit a surface the size of my screen my framerate drops by ~20 frames, is there a way I can avoid this in pygame?

    ##PYGAME INITATE##
import pygame, os
os.environ['SDL_VIDEO_CENTERED'] = '1'
pygame.init()

_W,_H = pygame.display.Info().current_w, pygame.display.Info().current_h
flags = pygame.DOUBLEBUF | pygame.HWSURFACE
gameDisplay = pygame.display.set_mode((_W,_H),pygame.FULLSCREEN ) ## CREATES SCREEN YOU DISPLAY ON ##
gameDisplay.fill((0,0,0)) ## FILL COLOR OF SCREEN ##
pygame.display.set_caption("Dope Game")  ## SETS NAME ##
gameClock = pygame.time.Clock() ## CLOCK OF THE GAME ##
import math
import os
import random
import copy


SQRT = math.sqrt
PI = math.pi
cos = math.cos
sin = math.sin


## REPEATABLE FUNCTIONS ##

def loadScale(file,command,sizeX,sizeY):
    temp = pygame.image.load(file)
    tempInfo = temp.get_rect()
    tempInfo1,tempInfo2,tempInfo3,tempInfo4 = temp.get_rect()
    tempInfo3 = int(tempInfo3)
    tempInfo4 = int(tempInfo4)
    if (command == "ratio"):    
        tempInfo3 = tempInfo3*sizeX
        tempInfo4 = tempInfo4*sizeY
        temp = pygame.transform.scale(temp,(int(tempInfo3),int(tempInfo4) ) )
        
    elif (command == "size"):
        temp = pygame.transform.scale(temp, (sizeX,sizeY) )         
        
    return(temp)
    
## NON GAME RELATED CLASSES ##

class EnterFrame():
    def __init__(self,frameReset,function,parse,reset):
        self.frameReset = frameReset
        self.currentFrame = frameReset
        self.function = function
        self.parse = parse
        self.reset = reset
        if (self.reset != "onComplete"):
            self.reset = (reset-1)
        enterFrameTable.append(self)
        
    def step(self,enterFrameTable):
        if (self.currentFrame == 0):
            self.function(self.parse)
            if (self.reset != "onComplete"):
                if (self.reset > 0): 
                    self.currentFrame = self.frameReset
                    self.reset = self.reset-1
                else:
                    enterFrameTable.remove(self)
                    del self
            else:
                self.currentFrame = self.frameReset
        else:
            self.currentFrame = self.currentFrame-1
        
class PlayerCreation():
    def __init__(self): 
        self.x = _W
        self.y = _H
        self.view = [1600,1600]
        self.viewShift = []
        self.viewChangeSpeed = 25
        
    def moveView(self,key):
        add = EnterFrame(0,self.moveViewAction,key,"onComplete")
        self.viewShift.append([add,key])
        
    def moveViewAction(self,key):
        if (key == "up"):
            self.view[1] = self.view[1]-self.viewChangeSpeed
            Map.recenterView()
            if (self.view[1] < 0):
                self.view[1] = 0
            
        elif (key == "right"):
            self.view[0] = self.view[0]+self.viewChangeSpeed
            Map.recenterView()
            if (self.view[0] > Map.tileSize*4):
                self.view[0] = Map.tileSize*4
                
                
        elif (key == "down"):
            self.view[1] = self.view[1]+self.viewChangeSpeed
            Map.recenterView()
            if (self.view[1] > Map.tileSize*4):
                self.view[1] = Map.tileSize*4
                
                
        elif (key == "left"):
            self.view[0] = self.view[0]-self.viewChangeSpeed
            Map.recenterView()
            if (self.view[0] < 0):
                self.view[0] = 0
    
    def endMoveView(self,key):
        for i in range(len(self.viewShift)-1,-1,-1 ):
            if (self.viewShift[i][1] == key):
                enterFrameTable.remove(self.viewShift[i][0])
                del self.viewShift[i]               

class ImageCreation():
    def __init__(self,name,image,type,hitBox):
        self.name = name
        self.image = image
        self.type = type
        self.hitBox = hitBox
        self.rect = self.image.get_rect()
        if (self.hitBox != "none"):
            self.shiftX = hitBox[0][0]
            self.shiftY = hitBox[0][1]
            for i in range(1,len(hitBox) ):
                if (hitBox[i][0] < self.shiftX):
                    self.shiftX = hitBox[i][0]
                    
                if (hitBox[i][1] < self.shiftY):
                    self.shiftY = hitBox[i][1]
                    
        else:
            self.shiftX = self.rect[2]/2
            self.shiftY = self.rect[3]/2
            
        imageTable.append(self)
        
    def draw(self,x,y):
        image = self.image
        self.blit = gameDisplay.blit(image,(x,y) )
                
class MapCreation():
    def __init__(self):
        self.tileSize = 800
        self.size = self.tileSize*10
        self.tiles = []
        self.loadedTiles = []
        self.surface = pygame.Surface([self.tileSize*5,self.tileSize*5], pygame.SRCALPHA, 32) 
        self.centerTile = [5,5] 
        self.drawPoint = [(_W-self.tileSize)/2,(_H-self.tileSize)/2]
        self.amount = round(self.size/self.tileSize)
        
        backGround = loadScale("Grass.png","size",self.tileSize,self.tileSize)
        for i in range(0,self.amount):
            for u in range(0,self.amount):
                image = copy.copy(backGround)
                newTile = Tile(image,[u*self.tileSize,i*self.tileSize])
                self.tiles.append(newTile)
        
        
        
        info = imageFind("House.png")
        tile = self.tiles[55]
        imageObject(info,tile,[240,50])
    
        self.loadTiles("center")
    
    def recenterView(self):
    
        if (Player.view[0] > 3*self.tileSize):
            self.centerTile[0] = self.centerTile[0]+1
            Player.view[0] = Player.view[0]-self.tileSize
            self.loadTiles("right")
            print("right")
            
        elif (Player.view[0] < 1*self.tileSize):
            self.centerTile[0] = self.centerTile[0]-1
            Player.view[0] = Player.view[0]+self.tileSize
            self.loadTiles("center")
            print("center")
            
            
        if (Player.view[1] > 3*self.tileSize):
            self.centerTile[1] = self.centerTile[1]+1
            Player.view[1] = Player.view[1]-self.tileSize
            self.loadTiles("center")
            print("center")
            
            
        elif (Player.view[1] < 1*self.tileSize):
            self.centerTile[1] = self.centerTile[1]-1
            Player.view[1] = Player.view[1]+self.tileSize
            self.loadTiles("center")
            print("center")
            
    
    def loadTiles(self,load):
        tileIndex = self.centerTile[0]+self.centerTile[1]*self.amount
        if (load == "center"):
            self.loadedTiles = []
            for i in range(-2,3):
                for u in range(-2,3):
                    loadTile = tileIndex + i*self.amount + u
                    self.loadedTiles.append(self.tiles[loadTile])
                    self.tiles[loadTile].loaded = [u+2,i+2]
        
            self.surface = pygame.Surface([self.tileSize*5,self.tileSize*5], pygame.SRCALPHA, 32)       
        
            for i in range(0,len(self.loadedTiles) ):           
                sx = self.loadedTiles[i].loaded[0]*self.tileSize
                sy = self.loadedTiles[i].loaded[1]*self.tileSize
                self.surface.blit(self.loadedTiles[i].buttomLayer,(sx,sy) )

            
            for i in range(0,len(self.loadedTiles) ):           
                sx = self.loadedTiles[i].loaded[0]*(self.tileSize+2)
                sy = self.loadedTiles[i].loaded[1]*(self.tileSize+2)
                self.surface.blit(self.loadedTiles[i].middleLayer,(sx,sy) )
            
            for i in range(0,len(self.loadedTiles) ):           
                sx = self.loadedTiles[i].loaded[0]*(self.tileSize+2)
                sy = self.loadedTiles[i].loaded[1]*(self.tileSize+2)
                self.surface.blit(self.loadedTiles[i].topLayer,(sx,sy) )
        
        elif (load == "right"):
            self.loadedTiles = []
            for i in range(-2,3):
                for u in range(-2,3):
                    loadTile = tileIndex + i*self.amount + u
                    self.loadedTiles.append(self.tiles[loadTile])
                    self.tiles[loadTile].loaded = [u+2,i+2]
            
            ## OLD METHOD THAT WASNT WORKING ##
            ##subSurf = self.surface.subsurface(self.tileSize,0,self.tileSize*1,self.tileSize*5)
            ##self.surface = pygame.Surface([self.tileSize*5,self.tileSize*5], pygame.SRCALPHA, 32)
            ##self.surface.blit(subSurf,(0,0) )
            
            ## NEW METHOD ##
            self.surface.scroll(dx=-self.tileSize*1,dy=0)
            
    def draw(self):
        global Player       
        image = self.surface.subsurface(Player.view[0],Player.view[1],self.tileSize,self.tileSize)      
        image = pygame.transform.scale(image,(self.tileSize,self.tileSize) )
        gameDisplay.blit(image,((_W-self.tileSize)/2,(_H-self.tileSize)/2) )
        image = pygame.transform.scale(self.surface,(300,300) )
        gameDisplay.blit(image,(0,0 ) )
        
class Tile():
    def __init__(self,image,coords):
        transparentSurface = pygame.Surface([1000,1000], pygame.SRCALPHA, 32)
        self.x = coords[0]
        self.y = coords[1]
        self.loaded = False
        self.buttomLayer = image
        self.middleLayer = copy.copy(transparentSurface)
        self.topLayer = transparentSurface      

class imageObject():
    def __init__(self,info,tile,coords):
        self.info = info
        self.image = info.image
        self.rect = self.image.get_rect()
        
        self.x = coords[0]
        self.y = coords[1]
        
        self.hitBox = []
        
        if (self.info.hitBox != "none"):
            for i in range(0,len(self.info.hitBox) ):
                self.hitBox.append([self.info.hitBox[i][0]+self.x,self.info.hitBox[i][1]+self.y])
        #self.object = createObject(self.hitBox,True,"none")
        
        if (info.type == "background"):
            tile.buttomLayer.blit(self.image,(self.x,self.y) )
        if (info.type == "object"):
            tile.middleLayer.blit(self.image,(self.x,self.y) )
        if (info.type == "object alphas"):
            tile.topLayer.blit(self.image,(self.x,self.y) )

        
def imageFind(name):
    for i in range(0,len(imageTable) ):
        if (name == imageTable[i].name):
            return(imageTable[i])
        
    return("none") 

def imageLoad(types):
    if (types == "basic"):
        image = loadScale("House.png","ratio",1,1)
        basicHouse = ImageCreation("House.png",image,"object",[[190, 375], [350, 375], [350, 235], [190, 235]])
        
        image = loadScale("Grass.png","ratio",1,1)
        grass = ImageCreation("Grass.png",image,"background","none")
            
def enterFrameHandle(enterFrameTable): 
    for i in range(len(enterFrameTable)-1,-1,-1 ):
        enterFrameTable[i].step(enterFrameTable)
            
def EventHandle(event):
    global Player
    
    if (event.type == pygame.QUIT):
        endGame()
        
    elif(event.type == pygame.KEYDOWN):
        key = (pygame.key.name(event.key) )
        
        if (key == "escape"):
            endGame()       
            
        elif (key == "up" or key == "right" or key == "down" or key == "left"):
            Player.moveView(key)
        
    elif(event.type == pygame.KEYUP):
        key = (pygame.key.name(event.key) )
        if (key == "up" or key == "right" or key == "down" or key == "left"):
            Player.endMoveView(key)
        
def endGame():
    global QuitGame
    QuitGame = True

def mainLoop(): 

    ## GLOBALS ##
    global QuitGame
    QuitGame = False
    
    global Player
    Player = PlayerCreation()
    
    ## MAIN TABLES ##
    global enterFrameTable
    enterFrameTable = []
    
    global basicObjectTable
    basicObjectTable = []
    
    ## TEMP TABLES ##
    global imageTable
    imageTable = []
    
    
    ## START UP LOOPS ##

    imageLoad("basic")
    global Map
    Map = MapCreation()
    
    ## Temporary ##
    
    while (QuitGame == False):
        enterFrameHandle(enterFrameTable)
        for event in pygame.event.get():
            EventHandle(event)
        

        Map.draw()
        pygame.display.update() ## updates the screen ##
        
        gameDisplay.fill([0,0,0]) ## Clears screen for next frame ##
        gameClock.tick(64) ## The FPS ##
        fps = gameClock.get_fps()
        if (fps < 64 and fps != 0):
            fps = gameClock.get_fps()
            print("FPS HAS DROPPED TOO LOW DOWN TO",fps)

    
runGameNow = True
                                
mainLoop()
pygame.quit()
quit()

Issue with path finder is due to picking up on itteration lines when finding nearest point.

Asked By: mason

||

Answers:

The problem is quite straight forward really.
Your view the amount of lines as disease and the cause must be Pygame. Usually, or at least in almost any case – if you’re not a god at using the language and library, the problem is probably not the language or the library. Because odds are you are no wear near to pushing the limits of what the two can deliver for you.
One dead give-away of this fact is, if you look at what your eyes see on screen, you’ll quickly notice that whenever your background is about to loop around – that’s when the glitch/frame drop occur.
Obviously, this COULD be a cause of the library doing something suspicious.. if it was the library that did the actual loop-around.

But in your code, this is a implementation you’ve done yourself.
So the best bet is to start looking there.
Just to be extremely confident in where the delay occurs – we can have a look at the profiler that comes with Python (cProfiler).

python -m cProfile -o sample_data.pyprof awesome_game.py
pyprof2calltree -i sample_data.pyprof -k

The result would show something along the lines of:
enter image description here

From this we can see that a lot of processing time goes to pygame.Surface and enterFrameHandle. And I bet that pygame.Surface is called within enterFrameHandle somewhere down the line.

So the best bet is to start at the end of the eventFrameHandle chain.
And in this breakdown, that’s loadTiles.

And straight off the bat, a lot of warning signs in here.
There’s at least four loops in here.. and loops are bad, because those take processing time.

I added debug information by simply doing:

def loadTiles
    if load == "center":
        print('Loading center')
    elif load == "right":
        print('Loading right')

Apparently center gets triggered whenever the glitch occurs.
So that narrows it down a little further. So i added timers around each for loop.

  • Loop one: takes 0.0 seconds
  • Loop two: takes 0.29 seconds
  • Loop three: takes 0.5 seconds
  • Loop four: takes 0.6 seconds

All in all, these are EXTREMELY bad for you, since they directly impact the render sequence. So why is this? Lets break it down even further.
We’ll start off with the first loop that takes up 0.3 seconds:

for i in range(0,len(self.loadedTiles) ):           
    sx = self.loadedTiles[i].loaded[0]*self.tileSize
    sy = self.loadedTiles[i].loaded[1]*self.tileSize
    self.surface.blit(self.loadedTiles[i].buttomLayer,(sx,sy) )

So len(self.loadedTiles) will be 25.
That means this loop has to iterate 25 times per render cycle where loadTiles("center") is called.

Each cycle takes almost exactly 0.01 seconds which amounts up to 0.25 seconds per full loop. It’s not terrible, well it is.. If you want 60 FPS out of your game, no loop or function call can take more than 0.0166 seconds in total.

So we’ve already overrun our desired FPS target. So we gotta knock a few milliseconds off this if we wanna get anywhere.

The loop in itself is not that bad, i mean 25 iterations, a modern PC can do that in less time than time() can measure. So, it all points to self.surface.blit().. Which from experience, it sure is.
This also correlate with the fact that our graph above spends 30% of the total CPU time in pygame.surface.blit.. So here’s our first thief.

Looking at the rest of the loops, they are essentially the same, except with bigger numbers in the math which takes slight longer to calculate, hence probably the time difference in the loops.

So, what can we do to shrink down .blit times?
Well we can stop calling blit in every loop iteration, and just move the sprite objects positions and then blit them one by one.
But sensei, that’s almost the same thing?
Well yes my soon to be ninja turtle, it is.. That’s why, we’ll move the sprites in to batches/groups, and render the group. Thus, we can change the positions (fast operation), and because we moved the rendering outside of the loops, we can also convert them into a group object and render the group.

First, we’ll convert your objects into pygame.sprite.Sprite, these are clever little objects that contain both collision detection, moving arounds etc.

self.grass = pygame.sprite.Sprite()
self.grass.image = pygame.image.load("Grass.png")
self.grass.rect = pygame.Rect(0, 0, 60, 60)

And you add this into a rendering group (a batch):

self.main_group = pygame.sprite.Group(self.grass)

Bam, and instead of updating 25 sprites, now you can do:

self.main_group.draw(gameDisplay)

And bam, SPEED!

Now, your code is NOT designed for this.
So this will take a while to fix and correct this design flaw.
Because this would take hours for me in order to keep your original code as close as possible, i ignored this and reworked your entire MapCreation and modified your PlayerCreation and how you move the map around (I changed from trying to recenter each time, it’s a nice idea but far to complex to implement 1PM rewriting someone else’s code).. So.. Here’s the new approach to your problem:

import pygame, os
from time import time
from collections import OrderedDict
os.environ['SDL_VIDEO_CENTERED'] = '1'
pygame.init()

_W,_H = pygame.display.Info().current_w, pygame.display.Info().current_h
flags = pygame.DOUBLEBUF | pygame.HWSURFACE | pygame.RESIZABLE
gameDisplay = pygame.display.set_mode((500, 500))
gameDisplay.fill((0,0,0)) ## FILL COLOR OF SCREEN ##
pygame.display.set_caption("Dope Game")  ## SETS NAME ##
gameClock = pygame.time.Clock() ## CLOCK OF THE GAME ##
import math
import os
import random
import copy


SQRT = math.sqrt
PI = math.pi
cos = math.cos
sin = math.sin


## REPEATABLE FUNCTIONS ##

def loadScale(file,command,sizeX,sizeY):
    temp = pygame.image.load(file)
    tempInfo = temp.get_rect()
    tempInfo1,tempInfo2,tempInfo3,tempInfo4 = temp.get_rect()
    tempInfo3 = int(tempInfo3)
    tempInfo4 = int(tempInfo4)
    if (command == "ratio"):    
        tempInfo3 = tempInfo3*sizeX
        tempInfo4 = tempInfo4*sizeY
        temp = pygame.transform.scale(temp,(int(tempInfo3),int(tempInfo4) ) )

    elif (command == "size"):
        temp = pygame.transform.scale(temp, (sizeX,sizeY) )         

    return(temp)

## NON GAME RELATED CLASSES ##

class EnterFrame():
    def __init__(self,frameReset,function,parse,reset):
        self.frameReset = frameReset
        self.currentFrame = frameReset
        self.function = function
        self.parse = parse
        self.reset = reset
        if (self.reset != "onComplete"):
            self.reset = (reset-1)
        enterFrameTable.append(self)

    def step(self,enterFrameTable):
        if (self.currentFrame == 0):
            self.function(self.parse)
            if (self.reset != "onComplete"):
                if (self.reset > 0): 
                    self.currentFrame = self.frameReset
                    self.reset = self.reset-1
                else:
                    enterFrameTable.remove(self)
                    del self
            else:
                self.currentFrame = self.frameReset
        else:
            self.currentFrame = self.currentFrame-1

class PlayerCreation():
    def __init__(self): 
        self.x = _W
        self.y = _H
        self.view = [1600,1600]
        self.viewShift = []
        self.viewChangeSpeed = 25

    def moveView(self,key):
        add = EnterFrame(0,self.moveViewAction,key,"onComplete")
        self.viewShift.append([add,key])

    def moveViewAction(self,key):
        if (key == "up"):
            self.view[1] = self.view[1]-self.viewChangeSpeed
            Map.move_tile(0, 1) # Player moves up, so the tiles should move down ->  (0, 1) == (x, y)
            if (self.view[1] < 0):
                self.view[1] = 0

        elif (key == "right"):
            self.view[0] = self.view[0]+self.viewChangeSpeed
            Map.move_tile(-1, 0)
            if (self.view[0] > Map.tileSize*4):
                self.view[0] = Map.tileSize*4

        elif (key == "down"):
            self.view[1] = self.view[1]+self.viewChangeSpeed
            Map.move_tile(0, -1)
            if (self.view[1] > Map.tileSize*4):
                self.view[1] = Map.tileSize*4

        elif (key == "left"):
            self.view[0] = self.view[0]-self.viewChangeSpeed
            Map.move_tile(1, 0) 
            if (self.view[0] < 0):
                self.view[0] = 0

    def endMoveView(self,key):
        for i in range(len(self.viewShift)-1,-1,-1 ):
            if (self.viewShift[i][1] == key):
                enterFrameTable.remove(self.viewShift[i][0])
                del self.viewShift[i]               

class ImageCreation():
    def __init__(self,name,image,type,hitBox):
        self.name = name
        self.image = image
        self.type = type
        self.hitBox = hitBox
        self.rect = self.image.get_rect()
        if (self.hitBox != "none"):
            self.shiftX = hitBox[0][0]
            self.shiftY = hitBox[0][1]
            for i in range(1,len(hitBox) ):
                if (hitBox[i][0] < self.shiftX):
                    self.shiftX = hitBox[i][0]

                if (hitBox[i][1] < self.shiftY):
                    self.shiftY = hitBox[i][1]

        else:
            self.shiftX = self.rect[2]/2
            self.shiftY = self.rect[3]/2

        imageTable.append(self)

    def draw(self,x,y):
        image = self.image
        self.blit = gameDisplay.blit(image,(x,y) )

class MapCreation():
    def __init__(self):
        self.tileSize = 800
        self.size = self.tileSize*10
        self.tiles = []
        self.centerTile = [5,5] 
        self.amount = round(self.size/self.tileSize)

        self.sprites = OrderedDict()
        self.grass_image = pygame.image.load("Grass.png")
        self.grass_group = pygame.sprite.Group()

        for x in range(0, self.amount*60, 60):      ## 10*60, but we step 60 pixels, so in the end, this will be 10 steps.
            for y in range(0, self.amount*60, 60):  ## Which is the same as `for x in range(self.amount)` but we scale it up
                                                    ## to give us pixels instead of just the ammount.
                index = len(self.sprites) # -- Generate a index for this sprite. Used for access later (to update pos for instace)

                ## == Create the sprite, add a image to it and define a position and size.
                self.sprites[index] = pygame.sprite.Sprite()
                self.sprites[index].image = self.grass_image
                self.sprites[index].rect = pygame.Rect(x, y, 60, 60)

                ## == Then add the sprite to the grass group.
                self.grass_group.add(self.sprites[index])

    def move_tile(self, dx, dy):
        for index in self.sprites:
            x, y, width, height = self.sprites[index].rect
            ## == this is how you move the sprites:d
            self.sprites[index].rect = pygame.Rect(x+dx, y+dy, 60, 60)

    def draw(self):
        self.grass_group.update()
        self.grass_group.draw(gameDisplay)

class Tile():
    def __init__(self,image,coords):
        transparentSurface = pygame.Surface([1000,1000], pygame.SRCALPHA, 32)
        self.x = coords[0]
        self.y = coords[1]
        self.loaded = False
        self.buttomLayer = image
        self.middleLayer = copy.copy(transparentSurface)
        self.topLayer = transparentSurface      

class imageObject():
    def __init__(self,info,tile,coords):
        self.info = info
        self.image = info.image
        self.rect = self.image.get_rect()

        self.x = coords[0]
        self.y = coords[1]

        self.hitBox = []

        if (self.info.hitBox != "none"):
            for i in range(0,len(self.info.hitBox) ):
                self.hitBox.append([self.info.hitBox[i][0]+self.x,self.info.hitBox[i][1]+self.y])
        #self.object = createObject(self.hitBox,True,"none")

        if (info.type == "background"):
            tile.buttomLayer.blit(self.image,(self.x,self.y) )
        if (info.type == "object"):
            tile.middleLayer.blit(self.image,(self.x,self.y) )
        if (info.type == "object alphas"):
            tile.topLayer.blit(self.image,(self.x,self.y) )


def imageFind(name):
    for i in range(0,len(imageTable) ):
        if (name == imageTable[i].name):
            return(imageTable[i])

    return("none") 

def imageLoad(types):
    if (types == "basic"):
        image = loadScale("House.png","ratio",1,1)
        basicHouse = ImageCreation("House.png",image,"object",[[190, 375], [350, 375], [350, 235], [190, 235]])

        image = loadScale("Grass.png","ratio",1,1)
        grass = ImageCreation("Grass.png",image,"background","none")

def enterFrameHandle(enterFrameTable): 
    for i in range(len(enterFrameTable)-1,-1,-1 ):
        enterFrameTable[i].step(enterFrameTable)

def EventHandle(event):
    global Player

    if (event.type == pygame.QUIT):
        endGame()

    elif(event.type == pygame.KEYDOWN):
        key = (pygame.key.name(event.key) )

        if (key == "escape"):
            endGame()       

        elif (key == "up" or key == "right" or key == "down" or key == "left"):
            Player.moveView(key)

    elif(event.type == pygame.KEYUP):
        key = (pygame.key.name(event.key) )
        if (key == "up" or key == "right" or key == "down" or key == "left"):
            Player.endMoveView(key)

def endGame():
    global QuitGame
    QuitGame = True

def mainLoop(): 

    ## GLOBALS ##
    global QuitGame
    QuitGame = False

    global Player
    Player = PlayerCreation()

    ## MAIN TABLES ##
    global enterFrameTable
    enterFrameTable = []

    global basicObjectTable
    basicObjectTable = []

    ## TEMP TABLES ##
    global imageTable
    imageTable = []


    ## START UP LOOPS ##

    imageLoad("basic")
    global Map
    Map = MapCreation()

    ## Temporary ##

    while (QuitGame == False):
        enterFrameHandle(enterFrameTable)
        for event in pygame.event.get():
            EventHandle(event)


        pygame.display.update() ## updates the screen ##
        gameDisplay.fill([0,0,0]) ## Clears screen for next frame ##

        #Map.draw()
        Map.grass_group.update()
        Map.grass_group.draw(gameDisplay)

        gameClock.tick(64) ## The FPS ##
        fps = gameClock.get_fps()
        if (fps < 64 and fps != 0):
            fps = gameClock.get_fps()
            print("FPS HAS DROPPED TOO LOW DOWN TO",fps)

        pygame.display.flip()


runGameNow = True

mainLoop()
pygame.quit()
quit()

This code rarely drop down in FPS.
And when it does, it drops down to ~63 FPS because I moved the window around (that’s a taxing thing to do because all the OpenGL references need to compensate for the new window position and events that triggers within Pygame (resize events, move events etc are taxing, but occurs once and not every render loop.. so it’s acceptable).

Conclusion

  1. It’s rarely the library’s fault for slow performance.
  2. Stop thinking as a developer for a second, and watch the screen and see if you can find clues to as where the fault may lie. In this case a obvious glitch occurred every time the tiles rotated around the screen edge.. That’s a clue! Not a single programming skill needed to visually see this bug.
  3. Use a profiler and debug where your code spends most of the time.
  4. Try to see how others have solved game designs.. You’re on the right track by putting everything in classes, but that mostly helps your readability of the code – try to see what’s in other peoples classes and how they use Pygame calls.

All in all, it’s quite a fun and nice code you got going.
It’s just not made for performance and it’s hard to code in afterwards.. so maybe it’s not such a bad idea to start all over? But keep your old code base for reference and avoid the obvious traps:

  • loops in the render sequence
  • rendering objects one by one, use batches instead
  • Only do loops to update positions/stats, not graphics!

Last note on the topic: Graphic cards are huge power houses. They can calculate a few million operations a second if not more. And each time you blit something to the screen, the graphics card needs to interrupt it’s calculations and memory allocation to flip around buffers and update what you see on the screen.. And that’s slow work because it’s not calculations, it’s operational tasks (which usually includes waiting for queues and signals).. So instead of throwing hundreds of blit‘s a second to the graphics card, throw math operations at it by sending it textures (math data describing how things look).. and then call blit (draw, in this case) once and let the graphics card take all the math you threw at it – do one big logical operation – and presto.. x100 the speed already.

Answered By: Torxed
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.