Hello again and welcome to scriptedfun.com screencast number 2. Today, we are going to look into using sprite sheets and drawing the background for the game Arinoid.
arinoid sprite sheet
The graphics for Arinoid were all taken from a single bitmap file called a sprite sheet. We have to find a way to extract the graphics that we need from this file and use them in our games.

class Spritesheet:
    def __init__(self, filename):
        self.sheet = pygame.image.load(os.path.join(‘data’, filename)).convert()
    def imgat(self, rect, colorkey = None):
        rect = Rect(rect)
        image = pygame.Surface(rect.size).convert()
        image.blit(self.sheet, (0, 0), rect)
        if colorkey is not None:
            if colorkey is -1:
                colorkey = image.get_at((0, 0))
            image.set_colorkey(colorkey, RLEACCEL)
        return image
    def imgsat(self, rects, colorkey = None):
        imgs = []
        for rect in rects:
            imgs.append(self.imgat(rect, colorkey))
        return imgs

Let us define a Spritesheet class, and the first thing that we want it to do is load the sprite sheet file.
Since the file we want to load is in the data subdirectory, we can use os.path.join to describe the location of the file in a platform-independent way. This means that the code will work regardless of the operating system that we use – Windows, Mac OS X, or Linux.
Let’s not forget to convert the image to the correct pixel format so that using it will be fast.
Let us define the method imgat, which will allow us to extract subimages from the sprite sheet. This method takes two arguments – a rect which describes the region we want to extract, and the colorkey, which will allow us to make the background transparent. Making the background transparent is very useful in games, because we want our graphics to blend in seamlessly with the background, instead of retaining their solid background, which makes them look rectangular.
Note that we converted the rect argument into a Rect object. This will allow us to pass 4-tuples, representing the upper-left corner and the size of the subregion, instead of actual Rect objects, to the imgat method because converting the 4-tuple into a Rect object is already done for us.
Then, let us make an empty pygame Surface, and convert it to the correct pixel format. This is where we will place the image that we want.
Then, let us transfer the subregion which we want from the sprite sheet into our blank image.
By default, we will not make the image transparent. Passing the parameter -1 will make the color of the upper-left pixel of the image transparent. This is often the case. Otherwise, we can specify the color which we want to make transparent.
Finally, let us apply the colorkey. We used the flag RLEACCEL to make using the resulting image fast. And we’re done!
It can also be useful for us to extract many images from the sprite sheet at the same time, and store all the resulting images in a list. Let us define the imgsat method to do this. First, let us make an empty list which we will use to store our images.
Then, we simply use imgat for each given rect, use the same colorkey for each image, and store the resulting images in the list. And we’re done!

import os, pygame

By the way, we should import the os module as well, so that we can use os.path.join when we initialize the Spritesheet class.
The Spritesheet class is a general-purpose class which we may paste into our barebones pygame application. We will definitely use this class again in our future projects.

class Arena:
    tileside = 31
    numxtiles = 12
    numytiles = 14
    topx = (SCREENRECT.width – SCREENRECT.width/tileside*tileside)/2
    topy = (SCREENRECT.height – SCREENRECT.height/tileside*tileside)/2
    rect = Rect(topx + tileside, topy + tileside, tileside*numxtiles, tileside*numytiles)
    def __init__(self):
        self.background = pygame.Surface(SCREENRECT.size).convert()

Let us define the Arena class, which will represent our playing field. The constant tileside, which contains the value 31, represents the width and height of the tiles in our sprite sheet. You can obtain this value by opening the sprite sheet file using your favorite image editor.
The numxtiles and numytiles correspond to the width and height of the playing field in tiles. This means that our playing field will be 12 tiles in width and 14 tiles in height.
Finally, it is useful to define a rect object which will correspond to the actual playing field. The upperleft corner of the playing field is one tile away from the corner of the screen because we can use the resulting space to draw a border around the playing field. The actual size of the playing field is then computed by multiplying the number of tiles to the size in pixels of each tile.
Note that having a tileside of 31 is a bit strange, because the dimensions of the screen, 640 by 480, are not divisible by 31. If we try to fill the screen with 31 by 31 tiles, using only whole tiles, you will see that there will be extra space left, corresponding to the remainder when you divide 640 by 31 and 480 by 31. So it would be good if we could make some adjustments to center the image on the screen, which we can do by evenly distributing the extra space around the screen as a border.
To do this, we must first determine the number of tiles that will fit in our screen. We do this by dividing the width of the screen by the tile size and the height of the screen by the tile size. Since both values are integers, the divisions will result in integers as well, leaving out the fractional part. Performing these divisions will give us the exact number of tiles that will fit crosswise and lengthwise.
Then, let us multiply both by tileside, so that we will get the width and height of the region if an exact number of tiles were used. This may surprise you initially, because it may seem that you just cancelled the effect of multiplying tileside. However, remember that the fractional part was discarded during the previous division, so you should end up with a number that is less than or equal to the original width or height.
Then, let us subtract the value that we currently have to the original width and height. This will give us the remainder, or the amount of extra space, when you divide the width or height by tileside.
Finally, since we want the border to be evenly distributed around the screen, which effectively centers the image, we divide the remainder by 2. This ensures that the amount of space at the top is equal to the amount of space at the bottom. The same goes for the extra spaces at the left and at the right.
To apply the border, we add topx and topy to the coordinates of the upper-left corner of the playing field, which moves the playing field by the computed amount, centering the screen. That does it for centering the screen!
Let us now draw the background. Let us define a pygame Surface as large as the screen.

