Lesson 16: Sprites and Sprite Sheets

A sprite is nothing more than an image that can be manipulated around on the computer screen. Sprites usually come accompanied with a background that is either transparent or opaque in color to use as transparency and are typically small in size with a known width and height dimension. More often than not sprites are used for character animation by showing a sequence of sprite cells in series. There are a number of different approaches to loading and displaying sprites. The first approach, as illustrated in the next example program, loads a number of individual images into an array to be displayed in order to create a walking character demonstration.

( This code can be found at .\tutorial\Lesson16\WalkDemo1.bas )

'** Walker Demo 1
'** Load Individual Sprite Images

CONST BRIGHTMAGENTA = _RGB32(255, 0, 255) ' declare colors
CONST WHITE = _RGB32(255, 255, 255)

DIM Frame% '             frame counter
DIM Sprite% '            sprite image counter
DIM Walker&(6) '         sprite images
DIM x1%, y1%, x2%, y2% ' sprite upper left and lower right X,Y coordinates
DIM Dir% '               sprite travel direction
DIM Filename$ '          file name of each sprite image
DIM Path$ '              location of sprites on drive

'** Load each sprite image one at a time

Path$ = ".\tutorial\Lesson16\" '                             path to sprites
FOR Sprite% = 1 TO 6 '                                     load 6 sprites
    Filename$ = "walk" + _TRIM$(STR$(Sprite%)) + ".png" '  filename of sprite
    Walker&(Sprite%) = _LOADIMAGE(Path$ + Filename$, 32) ' load sprite
   _CLEARCOLOR BRIGHTMAGENTA, Walker&(Sprite%) '           set transparent color of sprite
NEXT Sprite%

Frame% = 0 '                                               reset frame counter
Sprite% = 1 '                                              reset sprite image counter
Dir% = 1 '                                                 set sprite direction
x1% = 10 '                                                 upper left X coordinate of sprites
y1% = 50 '                                                 upper left Y coordinate of sprites
y2% = y1% + 155 '                                          lower right Y coordinate of sprites
SCREEN _NEWIMAGE(640, 480, 32) '                           enter graphics screen
DO '                                                       begin main loop
   _LIMIT 30 '                                             30 frames per second
   CLS , WHITE '                                           clear screen in white
   LOCATE 2, 21 '                                          locate text cursor
   PRINT " Right/Left arrows to walk, ESC to exit. " '     print directions
   IF _KEYDOWN(19712) THEN '                               right arrow key pressed?
        Dir% = 1 '                                         yes, set direction heading to right
   ELSEIF _KEYDOWN(19200) THEN '                           left arrow key pressed?
        Dir% = -1 '                                        yes, set direction heading to left
   END IF
   IF x1% + Dir% * 3 < 536 AND x1% + Dir% * 3 > 0 THEN '   sprite at edge of screen?
        x1% = x1% + 3 * Dir% '                              no, update X location of sprite
   ELSE '                                                  yes, at edge of screen
        Dir% = -Dir% '                                      change sprite direction
   END IF
    x2% = x1% + 103 '                                      calculate lower right hand X coordinate
   SELECT CASE Dir% '                                      which direction is sprite heading?
       CASE 1 '                                            to the right
           _PUTIMAGE (x1%, y1%), Walker&(Sprite%) '        display sprite image normal
       CASE -1 '                                           to the left
           _PUTIMAGE (x2%, y1%)-(x1%, y2%), Walker&(Sprite%) ' flip sprite image horizontally
   END SELECT
    Frame% = Frame% + 1 '                                  increment frame counter
   IF Frame% = 30 THEN Frame% = 0 '                        reset frame counter after 30 frames
   IF Frame% MOD 5 = 0 THEN '                              frame even divisible by 5?
        Sprite% = Sprite% + 1 '                            yes, increment sprite image counter
       IF Sprite% = 7 THEN Sprite% = 1 '                   keep sprite image counter within limits
   END IF
   _DISPLAY '                                              update screen with changes
