Any way to speed up Python and Pygame?

Question:

I am writing a simple top down rpg in Pygame, and I have found that it is quite slow…. Although I am not expecting python or pygame to match the FPS of games made with compiled languages like C/C++ or event Byte Compiled ones like Java, But still the current FPS of pygame is like 15. I tried rendering 16-color Bitmaps instead of PNGs or 24 Bitmaps, which slightly boosted the speed, then in desperation , I switched everything to black and white monochrome bitmaps and that made the FPS go to 35. But not more. Now according to most Game Development books I have read, for a user to be completely satisfied with game graphics, the FPS of a 2d game should at least be 40, so is there ANY way of boosting the speed of pygame?

Asked By: ApprenticeHacker

||

Answers:

You could try using Psyco (http://psyco.sourceforge.net/introduction.html). It often makes quite a bit of difference.

Answered By: Peter Collingridge

Use Psyco, for python2:

import psyco
psyco.full()

Also, enable doublebuffering. For example:

from pygame.locals import *
flags = FULLSCREEN | DOUBLEBUF
screen = pygame.display.set_mode(resolution, flags, bpp)

You could also turn off alpha if you don’t need it:

screen.set_alpha(None)

Instead of flipping the entire screen every time, keep track of the changed areas and only update those. For example, something roughly like this (main loop):

events = pygame.events.get()
for event in events:
    # deal with events
pygame.event.pump()
my_sprites.do_stuff_every_loop()
rects = my_sprites.draw()
activerects = rects + oldrects
activerects = filter(bool, activerects)
pygame.display.update(activerects)
oldrects = rects[:]
for rect in rects:
    screen.blit(bgimg, rect, rect)

Most (all?) drawing functions return a rect.

You can also set only some allowed events, for more speedy event handling:

pygame.event.set_allowed([QUIT, KEYDOWN, KEYUP])

Also, I would not bother with creating a buffer manually and would not use the HWACCEL flag, as I’ve experienced problems with it on some setups.

Using this, I’ve achieved reasonably good FPS and smoothness for a small 2d-platformer.

Answered By: Alexander

When using images it is important to convert them with the convert()-function of the image.
I have read that convert() disables alpha which is normally quite slow.
I also had speed problems until I used a colour depth of 16 bit and the convert function for my images. Now my FPS are around 150 even if I blit a big image to the screen.

image = image.convert()#video system has to be initialed

Also rotations and scaling takes a lot of time to calculate. A big, transformed image can be saved in another image if it is immutable.

So the idea is to calculate once and reuse the outcome multiple times.

Answered By: user1509818

First, always use ‘convert()’ because it disables alpha which makes bliting faster.
Then only update the parts of the screen that need to be updated.

global rects

rects = []

rects.append(pygame.draw.line(screen, (0, 0, 0), (20, 20), (100, 400), 1)) 

pygame.display.update(rects) # pygame will only update those rects

Note:

When moving a sprite you have to include in the list the rect from their last position.

Answered By: alexpinho98

When loading images, if you absolutely require transparency or other alpha values, use the Surface.convert_alpha() method. I have been using it for a game I’ve been programming, and it has been a huge increase in performance.
E.G: In your constructor, load your images using:

self.srcimage = pygame.image.load(imagepath).convert_alpha() 

As far as I can tell, any transformations you do to the image retains the performance this method calls. E.G:

self.rotatedimage = pygame.transform.rotate(self.srcimage, angle).convert_alpha()

becomes redundant if you are using an image that has had convert_alpha() ran on it.

Answered By: Craig

All of these are great suggestions and work well, but you should also keep in mind two things:

1) Blitting surfaces onto surfaces is faster than drawing directly. So pre-drawing fixed images onto surfaces (outside the main game loop), then blitting the surface to the main screen will be more efficient. For exmample:

# pre-draw image outside of main game loop
image_rect = get_image("filename").get_rect()
image_surface = pygame.Surface((image_rect.width, image_rect.height))
image_surface.blit(get_image("filename"), image_rect)
......
# inside main game loop - blit surface to surface (the main screen)
screen.blit(image_surface, image_rect)

2) Make sure you aren’t wasting resources by drawing stuff the user can’t see. for example:

if point.x >= 0 and point.x <= SCREEN_WIDTH and point.y >= 0 and point.y <= SCREEN_HEIGHT:
    # then draw your item

These are some general concepts that help me keep FPS high.

Answered By: The4thIceman

There are a few things to consider for a well-performing Pygame application:

  • Ensure that the image Surface has the same format as the display Surface. Use convert() (or convert_alpha()) to create a Surface that has the same pixel format. This improves performance when the image is blit on the display, because the formats are compatible and blit does not need to perform an implicit transformation. e.g.:

    surf = pygame.image.load('my_image.png').convert_alpha()
    
  • Do not load the images in the application loop. pygame.image.load is a very time-consuming operation because the image file must be loaded from the device and the image format must be decoded. Load the images once before the application loop, but use the images in the application loop.

  • If you have a static game map that consists of tiles, you can buy performance by paying with memory usage. Create a large Surface with the complete map. blit the area which is currently visible on the screen:

    game_map = pygame.Surface((tile_size * columns, tile_size * rows))
    for x in range(columns):
        for y in range(rows):
            tile_image = # image for this row and column
            game_map.blit(tile_image , (x * tile_size, y * tile_size))
    
    while game:
        # [...]
    
        map_sub_rect = screen.get_rect(topleft = (camera_x, camera_y))
        screen.blit(game_map, (0, 0), map_sub_rect)
    
    # [...]
    
  • If the text is static, the text does not need to be rendered in each frame. Create the text surface once at the beginning of the program or in the constructor of a class, and blit the text surface in each frame.

  • If the text is dynamic, it cannot even be pre-rendered. However, the most time-consuming is to create the pygame.font.Font/pygame.font.SysFont object. At the very least, you should avoid creating the font in every frame.
    In a typical application you don’t need all permutations of fonts and font sizes. You just need a couple of different font objects. Create a number of fonts at the beginning of the application and use them when rendering the text. For Instance. e.g.:

    fontComic40  = pygame.font.SysFont("Comic Sans MS", 40)
    fontComic180 = pygame.font.SysFont("Comic Sans MS", 180)
    
Answered By: Rabbid76