spritesheet = Spritesheet(‘arinoid_master.bmp’)

    Arena.tiles = spritesheet.imgsat([(129, 321, 31, 31),   # purple – 0
                                      (161, 321, 31, 31),   # dark blue – 1
                                      (129, 353, 31, 31),   # red – 2
                                      (161, 353, 31, 31),   # green – 3
                                      (129, 385, 31, 31)])  # blue – 4
 

The background elements will come from the sprite sheet. Let us make a Spritesheet instance using the Arinoid graphics sprite sheet.
arinoid background tiles
The background graphics are contained in this part of the sprite sheet.
upper-left corner and dimensions of a tile
To extract one of the tileable backgrounds, we need to know two things about it: the coordinates of its upper-left corner and its dimensions. You can use your favorite image editor to do this. In my case, I used The GIMP, a free image editor, and used the Crop and Resize Tool to determine the information that I need. Here, we see that the upper-right corner of this purple tile is at (129, 321), and its dimensions are 31 by 31.
All we have to do is take these numbers and use imgsat. As you can see, I used the numbers corresponding to the coordinates of the upper-left corner of the tile and the dimensions.
Then, we just do the same for the other background tiles.

def drawtile(self, tile, x, y):
        self.background.blit(tile, (self.topx + self.tileside*x,    \
                                    self.topy + self.tileside*y))
    def makebg(self, tilenum):
        for x in range(self.numxtiles):
            for y in range(self.numytiles):
                self.drawtile(self.tiles[tilenum], x + 1, y + 1)

The next thing we have to do is draw the background of our playing field using the tiles we have extracted from the sprite sheet. For this, let us define the drawtile method, which will draw an individual tile at the given place. For this, we just use blit, blitting the given tile image to the coordinates shown in the code. By multiplying tileside by x, we will get an exact multiple of tileside, which allows us to blit tiles which are situated exactly beside one another and do not overlap or have any gaps. By adding topx, we are making sure that the border that we constructed awhile ago to center the screen is followed. The same goes for y.
To make the background, all we have to do is use drawtile to lay down each tile individually in the proper place. We do this by using 2 for loops. Take note that we added 1 to both x and y, so that we will have space left for the border, just like when we constructed the rect for the playing field.
makebg takes the argument tilenum, corresponding to the tile color which we want to use. Recall that we placed 5 tile images with different designs in the list tiles. All we have to do is index this list, using the index of the tile image which we want to use. We made note of these indices as comments, as you can see at the bottom right of the screen. 0 corresponds to purple, 1 to dark blue, and so on.

arena = Arena()
    arena.makebg(0) # you may change the background color here
    screen.blit(arena.background, (0, 0))

Next, we have to make some changes to our original background code. We have to replace the highlighted code, which drew our blue background in the barebones pygame program, with code that will draw our playing field background.
Since all the background code is in the Arena class, all we have to do is make an Arena instance, then use its makebg method. Here, we used 0 as an argument to draw a purple background.
Then, we just have to change background to arena.background, since the arena instance stores the background image in its own background variable.

all.clear(screen, arena.background)

We have to make the same change in the clear part of our game loop.
And we are done! Although it may seem that we had to do quite a bit of work to do something as simple as this, we have actually written some code, in particular the sprite sheet code, which we will have to use later when we add the other game elements. At the same time, we have also tackled some of the principles behind drawing tile maps, which will come in handy if we decide to make tile and map-based games later on.
I’d love to hear from you! I highly encourage you to leave a comment on the site if you have any questions, comments, suggestions, or anything at all. In the next screencast, we will add the paddle to our game, and look into simple mouse handling. Thank you and I hope to see you again for the next screencast!

License

This work is published under a Creative Commons Attribution 2.5 License.