Commonwealth Network Creating A Game With VB4 This tutorial was written by Timbo, who's page is located at http://www.geocities.com/SiliconValley/Park/3269/ Part 2 - Introduction to Animation Today I'm going to put the actual game project aside so that we can explore one of the more interesting and fun parts of game programming - animation. Animation is really just an optical illusion. Objects are drawn on the screen in one position, erased, and redrawn in another position quickly enough so that the illusion of movement is created. There are different ways to make the characters of your game move around the screen. If you've developed games in other programming languages, you could probably think of four or five techniques without hesitation. Our problem is how do we draw, erase, and redraw our characters fast enough to fool the eye using Visual Basic. We could use the graphics OCX controls that come with Visual Basic (picture box, image, image clip, etc…) and their drawing methods, but I suspect that they would be too slow for animation in a game. We could use commercial animation controls, which would be ideal if we had the money to buy them. We could use the Windows API, but that gets really complicated when you have to deal with memory manipulation and such. I'm going to try a combination of standard OCX controls for handling the images, and the API to move and manipulate them. I'll leave the other techniques for you to explore (and, hopefully, share with me.) Setting up the picture box controls is pretty straightforward, set the size of the control to the size of the bitmap and set the picture property to the name of the bitmap. BitBlt - The Bitmap Blaster To handle the moving and manipulating of the images, we'll use the API routine BitBlT (Bit Block Transfer). BitBlt copies a rectangular area of a bitmap in a specified source device context (DC) to a specified destination DC. A device context, according to Nigel Thompson in Animation Techniques in WIN32, is a structure that defines a set of graphic objects and their associated attributes, and the graphic modes that affect output. I just think of it as a place where graphics go. This function should throw our graphics around fast enough to create a passable video game. The only drawback I can see is that BitBlt copies a rectangular bitmap. Unless our game uses nothing but rectangular images, this wouldn't be a problem. I have yet to see, nor would want to see, a video game with a bunch of rectangular blocks floating around the screen (Tetris doesn't count, those are irregular shapes formed out of square blocks). There is a way to copy irregularly shaped graphics using BitBlt. It involves a technique called masking and a feature of BitBlt called Raster OPerations or ROP's. Basically, ROP's are ways of manipulating and combining the source bitmap, destination bitmap, and the currently selected brush shape. I don't want to go into brushes just yet, so we'll just pretend they don't exist. Using a combination of ROP's and a special bitmap called a mask, we can combine our source bitmap with our destination bitmap without an ugly rectangular border around it. It's a lot easier to show you than it is to tell you how this works. I wrote a program called ROP Codes so that I could figure out how BitBlt and ROP's worked. ROP Codes ROP Codes consists of a form called FORM1 and a module called ROP.bas. I put three picture box controls on the form: picDest, for the final destination of the masked image; picSource, the bitmap that I want to put onto the destination; and picMask, the bitmap used to mask out the surrounding black area of the source bitmap. The first list of options are the ROP Codes used for subsequent BitBlt's. The name of the ROP code is followed by the number, in hexadecimal, used for that ROP in the BitBlt call. The decimal equivalent is given in parentheses. The other two option lists are to specify the source and destination of the BitBlt function. The last option on these lists, the Stage bitmap, is an area of memory set aside to save a graphic or perform off-screen graphics operations. To use this program, simply select a source, destination, and ROP code and press the Blit It button to make it happen. To reset the picture boxes back to their original states, press the Reset Bitmaps button. Try different combinations of sources, destinations and ROP codes to make the ball appear on the destination bitmap without the black border around it. I found the following sequence works pretty well: o Select Destination Bitmap for the source, Stage Bitmap for the destination & SrcCopy for the ROP code. This places a 32x32 pixel portion ot the background into the stage for later use. Don't forget to press the Blit It button. o Select Mask Bitmap for the source, Destination Bitmap for the destination & SrcAnd for the ROP code and Blit It. This copies only the black pixels of the mask bitmap to the background. o Select Source Bitmap for the source, Destination Bitmap for the destination & SrcInvert for the ROP code and Blit It. This copies the ball without the black border to the background. I don't know how or why, yet, but I'll figure it out. o To 'erase' the ball, select Stage Bitmap for the source, Destination Bitmap for the destination & SrcCopy for the ROP Code. One problem with this method, is that, in a real animation, blitting the mask onto the background just before the image might cause a flicker. The ideal thing to do is to blit everything off-screen in the stage first, then blit the masked image back to the background. I tried that with the above technique, but the ball inexplicably ended up with a yellow border around it. Experiment with different ROP codes and see if you can figure out a better way to accomplish the same thing. Let me know what you found. Next time, we'll use what we've learned here to make an actual animation. ROPCodes ZIP File Download Part 3 - Animation Using the PaintPicture Method Well, I couldn't figure out that problem with the yellow background. Any help on this would be greatly appreciated. It looks like I'm going to have to put BitBlt on the back burner until I can figure it out. Anyway, I want to make something move across the screen, so today I'll play around with the PaintPicture method of the PictureBox Control. This is a quick way to implement animation, but I believe it's going to be too slow for an actual video game. The PaintPicture method is very similar to the BitBlt function. In fact, I suspect PaintPicture might even use BitBlt to do its thing. In order to do animation, you need bitmaps of your character or 'sprite' in different poses to create the illusion of movement. I don't have time to draw my sprites for Splat, yet, so I'll borrow a sprite from SpriteLib--an excellent library of sprites for video games, written by Ari Feldman. The sprite I used is a running cat. I shrunk the size and made a mask of each cell in the graphic and saved it as CatWalk.bmp. It consists of four animating cells of the cat running and four masking cells. Each cell is 115 pixels wide by 64 pixels high. [Image] Then I created a form with a PictureBox control named picBG. I set the AutoRedraw property of picBG to false, the Picture Property to some arbitrary background BMP, and the ScaleMode property to 3 - Pixel. I added a Command Button called cmdAnimate, a Timer control called Timer1 (Interval set to 50 & Enabled set to FALSE), and a Command Button called cmdExit. I created a PictureClip control named CatClip and set its Picture property to CatWalk.bmp. I set the Rows property of CatClip to 2 and the Columns property to 4. This way I can use the GraphicCell method to clip the animation frame I want. I added these constants to the Declarations section of the form: Const SrcAnd As Long = 8913094 Const SrcInvert As Long = 6684742 Const SrcCopy As Long = 13369376 They will be used to determine how the specified image gets painted with the PaintPicture method. Then I added this code to the Timer1_Timer event procedure: Private Sub Timer1_Timer() picBG.Refresh Dim x As Single, y As Single Static CurPic As Integer Static CurX As Single If CurPic > 3 Then CurPic = 0 If CurX > 540 Then CurX = 0 picBG.PaintPicture CatClip.GraphicCell(CurPic + 4), _ CurX, 290, , , , , , , SrcAnd picBG.PaintPicture CatClip.GraphicCell(CurPic), _ CurX, 290, , , , , , , SrcInvert CurPic = CurPic + 1 CurX = CurX + 25 End Sub I defined two static variables, CurPic & CurX. CurPic is used for each frame in the animation. CurX is the current horizontal position of the sprite. The first PaintPicture call is used to put the mask onto the background. The second PaintPicture is used to put the current frame of the animation onto the background. Then the frame is incremented by one and the current position of the sprite is incremented by 25. The cmdAnimate button is used to control the animation. It's like a toggle. If the animation is stopped the caption on the button says "Animate", if the animation is running, the caption says "Stop". Here is the code used to implement this: Private Sub cmdAnimate_Click() If cmdAnimate.Caption = "Stop" Then Timer1.Enabled = False cmdAnimate.Caption = "Animate" Else Timer1.Enabled = True cmdAnimate.Caption = "Stop" End If End Sub The last bit of code in this program is for the cmdExit button. It is used to exit the program and the code simply contains the End statement. That's it. It's not too impressive. Flicker seems to be minimal, but this is only one sprite. In a real game, we would have at least 1 more (the bad guy). Try changing the code to animate four cats to realize the limitations of using this animation method. Private Sub Timer1_Timer() picBG.Refresh Dim x As Single, y As Single Dim Cat As Integer Static CurPic As Integer Static CurX As Single If CurPic > 3 Then CurPic = 0 If CurX > 540 Then CurX = 0 For Cat = 0 to 3 picBG.PaintPicture CatClip.GraphicCell(CurPic + 4), _ CurX, 290 - (Cat * 100), , , , , , , SrcAnd picBG.PaintPicture CatClip.GraphicCell(CurPic), _ CurX, 290 - (Cat * 100), , , , , , , SrcInvert Next Cat CurPic = CurPic + 1 CurX = CurX + 25 End Sub We definitely need to figure out a different way of doing this. Anyway, download the example if you don't want to do it on your own. Tinker with it, try making your own sprites, try moving them in different directions or positions, etc.. Next time, I hope to get this BitBlt problem worked out. Maybe then we can get down to some real animation. Animate01 ZIP File Download Part 4 - Animation Using Memory DC's and BitBlt Finally, after many trials and tribulations, I've figured out how to load a bitmap directly into a Memory DC. Let's back up and review what we were trying to accomplish. We wanted to use the BitBlt function to quickly paint trying to accomplish. We wanted to use the BitBlt function to quickly paint an image onto the screen. We did that using some picture box [Image] controls, but it wasn't very useful for animation. What we needed was a way to load the background image into memory, paint the current image of the animation onto it, then paint the whole thing back to the screen (See the figure on the right.). And it had to do it quickly. I had a difficult time figuring out how to do this, but I managed to load a bitmap directly into memory without using a picture box. Here's how I did it. First, I needed to create a staging area to paint cells for the sprite into memory. I accomplished this by using the CreateCompatibleDC API function. This function basically sets aside an area of memory that stores graphics data. Here is the code: First, the API declares and constants in the Declarations section of the module Option Explicit REM CreateCompatibleDC creates the memory dc, or staging areas for the graphics. Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Any) As Long REM CreateCompatibleBitmap makes a temporary (bogus) bitmap. Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc As Long, _ ByVal nWidth As Long, ByVal nHeight As Long) As Long REM DeleteDC frees the memory used by a memory dc after you are finished with it Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long REM SelectObject Selects a bitmap into the specified dc Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, _ ByVal hObject As Long) As Long REM BitBlt (Bit Block Transfer) copies a graphics area to another graphics area Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, _ ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, _ ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, _ ByVal dwRop As Long) As Long REM Constants used for the dwRop parameter in BitBlt. REM These are different Raster Operations or ROP's Public Const SRCAND = &H8800C6 Public Const SRCCOPY = &HCC0020 Public Const SRCINVERT = &H660046 The function for creating a memory dc - NewDC Function NewDC(hdcScreen As Long, HorRes As Long, VerRes As Long) As Long Dim hdcCompatible As Long Dim hbmScreen As Long hdcCompatible = CreateCompatibleDC(hdcScreen) 'Create the DC hbmScreen = CreateCompatibleBitmap(hdcScreen, HorRes, VerRes) 'Temporary bitmap If SelectObject(hdcCompatible, hbmScreen) = vbNull Then 'If the function fails NewDC = vbNull ' return null Else 'If it succeeds NewDC = hdcCompatible ' return the DC End If End Function We now have a way to create an area in memory to store bitmaps. What we need next is a way to put the bitmaps of our background and sprite into these areas. We can do that with the SelectObject function. The SelectObject function selects an object (bitmap, brush, font, pen, region) into the given device context (DC). The new object replaces the previous object of the same type. The only object we're concerned with is the bitmap. The parameters for the SelectObject function are hdc--the handle to the device context into which you want the object selected, and hobject--the handle to the actual object. I had a hard time figuring out how to get a handle to a bitmap with Visual Basic, until I came across the LoadBitmap function. This has one argument, the path and filename of the bitmap you want to load. And returns, you guessed it, a handle to that bitmap. All we need to do is use the LoadBitmap function as the hobject parameter when we call the SelectObject function. Does that make sense? Here is how I created a memory dc for the catwalk.bmp and loaded the bitmap into it: Dim OldObj As Long SpriteDC = NewDC(Picture1.hdc, 460, 128) OldObj = SelectObject(SpriteDC, LoadPicture("Catwalk.bmp")) 'Make sure to specify the 'correct path to the bitmap Pretty simple, huh? Once you have the necessary bitmaps loaded into memory, the rest is cake. Just call BitBlt with the handles to the dc's you created and, voila! Animation. I wrote a simple animation program that demonstrates this technique. Just click the download button. Remember, this is written for Visual Basic 4.0 32-bit version. You need to change the API declares to 16-bit if you are programming for Windows 3.x. Next time, we'll try to package this into a sprite engine that can be used with any sprites. Animate02 ZIP File Download Part 5 - Packaging the Animation Techniques into a Sprite Class Hello again, it's been awhile. If you can remember the last installment, we managed to create an area of memory called a memory device context (for breviey, I'll refer to a memory device context as a buffer.) that could store our bitmaps. This time we'll wrap these techniques up into a neat little package called a Class. Once we do this, we can create sprite objects that will make the process of animation a little easier. I had originally intended on including the functions for creating the buffer and manipulating the sprites into one class. After thinking about it, though, I realized I would have to separate these functions into different classes for a few good reasons. First, the bitmaps we use for our sprites might contain frames (or cels) for several different sprites. We would have to create a buffer for every sprite, even if they used the same bitmap. We would also have to create a buffer for each instance of the SAME sprite! This would be wasteful and… well… stupid. Another good reason not to combine the buffer functions with the sprite functions is that we need buffers for background images and staging areas. These images don't need all of the stuff associated with a sprite but they do use the same buffer functions. We would have to make an additional set of buffer functions for backgrounds and staging areas. We will set up a class for buffers called BitmapBuffer and a class for sprites called… er… Sprite. The first thing we need to do is define any properties for our classes. The BitmapBuffer will have two properties: BitmapFile - Used to store the path & filename of the bitmap that will be stored in the buffer. Some of the buffers we will create, such as the staging area, won't (and shouldn't) have bitmaps stored in them initially, so we'll make this property optional. We'll need to make this property READ/WRITE for obvious reasons. Handle - This is the handle to the actual memory device context used for the BitmapBuffer object. We'll need to know this so that we can use it with our BitBlt function. Since the function that creates the memory device context determines the handle, we shouldn't be able to change this property so we'll make it READ-ONLY. I can think of only two methods for the BitmapBuffer class: Create - This method creates the actual buffer and, optionally, loads a specified bitmap into it. If a bitmap is not specified, the width and the height of the buffer should be included. If a bitmap is specified, the width and height of the buffer can be determined from the bitmap. Destroy - In order to free up the resources used by the buffers, we should call the Destroy method after we are done with each buffer. A BitmapBuffer will automatically clean up after itself if the buffer is no longer referenced, but it's a good idea to be able to do this whenever we need to. Sprite objects have a lot more data associated with them. We need to know the size, number and location of the animation cels, where in the bitmap the group of cels for the sprite begin, where we want to paint the sprite, and the status of the sprite. Here are the properties for the Sprite class: Xcoord - The current horizontal position of the sprite. Ycoord - The current vertical position of the sprite. CelHeight - The height, in pixels, of each cel in the sprite animation. CelWidth - The Width, in pixels, of each cel in the sprite animation. CelCount - The number of cels in the sprite animation. CelNum - The current cel number in the sprite animation. CelhDC - The handle to the buffer that stores the bitmap of the animation cels. CelStartX - The horizontal coordinate in the bitmap where the cels for this sprite begin. CelStartY - The vertical coordinated in the bitmap where the cels for this sprite begin. Status - This property can indicate different conditions of the sprite, i.e. whether it's moving, animated, dead, walking, swimming, etc… This property will be set up as bit flags. If we make this property an integer (16 bits), we can have 16 different flags for the sprite. Bits 1 and 2 are going to be reserved for an Animated flag and a Moving flag, respectively and should not be used for any other purpose. The rest of the flags can be set to any arbitrary condition that we decide for our games. If you need more than 16 flags for your sprite, change the Status property to a Long Integer. This will double the number of flags. AnimSpeed, MoveSpeed, CollisionHeight and Collision Width - I'm including these properties in anticipation of dealing with timing and collision detection in future installments. They won't be used now, though. The Sprite class has only one method--Paint--but it's essential for this exercise to work. The Paint method paints the current animation cel onto the specified buffer. It uses the values specified in the other properties to accomplish this: Take only the black portion of the mask of the current cel and copy it to the destination buffer at the sprite's current coordinates. Take the area of the current cel and copy it to the destination buffer at the sprite's current coordinates. If the sprite status indicates 'Animated', then increment the current cel number. If the current cel number is greater than the number of cels, reset the current cel number to the first cel. You can look at the actual code for these classes if you download Animat03.zip. The class modules are BitmapBuffer.cls and Sprite.cls. The code is (I hope) well-commented so you can get an idea of how it all works. To use these classes in your own project, there are a few things you have to keep in mind: * Set up the bitmaps for your sprites so that the cels for each animation are consecutive from left to right and that the masks for each cel are directly under the animation cels. Look at Catwalk.gif for an example. In this version, the area around the animation that you don't want to show must be black. For the masks, the area that you don't want to show must be white and the area that you want to show must be black. This will change in the next installment of this tutorial so don't get too attached to this scheme. The upside of this is that the next version will create masks for your sprites automatically so you don't even have to worry about them. * Add the BitmapBuffer class, Sprite class, and Animation modules to your project. * Declare BitmapBuffer objects for your background, staging area, and any sprites that you'll be using. Here is an example: Dim BG as New BitmapBuffer 'The buffer for your background Dim Stage as New BitmapBuffer 'The buffer for the staging area Dim SpriteBuffer as New BitmapBuffer 'The buffer for your sprite(s) * Declare a Sprite object for each sprite in your game: Dim Sprite1 as New Sprite Dim Sprite2 as New Sprite * Set the BitmapFile property of the background buffer to the bitmap for your background. Do the same for any sprites: BG.BitmapFile = App.Path + "\GameBG.bmp" SpriteBuffer.Bitmap = App.Path + "\MySprite.bmp" * To actually create the buffer and load any bitmaps into it, call the Create method: SpriteBuffer.Create 'Create the buffer and load the bitmap for the sprites BG.Create 'Create the buffer and load the bitmap for the background 'Since the staging area doesn't have a bitmap to get it's 'width and height from, specify them when you create it. Stage.Create StageWidth, StageHeight 'Create the buffer for the staging area * Set some properties for your sprites: With Sprite1 .CelhDC = CatBuffer.Handle 'This is how you connect the sprite buffer to the sprite object. .Xcoord = 0 'The current horizontal coordinate of the sprite .Ycoord = 0 'The current vertical coordinate of the sprite .CelWidth = 64 'The width of each cel in the animation .CelHeight = 64 'The height of each cel in the animation .CelStartX = 0 'CelStartX and CelStartY specify where the cels for this .CelStartY = 0 'sprite are located (upper-left corner) on the sprite bitmap .CelCount = 4 'The number of cels in the animation .CelNum = 0 'The number of the current cel in the animation .Status = 1 + 2 'Status of the sprite. See below for details on Status. End with With Sprite2 ... End With The Status property uses bit fields to determine different states of the sprite. If bit 0 is set (Status And 1), the sprite is animated. Bit 1 is used to determine if the sprite is moving. The other bits can be used for whatever you need for your game, such as dead, walking, running, swimming, etc. To get the state of each flag, use Status And (2 ^ flag) where flag is the number of the bit you want to look for. Up to 16 flags can be set since Status is an Integer. If you need more than sixteen flags, change Status to a Long Integer. * In your animation loop, add code to Blit the background to the stage, Paint the sprite to the stage, then Blit the stage to your screen: BitBlt Stage.Handle, 0, 0, Stage.Width, Stage.Height, BG.Handle, 0, 0, SRCCOPY Sprite1.Paint Stage.Handle BitBlt Picture1.hDC, 0, 0, Stage.Width, Stage.Height, 0, 0, SRCCOPY '…code for incrementing the current sprite cel, changing the sprite coordinates, etc… All of this is illustrated in the Animat03 project. Load it up and play with it. Animat03 also demonstrates some other interesting stuff like a scrolling background, the ability to change the direction of the sprite by changing the increments by which the sprite is moved horizontally and vertically, and setting up a full-screen window like some commercial video games do. The scrolling background was achieved by making a background bitmap that was quite a bit longer than the viewport. I just showed incremental portions of the background consecutively and rolled it over to the beginning when the end of the bitmap was reached. The full-screen function sets the form's background to a specified color (default is black), sets the picture property--if specified, sets the scalemode to Pixels, gets rid of the form's caption, and maximizes the form. That's it for this installment. I know I went through it in kind of a hurry. I'm starting to get really excited about this stuff and I already have some ideas on how to make these animation classes better. If you didn't quite get everything I talked about, try loading the Animat03 project and really looking at the code and the comments. Next time, we'll create a collection for these classes, integrate the buffer into the sprite class, create masks on the fly, and deal with parallax scrolling. See ya. Animate03 ZIP File Download --------------------------------------------------------------------------- The Game Programming MegaSite