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'.
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.
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:
All assumptions will be listed here before diving into the code:
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!
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!
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!
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!
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.
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()
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!
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.
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()
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
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.
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!
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.
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 .......
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!
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;)
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
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!