Drawing & Animation I

Visual Basic 专栏收录该内容
17 篇文章 0 订阅

Drawing & Animation

Using the Win32 GDI #1

This three part tutorial first appeared some years ago on the old VBExplorer.com . Since then several errors and bugs have been discovered by various users on the VBExplorer.com Forums. This version reflects the changes made to overcome the bugs and erros. The text will note where a bug correction and / or update has been done, and of course also why. 
A big 'Thank you' goes out to all the people reading and reporting the bugs in the previous version of this tutorial. Please provide comments, questions, bug reports etc. at the VBExplorer.com forums in the Graphics & Game Programming section.

Graphics

One of the most important aspects of any game are the graphics. Graphics provide a visual interface for game play and more importantly, the means to explore exciting and fantastical virtual worlds. This means that cool graphics and smooth animation are key issues you will need to consider if you hope to create the right atmosphere and environment for enjoyable game play. While certainly not the only issues, they are two of the main factors that will affect the popularity and possible commercial success of your game.

Download Sample Programs Here.

Graphics programming in the world of Windows and GDI is largely based on Bitmaps. A bitmap is a structure, generally composed of a multitude of small dots, known as pixels, each being of a specific color. A bitmap file, with the extension BMP, is the file format that will be used throughout this book to store graphical elements. We will go a bit more into what a bitmap actually is and discuss various methods to manipulate them from both a programming and a design perspective.

The GDI system is part of the Win32 API. The GDI system contains the functions that draw and manipulate the visual interface of Windows. The GDI system is the system we, as game programmers, are most interested in.

There are many ways to display a bitmap in Visual Basic. Let's look a one of the simplest which is the Picture property.

A standard Form has a Picture property, which can be used to display a bitmap. The other two controls most often used for presenting graphics in Visual Basic, are the Image Control and the Picture Box. Of these two, the Picture Box is the control you are likely to use most often as a game programmer.

The main reason for using the PictureBox control, instead of the image control, is that it has a hDC property. This hDC property is used when we draw bitmaps to and from our drawing areas using an API function, which we will discuss next.

An hDC is also known as a Device Context, or more correctly it is the handle to a device context. A Device Context is a Win32 object, which we create in memory, which will be used to draw graphics to and from. A DC also many other associated attributes, besides the graphics attribute. Among these attributes are the Font, Brush, Pen, Drawing Mode which we'll discuss a little later. 

 

Drawing a Bitmap

Most games with graphics must be able to rapidly draw several graphics repeatedly from a source context onto the destination, which would be the actual gaming area. The need for this is quite obvious, since few games are compromised of a single graphic file with no animation. Certainly none that would interest a famous future game developer like you. So in order to make our games more interesting we need a way to draw a bitmap (or parts of it) from one place (the source), to another place (the destination).

The Win32 API is a collection of functions, contained in various DLL's, which form the backbone of windows. They include all the functions the operating system uses to handle memory, disk operations and anything else it may be called to accomplish. Even though Visual Basic controls encapsulate a lot of this functionality, thus hiding a lot of the complexity, there is usually a performance penalty to be paid for this lack of complexity. There are also some things that you just can't do without accessing the API directly.
The good news is that these functions are available to you. Though some API calls are more difficult to use and may contain some scary syntax, judicious use of API's can enhance the functionality and features you can add to your programs and are sometimes the only way to do what needs to be done.

The Win32 GDI system has a very special function for just the sort of thing that we need to do, and that is the BitBlt function. We will use this function throughout this book, so pay special attention to it. The function is declared as follows:

Declare Function BitBlt Lib "gdi32" Alias "BitBlt" _