LOOP UNTIL _KEYDOWN(27) '                                  leave when ESC key pressed
FOR Sprite% = 1 TO 6 '                                     cycle through the six sprite images
   _FREEIMAGE Walker&(Sprite%) '                           free image from memory
NEXT Sprite%
SYSTEM '                                                   return to operating system

Figure 1: Sprite animation

The program above loads 6 individual images, walk1.PNG through walk6.PNG, and saves them to the Walker&() array as seen in lines 17 through 22 of the code. The PNG images do not contain an alpha channel so line 21 identifies the color to use for transparency in each image.

walk1.PNG

walk2.PNG

walk3.PNG

walk4.PNG

walk5.PNG

walk6.PNG

In the main loop of the program the images are simply displayed in sequence as the loop progresses. An integer variable named Sprite% is consistently incremented from 1 to 6 identifying the next sprite image to display. In lines 55 through 58 modulus division is used to increment the Sprite% variable every 5th frame. Since the main loop runs at 30 frames per second the character animation needs to be slowed down otherwise the character would look like The Flash dashing around. This effectively slows the character animation down to 6 frames per second while still allowing the main loop to run at 30 frames per second.

Lines 47 through 52 examines the direction the character is traveling in and either displays the sprite as loaded for right hand motion or flipped horizontally for left hand motion. By using _PUTIMAGE to flip the sprites the program effectively has 12 sprite images it can display instead of the original 6 loaded.

Loading individual images is perfect for games with a small number of sprites which helps to keep the asset file count low. However, for large projects that require many sprites, say for a game like Super Mario, there would be hundreds, perhaps even thousands, of individual image files to keep track of. This is where a sprite sheet can help.

Sprite Sheets

A sprite sheet is a collection of individual sprite images contained in an ordered fashion on a larger image. By placing the individual images onto a larger sheet only one image needs to be loaded from disk. From there the individual images can be parsed out by using the _PUTIMAGE statement. The example program above has been modified to load the walking sprites from a master sprite sheet called walksheet104x156.PNG. The 104x156 in the name of the sprite sheet image file is used to identify the size of the individual sprite images contained on the sheet. This is nomenclature I have developed and used over the years while working with sprite sheets.

( This code can be found at .\tutorial\Lesson16\WalkDemo2.bas )

'** Walker Demo 2
'** Load Sprite Sheet and Parse Images

CONST BRIGHTMAGENTA = _RGB32(255, 0, 255) ' declare colors
CONST WHITE = _RGB32(255, 255, 255)

DIM Frame% '             frame counter
DIM Sprite% '            sprite image counter
DIM WalkerSheet& '       sprite sheet containing walker images
DIM Walker&(6) '         sprite images
DIM x1%, y1%, x2%, y2% ' sprite upper left and lower right X,Y coordinates
DIM Dir% '               sprite travel direction

'** Load sprite sheet and parse out individual images

WalkerSheet& = _LOADIMAGE(".\tutorial\Lesson16\walksheet104x156.png", 32)
_CLEARCOLOR BRIGHTMAGENTA, WalkerSheet&

OR Sprite% = 0 TO 5
    Walker&(Sprite% + 1) = _NEWIMAGE(104, 156, 32)
   _PUTIMAGE , WalkerSheet&, Walker&(Sprite% + 1), (Sprite% * 104, 0)-(Sprite% * 104 + 103, 155)
NEXT Sprite%

Frame% = 0 '                                               reset frame counter
Sprite% = 1 '                                              reset sprite image counter
Dir% = 1 '                                                 set sprite direction
x1% = 10 '                                                 upper left X coordinate of sprites
y1% = 50 '                                                 upper left Y coordinate of sprites
y2% = y1% + 155 '                                          lower right Y coordinate of sprites
SCREEN _NEWIMAGE(640, 480, 32) '                           enter graphics screen
DO '                                                       begin main loop
   _LIMIT 30 '                                             30 frames per second
   CLS , WHITE '                                           clear screen in white
   LOCATE 2, 21 '                                          locate text cursor
   PRINT " Right/Left arrows to walk, ESC to exit. " '     print directions
   IF _KEYDOWN(19712) THEN '                               right arrow key pressed?
        Dir% = 1 '                                         yes, set direction heading to right
   ELSEIF _KEYDOWN(19200) THEN '                           left arrow key pressed?
        Dir% = -1 '                                        yes, set direction heading to left
   END IF
   IF x1% + Dir% * 3 < 536 AND x1% + Dir% * 3 > 0 THEN '   sprite at edge of screen?
        x1% = x1% + 3 * Dir% '                             no, update X location of sprite
   ELSE '                                                  yes, at edge of screen
        Dir% = -Dir% '                                     change sprite direction
   END IF
    x2% = x1% + 103 '                                      calculate lower right hand X coordinate
   SELECT CASE Dir% '                                      which direction is sprite heading?
       CASE 1 '                                            to the right
           _PUTIMAGE (x1%, y1%), Walker&(Sprite%) '        display sprite image normal
       CASE -1 '                                           to the left
           _PUTIMAGE (x2%, y1%)-(x1%, y2%), Walker&(Sprite%) ' flip sprite image horizontally
   END SELECT
    Frame% = Frame% + 1 '                                  increment frame counter
   IF Frame% = 30 THEN Frame% = 0 '                        reset frame counter after 30 frames
   IF Frame% MOD 5 = 0 THEN '                              frame even divisible by 5?
        Sprite% = Sprite% + 1 '                            yes, increment sprite image counter
       IF Sprite% = 7 THEN Sprite% = 1 '                   keep sprite image counter within limits
   END IF
   _DISPLAY '                                              update screen with changes
LOOP UNTIL _KEYDOWN(27) '                                  leave when ESC key pressed
FOR Sprite% = 1 TO 6 '                                     cycle through the six sprite images
   _FREEIMAGE Walker&(Sprite%) '                           free image from memory
NEXT Sprite%
SYSTEM '                                                   return to operating system

The sprite sheet used in the example above

Each sprite on the sheet is 104 by 156 pixels in size and by using some simple math and the _PUTIMAGE statement each sprite can be copied and pasted to another internal image within the code. Lines 16 through 21 in the above example does this. Through each loop of the FOR...NEXT statement the location of the next image is calculated and copied into the appropriate Walker&() array index by the _PUTIMAGE statement. This method of loading one master image and then extracting each to a separate image still creates many images that need to be manipulated in code.

There is another way to use sprite sheets where the sprite sheet itself is the only image used. Whenever one of the sprites on the sheet need to be displayed the coordinates on the sheet can be referenced and _PUTIMAGE used to copy and paste directly to the screen. The next example code shows how this can be done.

( This code can be found at .\tutorial\Lesson16\WalkDemo3.bas )

'** Walker Demo 3
'** Load Sprite Sheet and Reference Images on Sheet as Needed

CONST BRIGHTMAGENTA = _RGB32(255, 0, 255) ' declare colors
CONST WHITE = _RGB32(255, 255, 255)

TYPE WALKER '             sprite locations
    x1 AS INTEGER '       upper left X
    y1 AS INTEGER '       upper left Y
    x2 AS INTEGER '       lower right X
    y2 AS INTEGER '       lower right Y
END TYPE

DIM Walker(6) AS WALKER ' sprite locations on sprite sheet
DIM Frame% '              frame counter
DIM Sprite% '             sprite image counter
DIM WalkerSheet& '        sprite sheet containing walker images
DIM x1%, y1%, x2%, y2% '  sprite upper left and lower right X,Y coordinates
DIM Dir% '                sprite travel direction

'** Load sprite sheet and record sprite locations

WalkerSheet& = _LOADIMAGE(".\tutorial\Lesson16\walksheet104x156.png", 32)
_CLEARCOLOR BRIGHTMAGENTA, WalkerSheet&
FOR Sprite% = 0 TO 5
    Walker(Sprite% + 1).x1 = Sprite% * 104
    Walker(Sprite% + 1).y1 = 0
    Walker(Sprite% + 1).x2 = Walker(Sprite% + 1).x1 + 103
    Walker(Sprite% + 1).y1 = 155
NEXT Sprite%

Frame% = 0 '                                               reset frame counter
Sprite% = 1 '                                              reset sprite image counter
Dir% = 1 '                                                 set sprite direction
x1% = 10 '                                                 upper left X coordinate of sprites
y1% = 50 '                                                 upper left Y coordinate of sprites
y2% = y1% + 155 '                                          lower right Y coordinate of sprites
SCREEN _NEWIMAGE(640, 480, 32) '                           enter graphics screen
DO '                                                       begin main loop
   _LIMIT 30 '                                             30 frames per second
   CLS , WHITE '                                           clear screen in white
   LOCATE 2, 21 '                                          locate text cursor
   PRINT " Right/Left arrows to walk, ESC to exit. " '     print directions
   IF _KEYDOWN(19712) THEN '                               right arrow key pressed?
        Dir% = 1 '                                         yes, set direction heading to right
   ELSEIF _KEYDOWN(19200) THEN '                           left arrow key pressed?
        Dir% = -1 '                                        yes, set direction heading to left
   END IF
   IF x1% + Dir% * 3 < 536 AND x1% + Dir% * 3 > 0 THEN '   sprite at edge of screen?
        x1% = x1% + 3 * Dir% '                             no, update X location of sprite
   ELSE '                                                  yes, at edge of screen
        Dir% = -Dir% '                                     change sprite direction
   END IF
    x2% = x1% + 103 '                                      calculate lower right hand X coordinate
   SELECT CASE Dir% '                                      which direction is sprite heading?
       CASE 1 '                                            to the right
           _PUTIMAGE (x1%, y2%)-(x2%, y1%), WalkerSheet&, ,_
                      (Walker(Sprite%).x1, Walker(Sprite%).y1)-_
                      (Walker(Sprite%).x2, Walker(Sprite%).y2) ' copy/paste image from sprite sheet
       CASE -1 '                                           to the left
           _PUTIMAGE (x2%, y2%)-(x1%, y1%), WalkerSheet&, ,_
                      (Walker(Sprite%).x1, Walker(Sprite%).y1)-_
                      (Walker(Sprite%).x2, Walker(Sprite%).y2)  ' copy/paste image from sprite sheet
   END SELECT
    Frame% = Frame% + 1 '                                  increment frame counter
   IF Frame% = 30 THEN Frame% = 0 '                        reset frame counter after 30 frames
   IF Frame% MOD 5 = 0 THEN '                              frame even divisible by 5?
        Sprite% = Sprite% + 1 '                            yes, increment sprite image counter
       IF Sprite% = 7 THEN Sprite% = 1 '                   keep sprite image counter within limits
   END IF
   _DISPLAY '                                              update screen with changes
LOOP UNTIL _KEYDOWN(27) '                                  leave when ESC key pressed
_FREEIMAGE WalkerSheet& '                                  remove image from memory
SYSTEM '                                                   return to operating system

In this example the Walker() array is used to hold the coordinates of each individual image on the sprite sheet. When a sprite needs to be displayed these coordinates are referenced by _PUTIMAGE instead of using an individual image file. The _PUTIMAGE statements in line 57 and 61 show how this is done. With this example the only image ever loaded and referenced is the sprite sheet.

The method chosen to use when dealing with sprites and sprites sheets is completely up to the programmer. Personally I always lean toward the second example because I prefer to have all my sprites preloaded into RAM as individual images ready for use. However, this method does take a considerable amount of RAM when dealing with hundreds or thousands of sprites. You also need to keep in mind that all of those images need to be removed from RAM before ending the program.

Numbered Sprites

There's a fourth method where images can be referenced from a sprite sheet without even using an array to hold the coordinates. Very large sprite sheets can have their individual sprites referenced by a number that can be used to calculate the x,y location of the sprite within the sheet. Figure 2 below shows a sprite sheet that contains 368 individual sprites.

Figure 2: That's a lot of sprites!

Note: The numbers have been placed on top of the individual sprites for reference only. The original sprite sheet named mario32x32.PNG does not contain the numbers.

Using this image as a guide any of the sprites can be referenced as a number from 1 to 368. The following example code shows how this can be done.

( This code can be found at .\tutorial\Lesson16\NumberedSheet.bas )

'** Sprite demo using a numbered sprite sheet

DIM MarioSheet& ' the mario sprite sheet
DIM x%, y% '      X,Y coordinate of sprite on sheet
DIM Columns% '    number of sprite columns contained on sheet

MarioSheet& = _LOADIMAGE(".\tutorial\Lesson16\mario32x32.png", 32) ' load sprite sheet
Columns% = _WIDTH(MarioSheet&) \ 32 '                                calculate number of columns on sheet

SCREEN _NEWIMAGE(320, 200, 32) '                                     enter graphics screen
DO '                                                                 begin main loop
   CLS '                                                             clear screen
   LOCATE 2, 1 '                                                     position text cursor
   LINE INPUT "Enter sprite number (0 to exit)> ", n$ '              print directions
    Num% = VAL(n$) '                                                 convert answer to numeric value
   IF Num% > 0 THEN '                                                value greater than 0?
       IF Num% MOD Columns% = 0 THEN '                               yes, is this sprite in rightmost column?
            x% = 32 * (Columns% - 1) '                               yes, calculate X coordinate of this sprite
            y% = (Num% \ Columns% - 1) * 32 '                        calculate Y coordinate of this sprite
       ELSE '                                                        no, in column left of rightmost column
            x% = (Num% MOD Columns% - 1) * 32 '                      calculate X coordinate of this sprite
          y% = (Num% \ Columns%) * 32 '                              calculate Y coordinate of this sprite
       END IF
       IF y% > _HEIGHT(MarioSheet&) - 1 THEN '                       does sprite row exist?
           LOCATE 7, 2 '                                             no, position text cursor
           PRINT "That sprite does not exist!" '                     report error to user
       ELSE '                                                        yes, row exists
           _PUTIMAGE (100, 50), MarioSheet&, , (x%, y%)-(x% + 31, y% + 31) ' copy/paste from sprite sheet
           LOCATE 7, 2 '                                             position text cursor
           PRINT "Sprite located at position"; Num% '                print results
           LOCATE 8, 2 '                                             position text cursor
           PRINT "Press any key to continue.." '                     print directions
       END IF  
       DO: LOOP UNTIL INKEY$ <> "" '                                 wait for key press
   END IF
LOOP UNTIL Num% = 0 '                                                leave when 0 entered for sprite number
_FREEIMAGE MarioSheet& '                                             remove sprite sheet from memory
SYSTEM '                                                             return to operating system

Figure 3: Accessing sprites by number

Using code in this manner eliminates the requirements for any sort of an array or secondary images all together. The only image required is the sprite sheet itself. Lines 16 through 22 of the code performs some simple calculations to determine the X and Y coordinate locations of any sprite contained on the sheet.

There is one major drawback to this method however. The sprite sheet has to have sprites that are all the exact same size because the number of sprite columns needs to be known beforehand for the calculations to work. The number of columns is calculated from the width of the sprite sheet divided by the width of an individual sprite as seen in line 8 of the example code.

Sprite sheets are a great way to quickly and easily enter sprites and animations into your games. If you do a Google search for "Sprite Sheet" you'll find thousands of pre-made sheets to choose from or create your own with your favorite graphics program.

Something you may have been wondering up to this point is, "Why is the transparent color used always bright magenta (255, 0, 255)?" Simple, it's a garish eye hurting color that no game programmer in their right mind would ever use in their game. Honestly, that's why. That, and it's easy to remember (255, 0, 255).