How to Use Blit in Py Game Uploading a Sprite
Using Sprite Sheets in Pygame
When nosotros congenital Alien Invasion, nosotros only needed two images: one for the send, and i for the conflicting. In some games, however, you'll demand lots of different images.
Game developers realized a long time ago that loading many images from dissever files causes a game to run really slowly, so people came up with the idea of sprite sheets. A sprite sail is a single file that contains many smaller images, all on a apparently or transparent background. To use a sprite sail, y'all load the sprite canvas as a unmarried large image, and so you load the individual images from the sprite sheet prototype. This turns out to be much more efficient than loading a agglomeration of separate image files.
For this example we'll expect at how you can load a full set of chess pieces from one sprite canvass. To build a chess game, you lot need 12 pieces: a black and white rex, queen, rook, bishop, knight, and pawn. Instead of loading 12 separate images, we'll load one image that contains an icon for each of these pieces. Nosotros'll then create 12 split objects representing each of these pieces. What you learn in this guide will be useful whatever time you desire to load a number of icons from a single epitome file.
- A Unproblematic Sprite Sheet
- Starting a Chess Game
- Modeling a Chess Piece
- Modeling a Chess Set
- The
ChessSet
class - Loading the first piece
- Loading all the pieces
- The
load_grid_images()
method - Using
load_grid_images()
- Setting values for
name
andcolor
- Seeing all the pieces
- The
- Making mistakes
- The
- Loading Your Own Images
- Final Words
- Groups vs Lists
- Sprite sheets with non-uniform grids
- Determining margin and padding sizes
A Simple Sprite Sheet
Here's the sprite canvass we'll work with:
I downloaded this epitome from Public Domain Prune Art, and converted it to a .bmp file. You tin right click and relieve the image file from this page, or you can notice information technology in the beyond_pcc folder when you download the resources for the book.
Starting a Chess Game
We're non going to make a chess game in this tutorial, but nosotros'll prepare up this project so yous could go along to expand on it and outset edifice a game if you want to. And so let'due south starting time with a simple Pygame file, just like we did for Alien Invasion. Make a folder chosen chess_game, and salvage this file as chess_game.py:
"""Chess game, for learning to grab images from a sprite sheet.""" import sys import pygame from settings import Settings class ChessGame : """Overall grade to manage game assets and behavior.""" def __init__ ( self ): """Initialize the game, and create resources.""" pygame . init () self . settings = Settings () self . screen = pygame . display . set_mode ( ( cocky . settings . screen_width , cocky . settings . screen_height )) pygame . brandish . set_caption ( "Chess" ) def run_game ( cocky ): """Start the primary loop for the game.""" while Truthful : cocky . _check_events () self . _update_screen () def _check_events ( self ): for event in pygame . effect . get (): if event . type == pygame . QUIT : sys . go out () elif event . type == pygame . KEYDOWN : if event . primal == pygame . K_q : sys . exit () def _update_screen ( cocky ): self . screen . make full ( self . settings . bg_color ) pygame . display . flip () if __name__ == '__main__' : chess_game = ChessGame () chess_game . run_game ()
And here'due south settings.py:
class Settings : def __init__ ( self ): self . screen_width , cocky . screen_height = 1200 , 800 self . bg_color = ( 225 , 225 , 225 )
This gives us an empty game window, and nosotros tin press 'q' to quit the game at any fourth dimension. I similar to have this choice because sometimes I run games in fullscreen mode, and you tin can't click the shut button in fullscreen manner.
You'll also need to create an images binder, and relieve the sprite sheet every bit chess_pieces.bmp in this folder.
Modeling a Chess Piece
A chess set is made up of a number of pieces. Let'southward think about the pieces get-go. Each piece needs a name and a colour, and an image. We need to be able to depict each piece to the screen. In a fully implemented game we might add attributes such every bit starting_position
, current_position
, captured
, and others.
We'll brand a file called chess_set.py, which will contain a class for representing pieces and a grade for representing the prepare every bit a whole. Here's the first of that file, with the Slice
grade defined:
"""Module to correspond a chess set, and individual pieces.""" class Piece : """Represents a chess slice.""" def __init__ ( cocky , chess_game ): """Initialize attributes to correspond a ches piece.""" self . paradigm = None self . proper name = '' self . color = '' cocky . screen = chess_game . screen # Outset each slice off at the pinnacle left corner. cocky . x , cocky . y = 0.0 , 0.0 def blitme ( cocky ): """Depict the slice at its current location.""" self . rect = self . image . get_rect () cocky . rect . topleft = self . ten , self . y cocky . screen . blit ( self . image , self . rect )
The Piece
class allows us to assign each piece an epitome, a name, and a color. Each piece will start off at the top left corner, only we tin move information technology wherever it needs to go. The only argument needed to create a slice initially is a reference to the overall game object. When we're set up to draw a slice to the screen, we can do then by calling blitme()
.
Modeling a Chess Set
Now we tin first to model the set as a whole. The fix will handle the task of creating all the pieces. In a fuller implementation, it might as well rails attributes such as the overall strength of the player'due south remaining pieces, which can be useful in developing playing strategies.
The ChessSet
class
Here'due south the offset of the ChessSet
class. To begin with, we want an __init__()
method that accepts the overall game object, and we desire to call a helper method that builds the pieces that make up the gear up. Add this code to chess_set.py:
"""Module to represent a chess set, and individual pieces.""" class ChessSet : """Represents a set of chess pieces. Each piece is an object of the Piece form. """ def __init__ ( self , chess_game ): """Initialize attributes to represent the overall set up of pieces.""" self . chess_game = chess_game cocky . pieces = [] self . _load_pieces () def _load_pieces ( self ): """Builds the overall set: - Loads images from the sprite sail. - Creates a Piece object, and sets appropriate attributes for that piece. - Adds each piece to the group self.pieces. """ pass class Piece : -- snip --
We have an __init__()
method which accepts a reference to the overall game object, and we have an attribute for storing the pieces in the set. We as well phone call_load_pieces()
, which is a stub for at present.
Loading the start slice
When loading images from a sprite sail, it'southward helpful to start with a library if possible. If yous search something like "pygame sprite canvas", one of the top results is from the Pygame wiki. Here's a cleaned-up version of the lawmaking featured in that location:
# This grade handles sprite sheets # This was taken from www.scriptefun.com/transcript-2-using # sprite-sheets-and-drawing-the-groundwork # I've added some code to fail if the file wasn't plant.. # Note: When calling images_at the rect is the format: # (x, y, ten + offset, y + offset) # Boosted notes # - Further adaptations from https://world wide web.pygame.org/wiki/Spritesheet # - Cleaned up overall formatting. # - Updated from Python ii -> Python 3. import pygame class SpriteSheet : def __init__ ( self , filename ): """Load the sheet.""" try : cocky . sheet = pygame . image . load ( filename ). convert () except pygame . error equally e : print ( f "Unable to load spritesheet image: { filename } " ) raise SystemExit ( e ) def image_at ( self , rectangle , colorkey = None ): """Load a specific epitome from a specific rectangle.""" # Loads image from x, y, ten+beginning, y+start. rect = pygame . Rect ( rectangle ) image = pygame . Surface ( rect . size ). convert () prototype . blit ( self . sail , ( 0 , 0 ), rect ) if colorkey is not None : if colorkey is - one : colorkey = image . get_at (( 0 , 0 )) image . set_colorkey ( colorkey , pygame . RLEACCEL ) return image def images_at ( cocky , rects , colorkey = None ): """Load a whole bunch of images and render them as a list.""" return [ self . image_at ( rect , colorkey ) for rect in rects ] def load_strip ( self , rect , image_count , colorkey = None ): """Load a whole strip of images, and return them as a list.""" tups = [( rect [ 0 ] + rect [ 2 ] * x , rect [ 1 ], rect [ two ], rect [ three ]) for x in range ( image_count )] render self . images_at ( tups , colorkey )
Re-create what you lot run across hither and save information technology as spritesheet.py, in the same folder where you saved chess_game.py.
This file contains a class called SpriteSheet
that can help usa work with sprite sheets. The method we're most interested in is image_at()
. To utilise this, nosotros'll brand an object from the SpriteSheet
form, passing it the location of our sprite sheet file. We'll figure out which rectangular portion of the sprite sheet we want to load - the coordinates of its top left corner, and the width and tiptop of the region we want to load. We'll telephone call image_at()
with these 4 values.
If y'all look at the file chess_pieces.bmp, you can see that the left border of the black male monarch is nigh 68 pixels from the edge of the image, and the pinnacle of the male monarch is about seventy pixels from the top edge of the image. These will be the first two values we pass to image_at()
. The king is nigh 85 pixels broad by 85 pixels tall.
Here's what we'll exercise:
- Import the
SpriteSheet
form. - Create a
SpriteSheet
object. - Pull the image associated with the rectangle (68, seventy, 85, 85).
- Create an object from the
Piece
class. - Assign this object the name
'king'
, the colour'blackness'
, and the image we only pulled. - Add this object to the list
pieces
.
We'll do all of this in chess_set.py, in the _load_pieces()
method:
"""Module to represent a chess set, and individual pieces.""" from spritesheet import SpriteSheet form ChessSet : -- snip -- def _load_pieces ( self ): """Builds the overall set: - Loads images from the sprite sheet. - Creates a Piece object, and sets appropriate attributes for that piece. - Adds each slice to the list self.pieces. """ filename = 'images/chess_pieces.bmp' piece_ss = SpriteSheet ( filename ) # Create a black king. b_king_rect = ( 68 , 70 , 85 , 85 ) b_king_image = piece_ss . image_at ( b_king_rect ) b_king = Piece ( self . chess_game ) b_king . image = b_king_image b_king . proper name = 'king' b_king . color = 'black' cocky . pieces . append ( b_king ) class Piece : -- snip --
At that place are many ways nosotros could take washed this. You lot can create the piece first, and and then load the image, or you can load the image and assign it to the slice in one line. I'm doing information technology the mode y'all run into here because in a lilliputian scrap I'k going to show you how to load all the images at in one case, and and so write a loop that creates the pieces all at one time besides.
To see the slice that we grabbed, let's modify the _update_screen()
method in chess_game.py:
"""Chess game, for learning to take hold of images from a sprite sheet.""" import sys import pygame from settings import Settings from chess_set import ChessSet class ChessGame : """Overall class to manage game avails and beliefs.""" def __init__ ( cocky ): """Initialize the game, and create resources.""" pygame . init () cocky . settings = Settings () self . screen = pygame . display . set_mode ( ( self . settings . screen_width , self . settings . screen_height )) pygame . display . set_caption ( "Chess" ) cocky . chess_set = ChessSet ( self ) def run_game ( self ): """Start the chief loop for the game.""" while True : self . _check_events () self . _update_screen () def _check_events ( self ): for upshot in pygame . event . get (): if consequence . blazon == pygame . QUIT : sys . go out () elif event . type == pygame . KEYDOWN : if event . key == pygame . K_q : sys . exit () def _update_screen ( cocky ): cocky . screen . fill ( self . settings . bg_color ) # Draw the black male monarch in its current position. self . chess_set . pieces [ 0 ]. blitme () pygame . display . flip () if __name__ == '__main__' : chess_game = ChessGame () chess_game . run_game ()
We first import ChessSet
. Then in __init__()
we make an attribute called chess_set
, which is an object of the ChessSet
class. This object needs a reference to the overall game object, which in this file is represented by self
. In _update_screen()
, we call blitme()
on the first (and but) slice in the fix.
The output shows the black king in the upper left corner of the game window:
Nosotros've pretty much got the image we want. We might want to go back and refine the rectangle we used for pulling this image, to even out the amount of background on each of the margins.
Don't be surprised if you lot run into a much dissimilar expanse of the sprite sheet than you were expecting when you run your own lawmaking. It can take a bit of exercise to understand how to choose the right rectangle coordinates, and even with practice information technology's piece of cake to brand a error that grabs the wrong role of the sprite sheet. If you run into a blackness rectangle, it's possible you lot asked for a portion of the sprite sheet that doesn't exist.
Loading all the pieces
As mentioned earlier, it'south possible to load all of the images nosotros demand from the spritesheet at once, and then assign each one to the appropriate piece. This tin be much easier than figuring out the coordinates by hand for each private piece, especially if y'all're working with multiple sprite sheets.
Consider the original sprite sail again, this time with a couple aspects of the sheet highlighted:
The dark blue rectangle shows the space to the left of the first column, which we can call a margin. The light blueish region shows the horizontal infinite between columns, which we tin telephone call padding. The dark greenish bar shows the margin above the first row, and the light green bar shows the vertical padding between each row.
These spacings let united states of america to piece of work out a design for where each epitome should be grabbed. For example the left position of the black king is equal to the width of the horizontal margin. The left position of the black queen is equal to the width of the horizontal margin, plus the width of a piece, plus the width of one strip of padding. For the third icon in the first row, the horizontal position is one margin width, plus two padding widths, plus ii icon widths. The width of an icon should exist the width of the overall image, minus the space taken past the margins and padding, divided past the number of columns.
If you want a challenge, endeavour adding a method chosen load_grid_images()
to SpriteSheet
. The method should take in the post-obit arguments: num_rows
, num_cols
, x_margin
, x_padding
, y_margin
, and y_padding
. The method should utilize these values to figure out the width and pinnacle of each piece, and call image_at()
with the appropriate parameters. You should be able to telephone call load_grid_images()
with the advisable values, and the method should return a listing of all sprites in the sprite sheet. If you lot desire to endeavour this, break and effort it at present, because I'yard going to prove that method and and so nosotros'll use it to load the rest of the pieces.
The load_grid_images()
method
Here's the load_grid_images()
method, which we can add together on to the finish of SpriteSheet
:
-- snip -- class SpriteSheet : def __init__ ( self , filename ): -- snip -- def image_at ( self , rectangle , colorkey = None ): -- snip -- def images_at ( self , rects , colorkey = None ): -- snip -- def load_strip ( cocky , rect , image_count , colorkey = None ): -- snip -- def load_grid_images ( self , num_rows , num_cols , x_margin = 0 , x_padding = 0 , y_margin = 0 , y_padding = 0 ): """Load a grid of images. x_margin is space between top of sail and top of starting time row. x_padding is space between rows. Assumes symmetrical padding on left and correct. Same reasoning for y. Calls self.images_at() to become list of images. """ sheet_rect = cocky . sheet . get_rect () sheet_width , sheet_height = sheet_rect . size # To calculate the size of each sprite, subtract the two margins, # and the padding betwixt each row, then divide by num_cols. # Aforementioned reasoning for y. x_sprite_size = ( sheet_width - 2 * x_margin - ( num_cols - ane ) * x_padding ) / num_cols y_sprite_size = ( sheet_height - ii * y_margin - ( num_rows - ane ) * y_padding ) / num_rows sprite_rects = [] for row_num in range ( num_rows ): for col_num in range ( num_cols ): # Position of sprite rect is margin + one sprite size # and one padding size for each row. Same for y. x = x_margin + col_num * ( x_sprite_size + x_padding ) y = y_margin + row_num * ( y_sprite_size + y_padding ) sprite_rect = ( x , y , x_sprite_size , y_sprite_size ) sprite_rects . append ( sprite_rect ) grid_images = cocky . images_at ( sprite_rects ) print ( f "Loaded { len ( grid_images ) } grid images." ) render grid_images
This might await like a long method, but it'southward just near 15 lines of code. Existent-world functions and classes can include more than comments than yous typically see in books. It's besides a niggling longer than it needs to exist, for clarity in a tutorial. For example if yous didn't need to run across how many images were loaded, you could collapse the last three lines into i line:
return self . images_at ( sprite_rects )
Using load_grid_images()
We'll showtime use this method to load all the images, and run across if information technology'south grabbing all the correct portions of the sprite sheet. We'll do this in _load_pieces()
, in ChessSet
:
def _load_pieces ( cocky ): """Builds the overall set: - Loads images from the sprite sheet. - Creates a Piece object, and sets appropriate attributes for that piece. - Adds each slice to the list self.pieces. """ filename = 'images/chess_pieces.bmp' piece_ss = SpriteSheet ( filename ) # Load all piece images. piece_images = piece_ss . load_grid_images ( 2 , six , x_margin = 64 , x_padding = 72 , y_margin = 68 , y_padding = 48 ) # Create a new Slice object for every image. for image in piece_images : piece = Piece ( self . chess_game ) piece . image = image self . pieces . append ( piece )
We utilize load_grid_images()
to load ii rows with 6 columns each, and specify appropriate margin and padding amounts. We and then create one Slice
object for every image that was loaded; we'll take care of setting the proper noun and color values in a moment.
When nosotros run chess_game.py again, we should run into the black king in the upper left corner, since it was the first slice loaded. This works; the game window looks but like information technology did when nosotros loaded a single image, with a slightly dissimilar cropping region.
Setting values for proper name
and color
In that location's a pattern in the sprite sheet, which we tin use to efficiently set the name and color values for each piece. We'll create a list of colors, and a list of piece names. Then we'll set upwardly nested loops that cycle through the pieces in the same order that load_grid_images()
works, ane row at a time.
Here'south the consummate _load_pieces()
:
def _load_pieces ( self ): """Builds the overall set: - Loads images from the sprite sheet. - Creates a Piece object, and sets appropriate attributes for that piece. - Adds each slice to the listing self.pieces. """ filename = 'images/chess_pieces.bmp' piece_ss = SpriteSheet ( filename ) # Load all piece images. piece_images = piece_ss . load_grid_images ( 2 , half dozen , x_margin = 64 , x_padding = 72 , y_margin = 68 , y_padding = 48 ) # Create a Slice for each image. colors = [ 'black' , 'white' ] names = [ 'rex' , 'queen' , 'rook' , 'bishop' , 'knight' , 'pawn' ] piece_num = 0 for color in colors : for proper name in names : piece = Slice ( self . chess_game ) piece . proper name = proper noun piece . color = color piece . image = piece_images [ piece_num ] cocky . pieces . suspend ( slice ) piece_num += 1
We load all the piece images, just every bit we did before. And so we set a counter, piece_num
, to continue rails of how many pieces nosotros've made. This will serve as an alphabetize to the image we want from the list piece_images
. We loop through the colors, and and so through the names. This will event in processing each of the black pieces, then each of the white pieces. For each piece, we set the appropriate values, add it to the list self.pieces
, and increment the value of piece_num
.
When we run chess_game.py over again, we should still see the black king because it'south always the first slice in the list.
Seeing all the pieces
Now let's bank check that all of the pieces were pulled correctly. Nosotros can do this in _update_screen()
, in chess_game.py:
def _update_screen ( cocky ): self . screen . make full ( self . settings . bg_color ) # Depict a row of blackness pieces. for index , piece in enumerate ( self . chess_set . pieces [: half-dozen ]): piece . x = alphabetize * 100 piece . blitme () # Depict a row of white pieces. for index , slice in enumerate ( self . chess_set . pieces [ 6 :]): slice . 10 = alphabetize * 100 piece . y = 100 piece . blitme () pygame . brandish . flip ()
Nosotros loop through the start half-dozen pieces. For each piece, we set the ten value 100 higher than the piece before it. If you haven't seen the enumerate()
function yet, information technology'south mentioned on page 335 in the book. The enumerate()
office returns the index and the value of each item equally yous loop through a list. We do the aforementioned for the white pieces, except nosotros set up the y value to 100 and then they announced equally a 2nd row.
Here we can run across that all of the pieces were grabbed appropriately:
Making mistakes
When you're reading a tutorial like this, information technology'due south easy to think that everything is supposed to work out perfectly the get-go time you write your code. That'southward not at all the case! I made a number of mistakes in the process of working out load_grid_images()
, and more mistakes when using information technology to grab the images for each piece. For case when I beginning chosen load_grid_images()
, I mixed up the values for the number of rows and columns. When I ran chess_game.py, I saw a blank game window. I had no idea why the images weren't being grabbed. I concluded upwardly looking at the value of sprite_rect
in load_grid_images()
, and saw that the width of the paradigm being grabbed was negative. That led me back to looking at the values I was passing to load_grid_images()
, and I spotted the mixup. But it took a while, and it was non at all obvious what was going on at first.
If I were going to use this module to create a lot of games using sprite sheets, or if I was maintaining this for a widely-distributed bundle, I'd probably add some error-checking code to make sure all of the sizes in load_grid_images()
come out positive. But I'm trying to keep things simple for now, so I'chiliad not calculation that caste of error-checking at this point.
If you see mistakes in the game you're working on, or any project y'all notice yourself involved in, please know that anybody makes mistakes almost every unmarried day. Y'all are not lone. :)
Loading Your Own Images
Some people like to identify files like spritesheet.py in a directory chosen utils within their main projection folder, and then their import statement looks like this:
from utils.spritesheet import SpriteSheet
This makes it clear what code is specific to your game, and what code is a utility module that could exist used for any game. If you're making a lot of games, y'all can place the utils directory in a location that'due south accessible to all your games, so you don't have a bunch of copies of the same utility module all over your system.
You can observe the full spritesheet.py module here. Information technology'southward as well in the beyond_pcc folder in the zip file of online resources for the volume. If you don't meet that folder, you might need to download a newer copy of the online resource, as I've just recently added this section.
Terminal Words
Groups vs Lists
The betoken of this guide was to show how you tin load images from a sprite sheet when using Pygame. There's a lot that we might do differently if nosotros were focused on edifice a fully-functioning chess game. For case, should nosotros store the pieces in a list like nosotros did, or should they exist placed into a Pygame Grouping
? I used a list here considering a list is ordered, and I wanted the order of cocky.pieces
to match the order we see in the sprite sheet. A group is not ordered, so it wouldn't necessarily work for this purpose. A group is nifty when you want to repeatedly draw a bunch of elements to the screen, and the gild you're keeping them in doesn't matter.
Sprite sheets with not-compatible grids
Many sprite sheets are prepare in a filigree like we saw with chess_pieces.bmp. For example a deck of cards might have four rows of 13 cards each, and perchance an actress row for a card back and a joker. Still, some sprite sheets have icons of different sizes on them. In that case you might be able to call load_grid_images()
for most of the icons, and then call image_at()
for some of the oddly-sized icons.
Determining margin and padding sizes
If you're not sure how to decide pixel sizes on a sprite canvass, attempt opening the file in an image previewer or editor. Nearly viewers and editors allow you lot to make selections in a way that shows you the dimensions of the selection in pixels. For case on macOS y'all can open Preview, click Tools > Rectangular Selection, and drag a rectangle effectually the region you want to measure. A small popup will evidence you lot the width and height of the rectangular region you have selected.
If you're completely at a loss, make a guess and see how accurately the first epitome is grabbed. That should allow you to work out the size of the margins, and looking at the second prototype should allow you to work out the padding.
Source: https://ehmatthes.github.io/pcc_2e/beyond_pcc/pygame_sprite_sheets/
0 Response to "How to Use Blit in Py Game Uploading a Sprite"
Post a Comment