A Simple Sprite Engine

Download this tutorial & Example Code

Introduction

It's time to pull all the bits from earlier tutorials together into a single, coherent module that can be used to display multiple sprites over a background. I say now that the technique presented here is just one of many ways of approaching the task and you may wish to customise to your own programming style, that's cool. This is intended as a Get You Started point!

Now make sure that you have plenty of time on your hands before you start working through this tutorial, its big and introduces lots of programming concepts. In an attempt not to reproduce all the code for the Engine here I have explained the function of all the routines as they are introduced. You are strongly advised to refer to the VB Source that accompanies this offering as you read, this way you will become more conversant with the code and my particular method of 'cooking'.

Terminology Used From Here On

The terminology I am going to use is my own, but is quite self explanatory:

Blitting: The process of copying graphics from one place to another. This generally requires the use of the BitBlt() function and is a 'hanger-on' from my Amiga days.

Bmp: I use this as an abbreviation of Bitmap, usually in variable names.

Frame: A rectangular area in a Bitmap that contains an image. Every sprite will have a frame associated with it, animated sprites a number of frames. A frame is described by the coordinates of its Top Left corner, its width and its height.

Gfx: I use this as an abbreviation of graphics, usually in variable names.

Mask: As already introduced, a mask is a 2 colour graphic used to achieve transparent Blitting.

Routine: I use this term to refer to subroutines and functions available from either the program itself or some dll. It saves having to type things like 'the subroutines and functions in super.dll'.

Sprite: A graphic that can be displayed and moved over a background picture.

Goals

Lets state what we are trying to achieve:

The Sprite Engine will be a self contained .BAS module with minimum dependencies. It will contain functions that can achieve the following:

  • Load a Bitmap from disk into an internal buffer. This bitmap will contain the graphics for all sprites currently in use.

  • Create a mask for the loaded bitmap. Only the one mask will be required; as the loaded bitmap contains all sprite graphics so the mask will contain all sprite masks.

  • Create a memory buffer for the game play area. The sprite engine is not going to rely on the presence of Picture Boxes, all display operations will take place on a memory DC that is copied to the display, either a Form or a Picture Box, once during each cycle of the game loop.

  • Load a bitmap from disk into the memory buffer.

  • Maintain an array that describes all frames in the loaded Bitmap.

  • Maintain an array that describes all sprites currently in use.

  • Allocate a sprite. This will mark a sprite as being used by the system and will allocate the resources needed for background saving.

  • Delete a sprite. This will mark a sprite as no in use by the system and will free the resources used for background saves.

  • Display a sprite. This will involve saving the background where the sprite is to be displayed and then copying the sprite over the background using transparent blitting.

  • Move a sprite. This will update the sprites coordinates.

  • Animate a sprite. Update the frame associated with this sprite.

  • Check for spite-sprite collisions. A requirement of all games.

  • Free all resources. A single function that can be used to stop the sprite engine prior to program termination. This function will free all resources used by the sprite engine.

    Assumptions

    All assumptions will be listed here before diving into the code:

  • The sprite engine will require a single Picture Box for loading bitmaps. This will have to exist on the main game form and should not be visible. Properties of this picture box will be changed by the sprite engine, so it should not be used elsewhere in the program.

  • Device dependant DCs are used for simplicity. This may change in a later version to use Device Independent DCs.

  • Only limited error detection will be included. TheSprite Engine is designed for programmers who have a clue! If you use indecies outside of array bounds then you expect to a crash, Sprite Engine will not disappoint!

    The programmer will never stop a program using 'End'. This is a pretty stupid thing to do anyway, it means the programmer was too lazy to code a correct shut-down routine or worse, they dont know how to shut the program down! BTW, stopping a program during development is just as bad, this is the same as hitting an 'End' statement and program shut-down routines will not be invoked. Sprite Engine allocates system resources beyond the scope of Visual Basic, they must be explicitly released or they will not be available again until the computer is shut down. Do not mail me about resaource losses with programs using Sprite Engine unless you are sure you have covered these eventualities! I may have made mistakes and will attempt to fix them if pointed out to me, I will not be a happy chappy if I find your program is taking a short-cut to disaster!

    Coding Convention

    I am going to set a few standards for this project to help make it easier to follow. Firstly variable naming. Variable names will be preceded by a scope identifier and a type identifier, if the scope identifier is absent then the variable is local to the procedure it is being used in:

    Scope Identifiers:
    m - module
    g - global
    
    Type Identifiers:
    i - integer
    l - long
    f - Single
    s - String
    t - User defined type (sprite structures etc.)
    

    Function parameters will not have a scope identifier, instead they will be preceded by an identifier that will describe how the parameter was passed to the function:

    Parameter Identifiers:
    r - passed by reference
    v - passed by value
    

    Variable names will be descriptive, usually with the first syllable of each word being included in the name and first letter capitalised. There will be one exception to this naming convention, trivial local integers such as i. Below are some examples to illustrate the technique:

    i                A trivial local integer
    j                A trivial local integer
    giHiScore        A global integer holding the High Score
    rsFileName       Function parameter passed by reference. A string holding a File Name
    mlColour         A module long holding a colour value
    

    All routines and global variable names will begin with 'Spr' to identify them as belonging to the Sprite Engine. Further, function names will be preceded by a type identifier that describes what type of data is returned by the function. The type identifier will be the same as that used for variables.

    All routines will contain a header that describes the routine, entry parameter requirements, exit parameters and comments about the routine. The header is shown below:

    'Purpose	Explains purpose of this routine.
    'Entry		Describes entry parameters
    'Exit		Describes exit parameter (functions)
    '			Also explains any parameters passed by reference that are modified
    'Comments	Notes on usage, optional!
    

    Getting Started - The Basic Module

    All routines, variables and constants used by the sprite engine are held in a single .BAS file. This file is called 'spreng.bas'. The decelerations section of this module is sub divided for quick location of constants, types, variables etc.

    To accompany the basic module is a Write document that explains all constants, variables and functions. Printing this may be a good idea, its the programmers guide to the Sprite Engine!

    The First Routine

    This will surprise you. The first routine, probably the most important routine, we are going to look at is the one used to free all resources used by the engine. It is essential this routine frees every resource or the performance of the PC will degrade each time a game is run, that means in design mode as well!

    Sub SprFreeAll ()
    
    'Purpose    Free all sprite engine resources
    'Entry      None
    'Exit       None
    'Comments   Call this just before program termination
    
    End Sub
    

    The routine shown above does not do anything, but it does in Sprite Engine. I will be referring to this routine often!

    Getting The Game Play DC

    As stated in the goals, the engine is going to perform all graphics operation on a memory DC and then copy this to the display once every game cycle, probably from the event code of a timer device!

    The routine to create this will need to know about the object the play area is going to be displayed on, the pixel width of the play area and the pixel height of the play area. Armed with this information a DC can be created. The DC will be stored in a module variable as there is no need for the game program to access it directly:

    New Variables:

    miGameDC    DC for the game play area
    miGameBmp   Bitmap for the game play area
    miGameW     Holds pixel width of the play area
    miGameH     Holds pixel height of the play area
    

    iSprGetPlayDC():

    Allocates a DC
    Allocates a Bitmap
    Selects Bitmap into DC
    Saves handles in modal variables
    Return True if operation successful
    

    A routine is required to free the play DC once the game is over.

    SprFreePlayDC():

    If Bitmap exists select it out of DC and free it
    If DC exists free it
    

    A call to SprFreePlayDC() is added to SprFreeAll() as well. This ensure that the DC is released when the game finishes.

    The above routines will create and destroy a play area. A routine is required to display the play area. This routine will be called once during each cycle of the game loop, usually from the event code of a timer device, and also whenever the form the game is displayed in receives a Refresh.

    SprShowPlayDC():

    If play DC exists then copy it to the specified DC at specified position.
    

    Loading The Sprite Graphics

    It is time to start working on the Sprites themselves. First off loading the graphics from disk. I am not a believer in building the graphics into the executable and prefer to supply *.BMP files that the executable loads at run time. This has the added advantage of letting the user refine the game graphics without having to recompile the game itself.

    The load routine will load the graphics from disk into a DC and will also create the mask DC. That is its sole function. Should an attempt to allocate a resource fail then all resources allocated will be released and the function return false.

    New Variables:

    miGfxDC     DC for the sprite graphics
    miGfxBmp    Bitmap for the sprite graphics
    miMaskDC    DC for the sprite masks
    miMaskBmp   Bitmap for the sprite masks
    

    iSprLoadGfx()

    Exit if gfx are already loaded.
    Load gfx into a temporary Picture Box
    Allocate a DC
    Allocate a Bitmap
    Copy the gfx from the PictureBox into the DC
    Allocate a DC for masks
    Allocate a Bitmap for masks
    Create masks
    

    A routine to free the resources used to hold the sprite graphics and masks is required:

    SprFreeGfx()

    If Mask Bitmap exists select it out of DC and free it
    If Mask DC exists free it
    If Sprite graphics Bitmap exists select it out of DC and free it
    If Sprite graphics DC exists free it
    

    This routine will be called from SprFreeAll()

    The Frames Array

    There is a global array that contains details of all frames in the sprite graphic DC. This array should be initialised by the game program, it is not initialised by the sprite engine and no defaults are configured. The array utilises a user defined type as shown below:

    New User Defined Type:

    Type SprFrame
    	iX as Integer
    	iY as Integer
    	iW as Integer
    	iH as Integer
    End Type
    

    New Variable:

    gtSprFrm()  Array of SprFrame types that holds all frame details
    

    Note that the array is dynamic and should be dimensioned each time a new graphics file is loaded from disk.

    After loading sprite graphics from disk, the game program should initialise this array. This can be done manually, with code along the lines of:

    ReDim gtSprFrm(2)	'Two frames only!
    
    gtSprFrm(0).iX = 0	'First frame
    gtSprFrm(0).iY = 0
    gtSprFrm(0).iW = 32
    gtSprFrm(0).iH = 16
    
    gtSprFrm(1).iX = 0	'Second frame
    gtSprFrm(1).iY = 16
    gtSprFrm(1).iW = 32
    gtSprFrm(1).iH = 16
    

    This would soon become a tedious effort, so a routine is provided that will read frames from a disk file. It assumes the file lives in the same directory as the executable, you may want to change this! Frame files are text files, each line holding comma delimited data for a single frame with the first line holding the number of frames in the file:

    Line 1: Number of frame definitions to follow
    Line 2: frame 0 iX,iY,iW,iH
    Line 3: frame 1 iX,iY,iW,iH
    Line 4: frame 2 iX,iY,iW,iH
    
    Eg. The following describes the two frames presented above:
    
    2
    0,0,32,16
    0,16,32,16
    

    Note that there is potential here for a utility that allows the programmer to select frames in a *.BMP file using banding boxes and then save the frame details to a file. Anyone interested?

    iLoadFrames()

    Open specified file
    Read number of frames
    Redimension array
    Read one frame at a time and add to array
    Close the file
    

    Why use frames? The answer is that many sprites can use the same frame with only one instance of the frame being loaded, this minimises required resources. However there is another reason and that is animation! Animation will be handled as a series of consecutive frames. This makes writing the animation driver a piece of cake, more on this subject soon!

    The Sprite User Type and Array

    This is a biggy! This user type will contain all details required to maintain a single sprite. Details of all sprites are maintained in an array in the same way as frames are. The sprite structure and array are shown below, followed by a description of each field in the structure:

    New User Defined Type:

    Type SprSprite
        iInUse As Integer           'True if sprite is allocated
        iActive As Integer          'True if sprite is displayed
        iX As Integer               'X position at which last drawn
        iY As Integer               'Y position at which last drawn
        iW As Integer               'Pixel width of sprite
        iH As Integer               'Pixel height of sprite
        iFrame As Integer           'Current frame index into gtSprFrm()
        iFrameX As Integer          'X offset into sprite graphics DC
        iFrameY As Integer          'Y offset into sprite graphics DC
        iSaveDC As Integer          'DC for background saves
        iSaveBmp As Integer         'Bitmap for background saves
        iFirstFrame As Integer      'Index of first frame in anim sequence
        iLastFrame As Integer       'Index of last frame in anim sequence
        iAnimAuto As Integer        'True if sprite is automatically animated
        iAnimRate As Integer        'Speed of animation (cycles per frame)
        iAnimCount As Integer       'Down counter until next frame of anim
        iUser1 As Integer            'User data
        iUser2 As Integer            'User data
        iUser3 As Integer            'User data
    End Type
    

    New Variable:

    gtSpr()     Array of SprSprite types that holds all sprite details
    

    iInUse is a flag that is set to True when a sprite is allocated and resources are in being used. It does not mean that the sprite is being displayed!

    iActive is a flag that is set to true for sprites that are to be displayed, or considered for display, collision detection etc.

    iX is the X pixel coordinate of this sprite in the game play DC.

    iY is the Y pixel coordinate of this sprite in the game play DC.

    iW is the pixel width of this sprite.

    iH is the pixel height of this sprite.

    iFrame is the current frame being displayed by this sprite.

    iFrameX is the X pixel coordinate of the frame in the sprite graphics DC.

    iFrameY is the Y pixel coordinate of the frame in the sprite graphics DC.

    iSaveDC is the handle of the DC used to hold a copy of the background where this sprite is displayed.

    iSaveBmp is the handle of the Bitmap used to hold a copy of the background where this sprite is displayed.

    iFirstFrame identifies the first frame in an animation sequence that is being displayed by this sprite.

    iLastFrame identifies the last frame in an animation sequence that is being displayed by this sprite.

    iAnimAuto is a flag used to indicate that the sprite engine should animate this sprite. Set this to false if the sprite is to be animated manually, for example a walking man under player control.

    iAnimRate determines how fast the sprite animates.

    iAnimCount is a counter that is used to determine when to move on to next frame in animation sequence. The counter starts with the value held in iAnimRate and is decrease during each call to animation routine. When the counter reaches zero it is reset and thence frame in the animation is used.

    iUser1 is not used by the sprite engine and is for game specific customisation.

    iUser2 is not used by the sprite engine and is for game specific customisation.

    iUser3 is not used by the sprite engine and is for game specific customisation.

    As you can see this is a real beast, but it is used to control all aspects of the sprite system as you will soon see.

    Allocating A Sprite

    This is where the fun begins. At the start of each level of a game all the sprites will be allocated ready for use. Allocation requires creating a DC for background saves, configuring the animation sequence and preparing the sprite for display. All this will be handled by a single function that will return true if the sprite was successfully allocated. Note that allocating a sprite will not display it.

    Because a fair amount of information is required for a new sprite I have used a user type as the entry parameter for the allocation routine. This type is shown below and is followed by an explanation of each field:

    New User Defined Type:

    Type SprNewSprite
        iId As Integer
        iFirstFrame As Integer
        iLastFrame As Integer
        iAnimFlag As Integer
        iAnimRate As Integer
    End Type
    

    iId is the index into gtSpr() to use for this sprite. No bounds checking is done by the engine, so get this right! I suggest that you use constants to identify sprite IDs, this will increase the readability of your code and make debugging a whole lot easier (this is covered soon).

    iFirstFrame is the index into gtSprFrm(), it is also the initial frame displayed by the sprite and is used to determine the size of the sprite. This raises an important limitation of the animation system; all frames in the sequence should be the same size. Failing to meet this criteria will not crash the engine, but will produce pear shaped results!

    iLastFrame is the index into gtSprFrm() of the last frame in the animation sequence.

    iAnimFlag should be True if this Sprite is to be animated automatically by Sprite Engine.

    iAnimRate is the speed at which the sprite animates.

    That was painless! Please bear in mind the notes above about the limitations of the animation system, failure to do so will break the background save mechanism and the background will become corrupt. Now onto the routine required to actually allocate a sprite:

    iSprAllocateSprite()

    If there is no play area DC then exit
    If the requested sprite is already allocated then exit
    Allocate a save DC based on size of first animation frame
    Allocate a save Bitmap based on size of first animation frame
    Fill in fields of gtSpr() for this sprite
    

    This routine has a partner that releases the resources used by a sprite:

    SprFreeSprite()

    If this sprite is not allocated then exit
    If there is a save Bitmap select it out of DC and free it
    If there is a save DC free it
    Mark the sprite as not allocated
    

    There is also a routine that frees all sprite resources, this should be called prior to initialising sprites at the start of a level and just prior to exiting the game. To ensure all sprites are released when the game ends this function is called from SprFreeAll()

    SprFreeAllSprites()

    For each entry in gtSpr() call SprFreeSprite()
    

    Sprite Activation

    At the start of a level all sprites will be allocated, but not all of these will necessarily be displayed. For instance there may be ten bullet sprites, but these will not be displayed until bullets are fired. So an allocated sprite can be marked as Active/Inactive to control wether it is to be displayed or not. Also, only active sprites are processed by sprite movement, animation and collision routines contained in the sprite engine.

    More theory! We are going to have to jump the gun a bit here and consider the sprite display process to understand what is required to activate a sprite.

    I am assuming that sprites will be controlled by a routine that is called at regular intervals as the game progresses, probably the event procedure of a timer device. I call this process the game loop and below is a typical outline of such a loop:

    1. Restore background where sprites are displayed
    2. Do additional game processing such as check keys, move sprites, collision checks
    3. Save background where sprites are going to be displayed
    4. Copy sprites into the background
    5. Copy background to the display
    

    In the above process, steps 1,3,4 and 5 are all going to be handled by the sprite engine. Steps 1, 3, and 4 loop through gtSpr() and process all sprites that are marked as active. The important factor is the first step, restoring backgrounds. When a sprite is activated a copy of the background where it is going to be displayed will need to be taken so that on the first pass through the game loop rubbish is not restored into the background by step 1.

    There is another condition to satisfy as well. When sprites are to be activated during the game loop, such as bullets and explosions, they must be activated between steps 1 and 3. Why? This is the only time that the background is intact with no other sprite data merged into it.

    Again jumping the gun, sprites may only be deactivated between steps 1 and 3. Why, because the background must be restored before switching the sprite off!

    With that out of the way, let us move on to the activation routine:

    SprActivateSprite()

    Exit if sprite is no allocated
    Set the sprites X, Y coordinates
    Take a copy of background at (X,Y) where sprite will be displayed
    

    And its partner function that marks the sprite as inactive:

    SprDeactivateSprite()

    Exit if sprite is not allocated
    Mark sprite as inactive
    

    The Sprite Display Routines

    Time to take another look at the game loop model I presented above:

    1. Restore background where sprites are displayed
    2. Do additional game processing such as check keys, move sprites, collision checks
    3. Save background where sprites are going to be displayed
    4. Copy sprites into the background
    5. Copy background to the display
    

    For the sprite engine to satisfy steps 1, 3 and 4 two routines are provided. One restores backgrounds, the other saves backgrounds and copies sprites into the background. Both of these routines only process active sprites:

    SprRestore()

    Exit if there is no play area DC
    For each active sprite in gtSpr(), working backwards through the array
        Copy data from sprites save DC into the background where sprite was displayed
    

    SprDraw()

    Exit if there is no play area DC
    For each active sprite in gtSpr(), working forwards through the array
        Copy data from background into sprites save DC where sprite is to be displayed
    	Copy sprite data into background using transparent blitting
    

    So why does the restore routine go through the array backwards? The answer lies in the display process, backgrounds are saved just prior to displaying the sprite. This means that as more and more sprites are displayed it is possible an overlap will occur and this means that a corrupt background clip will be saved. To restore the background, restores must happen in the reverse order!

    At this point you have enough to get sprites up and running, but there is more required for a game. The routines presented from here on are what make using a sprite engine the easiest way to program a game.

    Moving Sprites

    There are two routines for moving sprites, on will move the sprite to an absolute position while the other will move the sprite relative to its current position. These routines do not actually move a sprite, they just updates the sprites position. The sprite will be displayed at the new position when SprDraw() next gets called.

    SprMoveSprite()

    Exit if sprite is not active
    Update sprites iX and iY fields
    

    SprMoveSpriteRel()

    Exit if sprite is not active
    Update sprites iX and iY fields
    

    Important, always call these routines after backgrounds have been restored. The iX and iY fields are used by the restore routine to determine where the sprite was last displayed!

    Animating Sprites

    There are a number of sprite animation routines provided, these support both manual and automatic animation.

    Consider automatic animation first. The frames in the animation sequence are considered cyclic, that is after displaying the last frame in the sequence the process starts again with the first frame in the sequence. Sprites using this type of animation can be animated by the sprite engine, set the iAnimAuto flag to True when allocating the sprite and make sure the following routine is called during the game loop:

    SprAnimAuto()

    For each active entry in gtSpr() with iAnimAuto set true
        Decrease iAnimCount
        If iAnimCount is zero then
            reset iAnimCount to value in iAnimRate
    		bump iFrame
    		If iFrame > iLastFrame then reset iFrame to iFirstFrame
    		set iFrameX and iFrameY to values in gtSprFrm(iFrame)
    

    Now manual animation. There are many ways of using the frame sequence in a game. For instance the frames could show a man walking, but the sprite is user controlled and you would only move to the next frame when the user is pressing the appropriate key. Or the sprite may represent a homing missile with each frame showing the missile travelling in a different direction, you would want to select the frame according to the direction the missile is travelling at any instance. I cannot provide solutions to all the possibilities, but I have provided four general routines. If you cannot make one of these suit you need then you will have to add your own. There is no reason why each manually animated sprite cannot have its own animation routine (apart from the speed of the system!). Note the following routines do not test if the sprite is manually animated or not, I assume you know!

    The first routine will step the sprite onto the next frame in its animation sequence, wrapping round to the start once the sequence is complete.

    SprAnimNextFrame()

    If sprite is active
    	Bump iFrame
    	If iFrame > iLastFrame then reset iFrame to iFirstFrame
    	Set iFrameX and iFrameY to values in gtSprFrm(iFrame)
    

    There is also a routine for stepping through the animation sequence backward, again the sequence wraps:

    SprAnimPrevFrame()

    If sprite is active
    	Decrease iFrame
    	If iFrame < iFirstFrame then reset iFrame to iLastFrame
    	Set iFrameX and iFrameY to values in gtSprFrm(iFrame)
    

    The following routine will set the frame to whatever value is passed. There is no requirement that the frame passed is part of the animation sequence, but care should be taken to ensure that the frame specified is the same size as frames used by the sprite:

    SprAnimSetFrame()

    If sprite is active
    	If specified frame is within animation sequence range
    		Set iFrame to specified frame
    		set iFrameX and iFrameY to values in gtSprFrm(iFrame)
    

    Finally, specify which frame from the animation sequence to use; 0, 1, 2,.... Note that there is no bounds checking done by this routine, get it right!

    SprAnimSetFrameRel()

    If sprite is active
    	If specified frame is within animation sequence range
    		Set iFrame to specified frame
    		set iFrameX and iFrameY to values in gtSprFrm(iFrame)
    

    You now have all the routines needed to display, move and animate sprites.

    Collision Detection

    This is a subject and a half so I will say now that the detection algorithm offered is crude to say the least and could be improved on. I may well improve it myself soon. It works by seeing if the rectangles containing the sprites being tested, defined by iX, iY, iW and iH, are overlapping. If they are then a collision is flagged. Yes I agree it sucks, but it works and is easy to code.

    In a game it is common to want to check for a particular sprite colliding with one of a range of sprites, for example a players ship with any baddies bullets, as well as the occasional check of one sprite hitting another. For this reason there are two collision detection routines included in Sprite Engine. The first check if one sprite has hit another and returns true if so. The second that if one sprite has hit any in a range of sprites, if so it returns the ID+1 of the sprite hit otherwise it returns 0.

    iSprCollision()

    If both sprites are active
    	If bounding rectangles overlap flag collision
    

    iSprCollisionRange()

    For all sprites in supplied range
    	If both sprites are active
    		If bounding rectangles overlap flag collision and exit
    

    A quick note about the bounding rectangles routine. This does not check for overlap, rather it checks to see if one of four possible conditions are satisfied. If any of the conditions are satisfied then there is no way the rectangles are overlapping. If none of the conditions are satisfied then the rectangles must be overlapping as they are not (not overlapping). I would hate to have to provide the geometric proof of this algorithm, but it does work!

    A further note on improving the collision detection routine. One method would be to include a radius with each sprite so that the circle described by the radius from the center of the sprite is smaller than the bounding rectangle. Now to see if two sprites have collided see if the sum of their radii is shorter than the distance between their centers. Yeuk!

    The only way to truly check for collisions is to use collision masks, similar to the masks used for transparent blitting and a collision DC that is the same size as the play area. First copy the mask for one sprite into the collision DC and then AND the mask of the second DC with the collision DC one BYTE (or WORD as that's how bitmaps are aligned) at a time and test for true. If a single truth is encountered then there must have been set bits in the collision DC from the first mask at the same point as set points from the second mask were being copied, ie a collision occurred. Now on the Amiga, the Blitter had a mode that would do this, on the PC the code would need to be written (someone please correct me on this) and that would require a language with a little more flexibility and speed than VB. Maybe .......

    What Next

    Congratulate yourself, you now have everything you need to start writing games with animated sprites.

    What else? Well if I was to do any more I may as well write the game for you! Oh, examples. I almost forgot! I suppose I should demonstrate things in action just to prove the whole thing gels together and does what I say it should.

    In the examples that follow I have created separate *.BAS modules to hold the startup code, global constants not specific to Sprite Engine and example specific routines. This is not always a good idea and if you can put all this information into a single file then do so, however VB 3 suffered from an intermitent problem when dealing with files bigger than 64k so bear this in mind! All examples start from Subroutine Main(), not a form! If you are not familiar with this technique then read RTFM!

    Example 1

    This example is really going to stretch you and push the Sprite Engine right to its limits, it displays a single animated sprite! Ok this may seem like a non-event, but I am using the example to illustrate more than just the Sprite Engine; good programming practice (Ouch, that's asking for it;)!

    The project consists of a single form containing two objects; a Picture Box for use by the Sprite Engine and a Timer used to call Sprite Engine routines at regular intervals.

    As you know the Sprite Engine uses arrays to track sprites and frames. Have you ever looked through code, seen an array item referenced and thought to yourself "what the hell is that?". Yep! Make sure it doesn't happen in your code, use constants to define important array indecies. From eg1.bas, declarations:

    'Define how many sprites are in use
    Global Const MAX_SPRITES_USED = 1
    
    'Define the sprite
    Global Const LONELY_SPRITE_ID = 0
    Global Const LONELY_SPRITE_FRM_FROM = 20
    Global Const LONELY_SPRITE_FRM_TO = 21
    

    This may seem pointless in a program using just the one sprite, but you will see how useful the practice becomes very soon!

    Now to the startup code. This is just a 'noddy' program so it does not require level initialisation and all the other blurb that clutters up a real game, all the initialisation can be done in Main():

    Initialise the array used to hold the sprites.
    Load the form that the game will be displayed on. This must be done
     first because the sprite engine needs to know about the forms hDC
     and uses a picture box on the form for loading *.BMP files!
    Allocate a Play Area DC.
    Load the file that holds all sprite graphics.
    Load the file that defines all frames in the sprite bitmap.
    Allocate a single sprite, this requires defining the sprite.
    Activate the sprite.
    Show the form, all preperation is now complete. Note that the form
     will be show as modal so control will not return until the
     form is hidden or unloaded.
    Release all resources.
    Make sure that the form is unloaded.
    

    Looks reasonable so here is the code:

    Sub Main ()
    
    Dim i As Integer
    Dim tSpr As SprNewSprite
    
    'Must initialise the array
    ReDim gtSpr(1)
    
    'Load the game form
    Load frmMain
    
    'Get play area
    i = iSprGetPlayDC(frmMain.hDC, 300, 200)
    
    'Load sprite graphics
    If i Then
        i = iSprLoadGfx(frmMain.picSpr, "eg.bmp")
    
        'Load sprite graphic frame data
        If i Then
            i = iSprLoadFrames("frames.mm1")
    
            'Get a single sprite
            If i Then
                tSpr.iId = LONELY_SPRITE_ID
                tSpr.iFirstFrame = LONELY_SPRITE_FRM_FROM
                tSpr.iLastFrame = LONELY_SPRITE_FRM_TO
                tSpr.iAnimFlag = True
                tSpr.iAnimRate = 4
                i = iSprAllocateSprite(tSpr)
    
                'Activate the sprite
                If i Then
                    SprActivateSprite LONELY_SPRITE_ID, 10, 10
    
                    'Show the form and let play commence!
                    frmMain.Show VBModal
                End If
            End If
        End If
    End If
    
    'Make sure all resources are released
    SprFreeAll
    
    'Trash the form
    Unload frmMain
    
    End Sub
    

    Notice how flow only continues if a resource is allocated, this is good practice. I do not bother checking the return of functions such as BitBlt() or DeleteDC() as there is nothing that can be done if they fail, but trying to continue when resource allocation has failed is never a wise move.

    Ok, the sprite is allocated and activated plus the form is now visible. The rest of the program lives in the timer event procedure:

    Restore the paly area where the sprite was displyed.
    Animate the sprite
    Copy the sprite into the play area.
    Copy the paly area to the display (window).
    

    This is more like it! All of the above 4 requirements are met by routines available in Sprite Engine:

    Sub tmrGameLoop_Timer ()
    
    'Restore backgrounds
    SprRestore
    
    'Animate sprites
    SprAnimAuto
    
    'Draw sprites
    SprDraw
    
    'Update the display
    SprShowPlayDC frmMain.hDC, 10, 10
    
    End Sub
    

    See how easy it is! You to could soon be displaying a single sprite in a window on a PC not to far from where you are sitting. With a little work you might even progress to a game, but see the next example first;)

    Example 2

    Time for some action! This example uses three sprites; an invader, aplayer and a bullet. The invader is static, I did not want to bog the code down with movement logic. The players ship moves, the keyboard handler covered in Tutorial 2 is used to get key status (were you paying attention?). The player can fire a bullet which can hit and kill the invader.

    I started programming the example by copying the previous one. I added constants to the .BAS file for all the sprites and key codes I need, moved the sprie preperation code into a seperate routine, added the keyboard code from tutorial 2 and then expanded the game loop logic as follows:

    Restore backgrounds
    Animate sprites
    Check if players bullet hit the invader, deactivate both if so
    Check if players bullet is off screen, deactivate it if so
    Move players sprite if left or right cursor key is pressed
    Move players bullet if its active
    Activate the players bullet if fire key pressed and bullet is not active already
    Draw sprites
    Update the display
    

    Coding this is not too difficult as most of the work is done by Sprite Engine routines:

    Sub tmrGameLoop_Timer ()
    
    'Restore backgrounds
    SprRestore
    
    'Animate sprites
    SprAnimAuto
    
    'Check if players bullet hit invader
    If iSprCollision(PLAYER_BULLET_ID, INVADER_ID) Then
        SprDeactivateSprite PLAYER_BULLET_ID
        SprDeactivateSprite INVADER_ID
    End If
    
    'Check if players bullet is off screen
    If gtSpr(PLAYER_BULLET_ID).iActive Then
        If gtSpr(PLAYER_BULLET_ID).iY < 0 Then SprDeactivateSprite PLAYER_BULLET_ID
    End If
    
    'Move players sprite if key pressed
    If giKeyStatus And KEY_CUR_LEFT_FLAG Then SprMoveSpriteRel PLAYER_ID, -10, 0
    If giKeyStatus And KEY_CUR_RIGHT_FLAG Then SprMoveSpriteRel PLAYER_ID, 10, 0
    
    'Move players bullet if active
    If gtSpr(PLAYER_BULLET_ID).iActive Then SprMoveSpriteRel PLAYER_BULLET_ID, 0, -10
    
    'Fire a bullet if key pressed and bullet is free
    If Not gtSpr(PLAYER_BULLET_ID).iActive Then
        If giKeyStatus And KEY_FIRE_FLAG Then
            SprActivateSprite PLAYER_BULLET_ID, gtSpr(PLAYER_ID).iX + (gtSpr(PLAYER_ID).iW \ 2) - (gtSpr(PLAYER_BULLET_ID).iW \ 2), 160
        End If
    End If
    
    'Draw sprites
    SprDraw
    
    'Update the display
    SprShowPlayDC frmMain.hDC, 10, 10
    
    End Sub
    

    Closing Comments

    writing this tutorial and producing code suitable for others to use required quite a lot of effort on my part. I would like to see the code get used to make all the effort worthwhile. The copyright for Sprite Engine is mine, but I grant everyone the right to use and abuse the code in any way they see fit. If you do produce a game using the code then please consider doing the following:

    1. Let me know just for the ego trip.
    2. Mail me a copy of the game or a URL so I can come and see it.
    3. Include some credit for me in the About box or documentation.
    4. Include my URL in the documentation so others can come and join in the fun.
    5. Let me keep a copy of the game for download from my site, or a link to it at your site.
    

    If you found this tutorial useful then please let me know. If people are reading what I write then I will continue, otherwise the tutorial stream will cease to flow.

    Enjoy!

    Back To Tutorials


    This site is administered by Mark Meany.
    Copyright © 1997 by Mark Meany. All rights reserved.
    Last Revised: