markop
markop

Reputation: 75

Faster drawing in python pygame

I made a program using Python, with pygame, that loads pictures of materials and then creates blocks and each block is assigned with random material. Block is a class and in the drawing process, it iterates through the array with stored blocks, but that is very slow. Isn't there a faster method than storing them in array and iterating through?

class block:
    def __init__(self, texture, x, y):
           self.texture = texture
           self.x = x
           self.y = y


material = pygame.image
material.grass = pygame.image.load("textures/grass.png")
material.water = pygame.image.load("textures/water.png")
material.sand = pygame.image.load("textures/sand.png")

materials = [material.grass, material.water, material.sand]



white = (255,255,255);(width, height) = (2048, 1008);black = (0, 0, 0);screen = pygame.display.set_mode((width, height))

b_unit = 16

b = []

count = 0
cx = 0
cy = 0
while count < (width * height) / (b_unit * b_unit):
    b.append(block(random.choice(materials), b_unit * cx, b_unit * cy))
    cx += 1
    count += 1
    if cx == width / b_unit:
        cx = 0
        cy += 1

while True:
    for block in b:
        screen.blit(block.texture, (block.x + viewx, block.y + viewy))
    pygame.display.flip()

Upvotes: 1

Views: 3074

Answers (1)

skrx
skrx

Reputation: 20488

I've already mentioned in the comments that you should (almost) always convert your images to improve the performance.

It can also help to blit separate images/pygame.Surfaces onto a big background surface and then just blit this background once per frame. I use two nested for loops here to get the coordinates and randomly blit one of two images.

I get around 120 fps if I use separate sprites (5184) here and ~430 fps with this single background image.

Of course I'm just blitting here and in a real game you'd probably have to store the rects of the tiles in a list or use pygame sprites and sprite groups, for example to implement collision detection or other map related logic, so the frame rate would be lower.

import itertools

import pygame as pg
from pygame.math import Vector2


BLUE_IMAGE = pg.Surface((20, 20))
BLUE_IMAGE.fill(pg.Color('lightskyblue2'))
GRAY_IMAGE = pg.Surface((20, 20))
GRAY_IMAGE.fill(pg.Color('slategray4'))


def main():
    screen = pg.display.set_mode((1920, 1080))
    clock = pg.time.Clock()
    all_sprites = pg.sprite.Group()

    images = itertools.cycle((BLUE_IMAGE, GRAY_IMAGE))
    background = pg.Surface(screen.get_size())
    # Use two nested for loops to get the coordinates.
    for y in range(screen.get_height()//20):
        for x in range(screen.get_width()//20):
            # This alternates between the blue and gray image.
            image = next(images)
            # Blit one image after the other at their respective coords.
            background.blit(image, (x*20, y*20))
        next(images)

    done = False

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True

        # Now you can just blit the background image once
        # instead of blitting thousands of separate images.
        screen.blit(background, (0, 0))
        pg.display.set_caption(str(clock.get_fps()))
        pg.display.flip()
        clock.tick(1000)


if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()

Side notes: Don't add your images to the pygame.image module (that makes no sense at all).

material = pygame.image
material.grass = pygame.image.load("textures/grass.png")

Writing several statements in the same row separated with semicolons is really ugly and makes code less readable.

white = (255,255,255);(width, height) = (2048, 1008)

Upvotes: 7

Related Questions