|
Drawing & Animation
Using the Win32 GDI #1
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
|

Figure1.1:
Sample Picture
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. |
| X: |
The upper X-coordinate on the destination area. |
| Y: |
The upper Y-coordinate on the destination area. |
| nWidht: |
The Width of the drawing area in pixels. |
| nHeight: |
The Height of the drawing area in pixels. |
| hSrcDC: |
The Source Device Context. |
| xSrc: |
The 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
Private 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
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;
vbBlackness Sets 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 1111 AND
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:
11001001
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 1111 OR
Destination:
0000 0000 (The black of the mask)
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, 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, 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 wobbles 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, 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.
[Drawing & Animation #1] [Drawing & Animation #2] [Drawing & Animation #3] [Download All Samples]
These tutorials were originally developed by
Soren Christensen and Burt Abreu as part of a book which we were working on.
The book idea didn't come to fruition and so we decided to post the completed
chapters here in the hopes that you would find them useful. We retain copyright
to this material and you may not reproduce it, post it, or otherwise disseminate
it in any fashion without our express written consent with the exception of making
copies for your personal use.
|