(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

 

 

This function draws the area defined from one hDC (Device Context) to another Device Context. Anything graphical in the source device context is drawn on the destination device context. Already here you can observe the use of the hDC property of a picturebox, since this means that we can draw the contents of one picturebox to another picturebox, simply by specifying the device context (hDC).

Let's take a look at the parameters in the BitBlt function:

  
hDestDC:The destination Device Context.
XThe upper X-coordinate on the destination area.
YThe upper Y-coordinate on the destination area.
nWidhtThe Width of the drawing area in pixels.
nHeightThe Height of the drawing area in pixels.
hSrcDCThe Source Device Context.
xSrcThe upper X-coordinate of the source area.
ySrc The upper Y-coordinate of the source area.
dwRop The Raster operation constant.

 

At this point you can really impress your family and friends by using words like hDC, dwRop and BitBlt in casual conversation and are likely to be the life of the party but if your goal is to actually write a game we'll need to go on. Let's try a simple project to see how this all works.

BitBlt Test

This sample project demonstrates the use of the BitBlt function to draw a bitmap from a picturebox to a form. This sample project is found in BITBLTTEST.ZIP .

In the project we have a Form (frmBitBlt), a Picturebox with a small picture (picBitBlt) and a command button (cmdBitBlt).

NOTE: Since the BitBlt function expects the parameters to be in pixels, the ScaleMode property of the picturebox and the form has been changed from 1-Twips to 3-Pixels.

The code for this small program is very simple:

Option Explicit



Declare Function BitBlt Lib "gdi32" Alias "BitBlt" _

(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

 


Private Sub cmdBitBlt_Click()

Me.Cls
BitBlt Me.hDC, 0, 0, picBitBlt.ScaleWidth, _
       picBitBlt.ScaleHeight, picBitBlt.hDC, 0, 0, vbSrcCopy

End Sub

 

When the button is clicked, the BitBlt function is called to draw the Picture of the picBitBlt picture box onto the form. We specify the hDestDC parameter of the BitBlt function to be that of the current form (Me.hDC). Then we specify the upper left X and Y coordinates of the destination to be 0. The Width and Height of the picture on the Destination is defined to be the ScaleWidth and ScaleHeight of the picture box. This ensures that we will not be copying the border of the picture box, but only the elements inside the picture box. The hSrcDC is set to the source hDC, which is of course the picBitBlt picturebox. We specify that the drawing should start at 0,0. The width and height of the source will then be the same as the width and height of the destination area.

Press the command button and observe how the picture is drawn onto the form.

 

The observant reader might want to protest a little bit now about the use of an API function, instead of the native VB PaintPicture method. After all, PaintPicture uses BitBlt to do its work so if they both do the same thing, why use the API? The reason for this is simple; the PaintPicture method is extremely slow, compared to the speed that can be achieved by using the BitBlt function directly. A quick test using each to copy the same picture shows that PaintPicture takes almost 10 times as long to copy a picture! In a game, where speed in serving up your graphics is a big issue, I'm sure you will agree this would yield unacceptable performance.

 

Sprites, Raster and Masks

The Sprite

Let us take a step away from the actual coding and have a good look at a very central aspect of almost any graphical game, The Sprite. The Sprite or rather the sprites are the small things that move around the gaming field, either controlled by a human or by the computer. As you have probably already guessed, you use the BitBlt function to draw sprites onto the gaming field from a specified source. Since we are blitting a rectangular area this would all be very nice and neat, if all sprites were rectangular but they are not. In fact they are usually anything but rectangular, running the gamut from simple blobs to elaborate spaceships. So how can we make the rectangular area we just blitted any other shape we want? The simple answer is, we can not, but we can make it appear as though they are.

Raster Operations

The technique we will use to make the sprites appear to have the shape we want involves the use of Raster operations. In the previous sample, we simply drew what was in the picturebox onto the screen. This means that we set the Destination Pixel = Source Pixel. In other words, when we blit the picture we are specifying that each of the destination pixels be exactly like its corresponding source pixel. We specify the Raster Operation we want to apply using the last parameter of the BitBlt function: dwRop.

Before showing you how this can be used to make sprites of any shape, or more accurately, to make unwanted areas of the bitmap disappear (also known as transparency), lets take a closer look at Raster Operations.

When we are drawing an image from one Device Context to another, BitBlt is actually copying the value of each pixel in the source picture, and performs a specified Raster Operation on the destination with this copied value. To simplify, if we could hear BitBlt thinking, we might hear something like this "Ok, so now I have this black pixel from the source and I am going to copy it to the destination which is gray. Do you want me to mix the two colors, or place one on top of the other, or do something else to them?" The way we tell BitBlt how to handle the operation is by using the dwRop parameter we mentioned earlier.

So the Raster operation involves both the current value of the destination and the copied value from the source. The values used in this context, are the color values of the pixels. Each pixel in an image or device context is made of a number of bits, and the raster operations combine these bits to form a new pixel on the destination. The basis of the raster operations are the logical Boolean operators, NOT, AND, OR & XOR.

 

Visual Basic provides several constants for raster operations, here are a few of the most frequently used ones;


vbBlacknessSets destination = 0 (Black)
vbDstInvert Inverts the destination rectangle (NOT Destination)
vbMergeCopy Combines the source with the destination using AND
vbMergePaint Combines the source with the destination using OR
vbNotSrcCopy Inverts the source and then copies it to destination
vbNotSrcErase Inverts the result of combining the source and destination using OR
vbSrcAnd Combines the source and the destination with the AND operator
vbSrcCopy Copies the source directly to the destination
vbSrcErase Combines the inverted destination with the source using AND
vbSrcInvert Combines source and destination with XOR
vbSrcPaint Combines the source and the destination with OR
vbWhiteness

Destination is set to white


The raster operation is done on the entire destination rectangle.

 

Masks

Now, just using these raster operation constants will not make a sprite transparent, but if we combined the original sprite bitmap with a mask bitmap, then we could reach transparency through these raster operations.

A Mask is a special bitmap consisting of only two colors, black and white. The areas on a mask, which are white, will be the transparent areas, and the black areas will be the areas we want to see.

Have a look at the following two bitmaps of a sprite and a mask of the sprite:

 

 

The left one is the mask of the first sprite, and the right one is the sprite. Basically we first Blit the mask onto the surface, and then blit the sprite at the same position. The result should be just a blue circle. Let's see how this is accomplished.

What follows is a bit involved, and some will find it difficult. You can still use BitBlt even if you don't understand all the specifics of Boolean math -just like you can drive a car without understanding how everything in a car's engine works. So if you don't follow all of this don't panic. At some point you will find that knowing how and why a thing works will allow you to supercharge your programming so determine to come back to this as your skills increase.

When the Mask is first blitted onto the game field, we specify the vbSrcAnd operator for BitBlt's dwRop parameter. This operator tells the BitBlt function to make the destination equal to the source color (pixel) combined with the destination pixel color using the AND operator.

Imagine that the destination has a background color defined as such: 1100 1001 (201 in decimal). By using the BitBlt function with vbSrcAnd operator the result on the destination would be.

For the white (white is 1111 1111) parts of the mask the resulting destination color would be:

 

Source:1111 1111AND
Destination:1100 1001
Result:

1100 1001

Which is the same as the original destination color (the background).

For the black (Black is 0000 0000) areas it would be:

Source:0000 0000 AND
Destination:1100 1001
Result:

0000 0000

This would make all the black areas of the mask still be black and the white areas would be the same color as the destination background. So if we blitted the mask shown above, the destination would now have a perfect round black circle on it. It's almost as if we cut out a section of the bitmap to place our sprite in.

The next thing to do is to Blit the actual colored sprite onto the destination at the same coordinates as the mask. For this Blit we use the vbSrcPaint raster operation, which will make the destination equal to the source combined with the destination in an OR operation.

For the black areas of the sprite this would give a background color of:

Source:0000 0000 OR
Destination:1100 1001
Result:

1100 1001

The same color as the original destination. So now the black areas of the sprite become the same color as the destination pixels they are combined with.

For the Blue areas (we define blue as 0011 1111, which might not be the truth on your system) the resulting colors would be:

Source:0011 1111OR
Destination:0000 0000
Result:

0011 1111

Which is the color blue.

So to sum up, in order to draw a transparent sprite:

  • Make all the areas of the sprite that should be transparent black
  • Create the mask, making all the areas that should be transparent on the original sprite white. Make all the areas that should be blitted unchanged to the destination black.
  • BitBlt the Mask onto the destination with the vbSrcAnd raster constant.
  • BitBlt the original sprite with the vbSrcPaint raster operator.
     

Let's take a look at the TRANSBLT sample project for this section which demonstrates the drawing of a transparent sprite. This sample is contained in the TRANSBLT.ZIP file.

The sample project has two picture boxes, picSprite and picMask. They contain the sprite and the mask. The images in these picture boxes will be blitted onto the form, and create the transparent sprite. The only enabled button at the start of the project is the cmdDrawMask button. This button will obviously draw the mask onto the form, with the vbSrcAnd raster operator. The code in the button looks like this:


Private Sub cmdDrawMask_Click()
'Draws the mask with vbSrcAnd raster operation
BitBlt Me.hDC, 0, 0, picMask.ScaleWidth, picMask.ScaleHeight, picMask.hDC, 0, 0, vbSrcAnd
End Sub


The round black circle of the mask should now appear on your Form in top left corner.

The next thing to do is to draw the actual sprite (the blue circle) onto the form, at the exact same location as the mask. This is done is the cmdDrawSprite command button:


Private Sub cmdDrawSprite_Click()
'Draws the sprite witht the vbSrcPaint raster operation
BitBlt Me.hDC, 0, 0, picSprite.ScaleWidth, picSprite.ScaleHeight, picSprite.hDC, 0, _
  0, vbSrcPaint
End Sub


Now all you should see is the blue circle, which is our transparent sprite.

Simple movement

This is all very nice for non-moving sprites, but what about moving sprites? Well, moving a sprite is not very complex, as you have probably already guessed it is a simple matter of varying the X and Y coordinates of the destination rectangle.

 

 

To demonstrate this, take a look at the MOVING1 sample project found in MOVING1.ZIP. This simple program uses a timer to vary the X and Y coordinates of the sprite, and thus makes it move.

The actual drawing process is the same as in the foregoing section:

  •  First the mask
  •  Then the actual sprite

To start the movement, click the cmdStart command button. This enables the timer, and the sprite will start moving.

The code to move the sprite is in the Timer event of the Timer control:


Private Sub TimerMove_Timer()
Static X As Long
Static Y As Long

X = X + 1
Y = Y + 1

'Keep the ball of the edge
If X > Me.ScaleWidth Then
    X = 0
End If

If Y > Me.ScaleHeight Then
    Y = 0
End If

BitBlt Me.hDC, X, Y, SpriteWidth, SpriteHeight, picMask.hDC, 0, 0, vbSrcAnd
BitBlt Me.hDC, X, Y, SpriteWidth, SpriteHeight, picSprite.hDC, 0, 0, vbSrcPaint

End Sub


We have two static variables, X and Y, which will be used as our drawing point on the form. These are increased by 1 each time the event is fired. After incrementing the variables, we check to see if they have gotten past the edge of the form. If they are out of bounds they are reset to 0. Each time this is done, we will draw the sprite using the BlitBlt function.

Try to run the program, it does not look pretty. All you see is a long blue line being drawn on the form. The reason for this behavior is obvious if we think about it. We draw the sprite repeatedly but we do not erase the previous drawn sprite, so it will still be there when the timer event is fired. To deal with this we can insert a Me.Cls call, which will clear the form. Modify the code so it looks like this:


Private Sub TimerMove_Timer()
Static X As Long
Static Y As Long

X = X + 1
Y = Y + 1

'Keep the ball of the edge
If X > Me.ScaleWidth Then
    X = 0
End If

If Y > Me.ScaleHeight Then
    Y = 0
End If


 
Me.Cls
 
BitBlt Me.hDC, X, Y, SpriteWidth, SpriteHeight, picMask.hDC, 0, 0, vbSrcAnd
BitBlt Me.hDC, X, Y, SpriteWidth, SpriteHeight, picSprite.hDC, 0, 0, vbSrcPaint

End Sub

 


Now the Sprite moves as a single ball over the form. The animation isn't too pretty since it flickers and 憌obbles?as it moves along. Depending on the power of your computer, you might also see the black mask ball a few times. Why does it do this? The answer is that we do not synchronize our drawing operation with the redrawing of the form after each Cls operation. So when we draw our sprite we might just be unlucky enough to have drawn it, after the form was finished with redrawing itself. This will result in a form momentarily without our sprite. Since these drawing operations are so fast you will not actually see the sprite missing, but instead just a quick flicker. It's as if you had a movie and cut out several frames at random, the action would seem jerky whenever you got to the missing frame.

So what we need is a way to update the form just after we have drawn the sprite, and not during the actual drawing process. This can be achieved with the Refresh method of the form. This method Refreshes (Updates) the form when it is called, which is exactly what we need. But this is not quite enough, since the refresh method will draw the display of the form as it appears in memory. Since we have not stored the sprite in the display memory of the form, it will not be drawn automatically when the Refresh method is called. To store the current display of the form in memory, which would include our sprite, we have to set the Autoredraw property of the form to True. This will store the visual appearance of the form, just the way we want it. The code will then look like this:


Private Sub TimerMove_Timer()
Static X As Long
Static Y As Long

X = X + 1
Y = Y + 1

'Keep the ball of the edge
If X > Me.ScaleWidth Then
    X = 0
End If

If Y > Me.ScaleHeight Then
    Y = 0
End If


 
Me.Cls
 
BitBlt Me.hDC, X, Y, SpriteWidth, SpriteHeight, picMask.hDC, 0, 0, vbSrcAnd
BitBlt Me.hDC, X, Y, SpriteWidth, SpriteHeight, picSprite.hDC, 0, 0, vbSrcPaint
 
Me.Refresh

End Sub

 

If you try it now, the sprite should move slowly down the form, in a very stately manner.

End of Part I of the three part Drawing and Animation Tutorial by Burt Abreu & S鴕en Skov.

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值