So along with my friends I am making a game in Pygame, and I just got onto programming the Animation for the main character.
I figured there could be a couple ways to do this. Either make a list of the images and just loop over the list, or make a dictionary containing the coordinates of the pictures on a sprite sheet.
I went with the second option, and now I decided I want to check which one was faster. So I programmed both of them in quite a simplistic way just to quickly check how they perform.
The results were very close to each other around (0.41 with dictionaries, and around 0.40 without them). Then I decided I want to try out what would happen if I went with the first option except I would call pygame.image.load
in a separate file and store the animation frames as global variables that would than be imported into the main file.
I though it would be really slow, because I read somewhere that python imports are extremely slow... But surprisingly I got a result of 0.021 of a second!
This is a huge change to performance, which might be crucial for my game, so I was wondering if anyone would happen to know why is this method so much faster, and is it only because of certain x or y why it is faster in this case and will be really slow in another.
Here is the code similar to the current state of my game:
import pygame
import time
start_time = time.time()
playerSpriteSize = 192
img = (0, 0)
class SpriteSheet():
def __init__(self, filename):
self.sheet = pygame.image.load(filename).convert()
def get_image(self, coords, size, flip=False):
surf = pygame.Surface(size).convert()
surf.blit(self.sheet, (0, 0), (coords[0], coords[1], size[0], size[1]))
pygame.transform.flip(surf, False, True)
surf.set_colorkey((0, 0, 0))
surf = pygame.transform.flip(surf, flip, False)
return surf
pygame.init()
displaySurface = pygame.display.set_mode((400, 400))
x = SpriteSheet("playerAnimation.png")
animationList = {0: (0, playerSpriteSize*0), 1: (0, playerSpriteSize*1), 2: (0, playerSpriteSize*2),
3: (0, playerSpriteSize*3), 4: (0, playerSpriteSize*4), 5: (0, playerSpriteSize*5),
6: (0, playerSpriteSize*6)}
rounda = 0
for i in animationList:
a1 = x.get_image(animationList[rounda], (playerSpriteSize, playerSpriteSize))
displaySurface.blit(a1, (30, 30))
rounda += 1
pygame.display.update()
print("--- %s seconds ---" %(time.time() - start_time))
Now this is the code that used images loaded before the main game loop:
import pygame
import time
start_time = time.time()
imageSize = 192
img = (0, 0)
class SpriteSheet():
def __init__(self, filename):
self.sheet = pygame.image.load(filename).convert()
def get_image(self, coords, size, flip=False):
surf = pygame.Surface(size).convert()
surf.blit(self.sheet, (0, 0), (coords[0], coords[1], size[0], size[1]))
pygame.transform.flip(surf, False, True)
surf.set_colorkey((0, 0, 0))
surf = pygame.transform.flip(surf, flip, False)
return surf
pygame.init()
displaySurface = pygame.display.set_mode((400, 400))
x = SpriteSheet("playerAnimation.png")
a1 = x.get_image((0, 0), (imageSize, imageSize))
a2 = x.get_image((0, 192), (imageSize, imageSize))
a3 = x.get_image((0, 384), (imageSize, imageSize))
a4 = x.get_image((0, 576), (imageSize, imageSize))
a5 = x.get_image((0, 768), (imageSize, imageSize))
a6 = x.get_image((0, 960), (imageSize, imageSize))
a7 = x.get_image((0, 1152), (imageSize, imageSize))
animationList = [a1, a2, a3, a4, a5, a6, a7]
for i in animationList:
displaySurface.blit(i, (30, 30))
pygame.display.update()
print("--- %s seconds ---" %(time.time() - start_time))
and this is the code(split into 2 files) that runs in around 0.021 seconds: File 1 (main file)
import pygame
import time
from mainDifferentExtern import a1, a2, a3, a4, a5, a6, a7
start_time = time.time()
imageSize = 192
img = (0, 0)
class SpriteSheet():
def __init__(self, filename):
self.sheet = pygame.image.load(filename).convert()
def get_image(self, coords, size, flip=False):
surf = pygame.Surface(size).convert()
surf.blit(self.sheet, (0, 0), (coords[0], coords[1], size[0], size[1]))
pygame.transform.flip(surf, False, True)
surf.set_colorkey((0, 0, 0))
surf = pygame.transform.flip(surf, flip, False)
return surf
pygame.init()
displaySurface = pygame.display.set_mode((400, 400))
x = SpriteSheet("playerAnimation.png")
animationList = [a1, a2, a3, a4, a5, a6, a7]
for i in animationList:
displaySurface.blit(i, (30, 30))
pygame.display.update()
print("--- %s seconds ---" %(time.time() - start_time))
and the file that it imports from:
import pygame
imageSize = 192
class SpriteSheet():
def __init__(self, filename):
self.sheet = pygame.image.load(filename).convert()
def get_image(self, coords, size, flip=False):
surf = pygame.Surface(size).convert()
surf.blit(self.sheet, (0, 0), (coords[0], coords[1], size[0], size[1]))
pygame.transform.flip(surf, False, True)
surf.set_colorkey((0, 0, 0))
surf = pygame.transform.flip(surf, flip, False)
return surf
pygame.init()
pygame.display.set_mode((1, 1))
x = SpriteSheet("playerAnimation.png")
a1 = x.get_image((0, 0), (imageSize, imageSize))
a2 = x.get_image((0, 192), (imageSize, imageSize))
a3 = x.get_image((0, 384), (imageSize, imageSize))
a4 = x.get_image((0, 576), (imageSize, imageSize))
a5 = x.get_image((0, 768), (imageSize, imageSize))
a6 = x.get_image((0, 960), (imageSize, imageSize))
a7 = x.get_image((0, 1152), (imageSize, imageSize))
EDIT: As suggested by match, I have changed the method of measuring the time, and instead went with python profiling (using cProfile), but I got roughly the same results, except the times for the first two methods came out to be a bit longer
I believe the difference here is due to python compiling bytecode for any imported modules. This in turn will speed up the loading/execution of the import. You can see these as .pyc
files in the directory alongside the original code.
I suspect that if you do the following your results will balance out again:
rm *.pyc
PYTHONDONTWRITEBYTECODE=1 python mygame.py
Also bear in mind that a chance of 0.02 seconds is well within the normal 'drift' of any system - which is why tools like timeit
run the same code many thousands of times and average the results.
On that note - consider how many times your potentially slow operation occurs in the code - if you only load the images once, and your performance gain by doing it differently is 0.02 seconds - while the game is expected to run for minutes or hours, then this is looking suspiciously like a premature optimisation.