Name: Whack A Rat
Author: David Brebner
Date: 9/06/97

Description: A game tutorial using Bit Blit from David Brebner's Unlimited Realities website. You'll also be able to find many other excellent, more advanced animation and graphics tutorials. Well worth the visit!
Controls needed: Form, Command Button, Timer
Level: Beginner-Intermediate


Right, lets do some damage. So you want to actually have a go at making a real simple game?

This strategy I am going to present is best described as one step at a time. Where you think of your game as a whole lot of slightly more finished games. You complete bits of your game so that you can run it and test it, before proceeding to the next step.

I am going to list the whole non-sequential gory process to give you some idea of how to go about it.
 
 

Ideas 


Not very original I know (if you played sam n max you have seen a version of this game) but the idea was to do something simple. I was trying to come up with some ideas that were a bit more fun than space invaders / noughts n crosses / pacman... I thought this could be kinda cool. (keep a note book with you at all times, this was planned in an airport lounge)
 
 

Preliminary Planning 


Before I went any further I sketched out what the game should look like and what it should do.

Being the kinda guy I am, I tend to think about how things will look. However this is important as the look of your game will determine how you go about programming it.

I decided I would have 9 rat holes. Rats stick their heads up to be whacked by your mallet. To make life easy I decided I would ignore perspective, so the rats are all the same size.
 
 

Graphics 


Rather than 3D rendering rats, or hand drawing each frame, I decided to use models. I have a video camera and capture card that make this easy to do. The background art was drawn in pen, then scanned and coloured on the computer.

p.s. these are a special mongolian breed of weird pink rats.

 

If you don't have this sort of gear, then you may need to find someone who does. I used to draw my graphics myself, directly on the PC using a mouse (even an analogue joystick on an Apple IIe, and using rubber keys on my old Spectrum, and even worse using HEX on my older ZX81!!) and trust me, a video camera and scanner are much easier!

When I had scanned in the backround pen drawing I coloured it in using photoimpact, you can choose whatever tool you prefer (paintshop pro is a good shareware tool). I the applied a blur to the background to give the image a feeling of depth.

In addition I made 3 holes of the right size for my rats, then copied and pasted them.

The next step is to create the sprites. (Check my tutorial on sprites)

Because this game is a simple BitBlt game, rather than using DirectDraw we need to mask the sprites ourselves. For simplicity I have created a second bitmap which contains the masks you will need.

 

Basically I have a hammer in 3 positions, upright, down, and hitting a rat. The rat pokes his head up, looks around to give the player a chance to whack him, and then him popping up and cheering (points for the rats). In addition, the all important squashed rat frames.

Sorry, but I have avoided the issue here slightly and processed this sprite sheet though my sprite editor (which is not yet ready for public consumption). Suffice to say, that it makes it easier to establish the position of each of your sprites within the sprite sheet, and creates a simple data file.

You can do this yourself, by writing down the coordinates and dimensions of the sprites on your sprite sheet, or placing each sprite in its own bitmap.
 
 

Version 1 (bobbing rats) 


Right its time to pull out your trusty VB and start coding. I like to start with some tests to make sure my graphics look ok, and that things are going to work ok.

This also gives you a chance to test out some strategies for implementing your game.

Get those declares.

I usually use a standard BAS file I made up called blit.bas. This has all the normal stuff I use all the time like bitblt, strechblt, the constants and soundplaysnd.

Here's what we need for this project, declared at form level (in your general section),
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 Declare Function sndPlaySound Lib "winmm.dll" Alias "sndPlaySoundA" (ByVal lpszSoundName As String, ByVal uFlags As Long) As Long 

Const SRCCOPY = &HCC0020 ' (DWORD) dest = source Const SRCINVERT = &H660046 ' (DWORD) dest = source XOR dest Const SRCAND = &H8800C6 ' (DWORD) dest = source AND dest 

Right first things first.. aer second.

Time to stick some pictures and a timer on our form. We need 4 pictures,

Each of these pictures should be invisible, have autoredraw=true and scalemode=pixel. With the exception of PicBuf set autosize to true and Load up the matching files off the disk (whack_bak.gif, whack_img.gif, whack_msk.gif).

You should also make sure that the PicBuf is as big as PicBak, as it has to buffer this image.

Next add a timer, enabled=true and interval (the speed of our game clock) equal to... say 30 (ms).

You also need to do some things to the form. Make scalemode=pixel, and create a blank icon - set the mouseicon property to your blank icon, and put mousecursor=99 (custom). The overall effect is to hide the mouse cursor when you move over the form at run-time. We will replace the cursor with our hammer graphics we have designed.

Lets start coding!

Lets see some graphics!! Add this line to your form_paint event.
u& = BitBlt(hDC, 0, 0, Picbuf.ScaleWidth, Picbuf.ScaleHeight, Picbuf.hDC, 0, 0, SRCCOPY) 
When you run your project you should get a cool wac-a-rat background picture in the top left corner of your form.

Now for a hammer!

Right, time for some of that dreaded masking and double buffering business.

It all goes in the timer event. You can think of this as the heartbeat of the game.

First we need some form level variables (in your general section) to remember the mouse position, and some contants to define the different states of the hand. (remember the hammer can be up, down and hit)
Dim handx%, handy% 'current hand position 

Dim handpos% 'how far through a swing
Const hand_up = 0
Const hand_down = 1
Const hand_hit = 2

Now we have these declarations to keep track of the mouse, lets put in the code which actually does the drawing of the hammer.
Private Sub Form_MouseMove(Button As Integer, Shift As Integer, x As Single, y As Single)
handx% = x - 15
handy% = y - 40
'this offset is so the head of the hammer is at the same point as the mouse cursor
End Sub 

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
If handpos% = hand_up Then handpos% = hand_down
End Sub 

Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
handpos% = hand_up
End Sub 

Private Sub Timer1_Timer()
'copy from background
u& = BitBlt(Picbuf.hDC, 0, 0, Picbuf.ScaleWidth, Picbuf.ScaleHeight, Picbak.hDC, 0, 0, SRCCOPY)

'place the sprite of the hand
drawhand

'copy to screen
u& = BitBlt(hDC, 0, 0, Picbuf.ScaleWidth, Picbuf.ScaleHeight, Picbuf.hDC, 0, 0, SRCCOPY)
End Sub 

Sub drawhand()
Select Case handpos%
--Case hand_up
----u& = BitBlt(Picbuf.hDC, handx%, handy%, 97, 54, Picmsk.hDC, 0, 0, SRCAND)
----u& = BitBlt(Picbuf.hDC, handx%, handy%, 97, 54, Picimg.hDC, 0, 0, SRCINVERT)
--Case hand_hit
----u& = BitBlt(Picbuf.hDC, handx%, handy%, 90, 64, Picmsk.hDC, 0, 118, SRCAND)
----u& = BitBlt(Picbuf.hDC, handx%, handy%, 90, 64, Picimg.hDC, 0, 118, SRCINVERT)
--Case hand_down
----u& = BitBlt(Picbuf.hDC, handx%, handy% - 6, 90, 64, Picmsk.hDC, 0, 54, SRCAND)
----u& = BitBlt(Picbuf.hDC, handx%, handy% - 6, 90, 64, Picimg.hDC, 0, 54, SRCINVERT)
End Select
End Sub 

Initially the mouse-move event simply tracks the current cursor position (with an offset so the hammer head matches the pointer position). In addition the button status is kept track of so that when a button is pressed the hammer state changes to down. (we're not ready to work out if it hit yet)

The next subroutine, the timer, is the guts of it all. We initally copy the background into the buffer to start off afresh. We then do all our drawing, which currently consists of a mallet. After this we copy the whole buffer to screen so the animation is relatively smooth.

The drawing of the hand is handled in a slightly kludgy way. As I said earlier in the graphics section I have a tool to create coordinates for sprites, however just for you I have done it an old fashioned way, and hard coded the hand positions.

This involved using photoimpact, and selecting the appropriate hand, and writing down the x,y, width and height. I then simply type these values into the bitblt for each case. (see my tutorial on bitblting and sprites)

When you run the project now, you should see a hammer instead of a mouse cursor! p.s. if you still see an arrow as well, then check my instructions on hiding the mouse cursor earlier on.

I want some RATS!

Lets get some rats onscreen for our satisfaction. Once again lets start with some variable to hold our rats positions. I am going to use a 2 dimensional array (whoo, hoo) because even though its not necessary, I figure the rats are layed out in rows and columns.
Dim ratx%(2, 2), raty%(2, 2), ratpos%(2, 2) 

'hold the sprites
Dim spnx%(15), spny%(15), spnw%(15), spnh%(15), spnox%(15), spnoy%(15) 

In addition to the two dimensional arrays to hold the rats x and y coords along with it's current position out of the whole - I am also setting up variable to hold the sprite information I have saved from my sprite editor. This will tell me exactly where in the sprite bitmap I should get them from.

Heres a chunk of code to add to your timer event. You should add it after you clear the buffer and before you draw the hand (the hand should always be on-top of the rats).
'draw some rats For col% = 0 To 2
--For row% = 0 To 2
----drawarat ratx%(row%, col%), raty%(row%, col%), ratpos%(row%, col%)
----ratpos%(row%, col%) = ratpos%(row%, col%) - 1
----If ratpos%(row%, col%) < 1 Then ratpos%(row%, col%) = 12
--Next
Next
I will give you the code for the drawarat subroutine soon, but the other two lines simply cycle the rat at the current row and column backwards through positions 0 to 12. (so the rats will all bob up and down)
Sub drawarat(X%, Y%, cell%)
If cell% > 0 Then
--u& = BitBlt(Picbuf.hDC, X% + spnox%(cell%), Y% + spnoy%(cell%), spnw%(cell%), spnh%(cell%), Picmsk.hDC, spnx%(cell%), spny%(cell%), SRCAND)
--u& = BitBlt(Picbuf.hDC, X% + spnox%(cell%), Y% + spnoy%(cell%), spnw%(cell%), spnh%(cell%), Picimg.hDC, spnx%(cell%), spny%(cell%), SRCINVERT)
End If
End Sub 
Its that easy! Now we have to actually load the sprite file and configure the rat positions (x and y values). To place the rats, I simply used trial and error, e.g. I changed the x and y settings for each rat until it was nicely above its hole.
Private Sub Form_Load()
'load up the sprites
Open App.Path & "\whack_img.spr" For Random As #1 Len = 2
For a% = 0 To 14
--Get #1, a% * 6 + 1, spnox%(a% + 1)
--Get #1, a% * 6 + 2, spnoy%(a% + 1)
--Get #1, a% * 6 + 3, spnx%(a% + 1)
--Get #1, a% * 6 + 4, spny%(a% + 1)
--Get #1, a% * 6 + 5, spnw%(a% + 1)
--Get #1, a% * 6 + 6, spnh%(a% + 1)
Next
Close #1

'position the rats over their holes
stickrat 0, 0, 187, 48, 0
stickrat 1, 0, 127, 79, 0
stickrat 2, 0, 75, 116, 0
stickrat 0, 1, 247, 58, 0
stickrat 1, 1, 189, 90, 0
stickrat 2, 1, 146, 126, 0
stickrat 0, 2, 304, 66, 0
stickrat 1, 2, 262, 98, 0
stickrat 2, 2, 222, 134, 0
End Sub 

Sub stickrat(row%, col%, X%, Y%, pos%)
ratx%(row%, col%) = X%
raty%(row%, col%) = Y%
ratpos%(row%, col%) = pos%
End Sub 

The stickrat subroutine is just to tidily initialise the arrays for the rats, and so that I could just try new setting in the debug window until the rat was lined up right.

Fire it up and you should have 9 shifty rats poking their heads up while you wander around with a mallet banging to no effect. Time to proceed to version 2!
 
 

Version 2 (a game?) 


Well its time we got ourselves a game. What do we want in our game, and what are the priorities?

Lets get it working in that order.

Rats popping up

Here are some new variables we will need to add to the declare section.
Dim ratspeed%(2, 2)

' game status
Dim difficulty% 'start at 0, (easy), 8=hard

' current frame (will just increment)
' (hopefully won't play till it overflows ;)
Dim frame As Long

Ratspeed, holds the speed with which rats pop up their ugly heads (sorry to all those rat lovers out there). The game difficulty variable can be changed between 0 to 8, making the game progresively harder and faster. The frame hold the current game frame. It just keep counting upwards each tick of the timer. This may also be used to show the time elapsed..

We need to add the randomize statement to the form_load so that the random numbers we will be generating are different each time we run the game.
(into form_load)
Randomize 
Now change the timer1_timer event to look like this.
Private Sub Timer1_Timer()
'copy from background
u& = BitBlt(Picbuf.hDC, 0, 0, Picbuf.ScaleWidth, Picbuf.ScaleHeight, Picbak.hDC, 0, 0, SRCCOPY)

frame = frame + 1
popuprats

'draw some rats
For col% = 0 To 2
--For row% = 0 To 2
----drawarat ratx%(row%, col%), raty%(row%, col%), ratpos%(row%, col%)
----updateRat row%, col%
--Next
Next
'place the sprite of the hand
drawhand

'copy to screen
u& = BitBlt(hDC, 0, 0, Picbuf.ScaleWidth, Picbuf.ScaleHeight, Picbuf.hDC, 0, 0, SRCCOPY)
End Sub 

We have just added the frame incrementing as time passes by, followed by a call, popuprats, that will check a random number generator (biased by the difficulty) to decide if to add a rat to pop up.

The other new subroutine, updateRat, takes the place of that bobbing code we have in version 1. This will check to see if there is currently a rat, and if there is, then to bob at the speed defined when popuprat was called.

See what I mean, heres the code for those two subroutines;
Sub updateRat(row%, col%)
If ratpos%(row%, col%) <= 12 And ratpos%(row%, col%) > 0 Then
--'if the rat has started popping up!
--'check if time has elapsed sufficent for the speed
--If frame Mod ratspeed%(row%, col%) = 0 Then
----'e.g. the bigger the speed, the less frequently it will
----'be an exact division (e.g. the slower the rat pops up)
----'change the frame
----ratpos%(row%, col%) = ratpos%(row%, col%) - 1
--End If
End If
End Sub 

Sub popuprats()
'randomly created rats popping up at random speeds
'the harder the difficulty (higher the value)
'the more likely and the faster the rats.
If Int(Rnd(1) * (30 - difficulty%)) = 1 Then
--rndrow% = Int(Rnd(1) * 2.99)
--rndcol% = Int(Rnd(1) * 2.99)
--If ratpos%(rndrow%, rndcol%) = 0 Then
----ratpos%(rndrow%, rndcol%) = 12
----ratspeed%(rndrow%, rndcol%) = Int(Rnd(1) * (8 - difficulty%)) + 1
--End If
End If
End Sub 

The figures in the popuprats sub are not hard and fast, just some stuff I arbitrarily decided, you may well want to change it to make the game harder, or easier. One thing to note is the use of RND. You may well use this in other projects, Rnd(1) returns a 'random' number from 0 to 1. I multiply by 2.99 because if I used 3, it may, very very infrequently return Rnd(1)=1, hence int(1*3) is a number too big for my array. Because Int always truncates however, int(1*2.99) = 2!

Run wac-a-rat and you should get some randomly bobbing rats, and still a very impotent hammer.

Lets get those smug, self satisfied rodents.

How do we go about whacking the rats on the head? Well I figured two options we could use initially. 1) check in the mousedown if we have clicked inside the mouse sprite. 2) check in the mousedown if we are a certain radius away from the centre of the hole.

Well as cool as #2 sounds, its probably not so effective. We could achieve like this, xdiff = abs(holex - handx), ydiff = abs(holey - handy). The distance is now srqt(xdiff^2 + ydiff^2).

Technique #1 is better as its quick, and the bigger the sprite, the easier to hit (i.e. slightly more realistic)

Lets have a go;
Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
If handpos% = hand_up Then handpos% = hand_down
For col% = 0 To 2
--For row% = 0 To 2
----sp% = ratpos%(row%, col%)
----If sp% > 0 And sp% <= 12 Then
------'this rat is trying to popup!
------If X > ratx%(row%, col%) + spnox%(sp%) And X < ratx%(row%, col%) + spnox%(sp%) + spnw%(sp%) Then
--------'inside the left and right of the sprite
--------If Y > raty%(row%, col%) + spnoy%(sp%) And Y < raty%(row%, col%) + spnoy%(sp%) + spnh%(sp%) Then
----------'inside the top & bottom too!
----------u& = sndPlaySound(App.Path & "\whack_pop.wav", 1)
----------ratpos%(row%, col%) = 13
----------ratspeed%(row%, col%) = 10 '10 frames to fade away
--------End If
------End If
----End If
--Next
Next
End Sub 
Here's the plan - we go though each rat on the board checking to see if it is currently in the process of popping up (thats 1 to 12). If it is then we check to see if we have clicked inside it. We check if the x falls within the left and right, then if the y within the top and bottom. IF we find a match - then HIT IT. Play a sound whack_pop, set the rat postion to 13 (unlucky for some) to the first squash position. Set the speed to 10, so we can see the animation.

Now that we can have rats in a new position, 13, we need to handle this in our updateRat routine.
Sub updateRat(row%, col%)
--If ratpos%(row%, col%) <= 12 And ratpos%(row%, col%) > 0 Then
----'if the rat has started popping up!
----'check if time has elapsed sufficent for the speed
----If frame Mod ratspeed%(row%, col%) = 0 Then
------'e.g. the bigger the speed, the less frequently it will
------'be an exact division (e.g. the slower the rat pops up)
------'change the frame
------ratpos%(row%, col%) = ratpos%(row%, col%) - 1
----End If
--ElseIf ratpos%(row%, col%) > 12 Then
----If frame Mod ratspeed%(row%, col%) = 0 Then
------ratpos%(row%, col%) = ratpos%(row%, col%) + 1
------If ratpos%(row%, col%) >= 15 Then
--------'finished animation, get rid of it
--------ratpos%(row%, col%) = 0
------End If
----End If
--End If
End Sub 
The new bottom half essentially means that for rats above 12, increment their position until you reach 15. At this point we have finished demoralising the rat, and can dispose of it by setting the position back to 0, ready for a new tennant.

Finishing it off

Well its getting late, so lets tidy it up - remember it is only an example.

We need a score, the rat position * 3 points for a hit, and -20 points per miss. Remember the rats position gets closer to 0 as it comes up out of the hole, e.g. the later you hit it, the less points you get.

We need a new game button which lets you choose a level.

I figure 1000 frames is enough rat whacking for anyone, so thats the time you have to whack as many as you can.
(in general)
'score
Dim score% '+/- 32000 should cover it

(in form load)
u& = BitBlt(Picbuf.hDC, 0, 0, Picbuf.ScaleWidth, Picbuf.ScaleHeight, Picbak.hDC, 0, 0, SRCCOPY)
Timer1.Enabled = False
MousePointer = 0 

Add a variable to hold the players score for this game, and three new lines in form_load. The first bitblt is to put the backgound into the buffer for the first paint event.

The next two lines is so that until the game starts the timer is off. Because the timer is off we need to turn the default mousepointer on so we can see what we are doing (there is no hammer).

Now stick a button labeled New Game in the top left hand corner of the game window.
Private Sub Command1_Click()
frame = 0
score% = 0
Level% = Val(InputBox("Choose a difficulty from 0 to 8" & Chr$(13) & "8 being the hardest."))
If Level% > 8 Then difficulty% = 8 Else difficulty% = Level%
Timer1.Enabled = True
MousePointer = 99
End Sub 
This button resets the frame counter and score to 0, chooses a level of difficulty, enables the game timer and makes the mouse invisible!
(Add to the Form_Mousedown next to the sndPlaysound)
score% = score% + ratpos%(row%, col%) * 3 

(Add just after you decrement the rat positon in updateRat sub) If ratpos%(row%, col%) = 0 Then
--'the rat got away without being hit
--score% = score% - 20
End If

This takes care of the scoring. The mousedown event tracks the mousehits, multiplying * 3 the position to give you a bonus the earlier you can hit the mouse. The updateRat traps the rats that got away, those that popup completely.
(this routine goes into Timer1_timer just after popuprats)
'update score and time
Picbuf.CurrentX = 90
Picbuf.CurrentY = 10
Picbuf.Print "Score : " & score%
Picbuf.CurrentX = 90
Picbuf.Print "Time : " & (1000 - frame)
If frame = 1000 Then
--'thats the end of this game
--Timer1.Enabled = False
--MousePointer = 0
End If
Well this is cool, it simply prints the current frame (as a countdown) and the current score onto the buffer. This gets copied to screen along with the rest of the buffer at the end of the timer_event.

The last bit is to check if time has expired. In which case the timer is disabled and the mousepointer turned on again.

Congratulations!

Download the source Code

Don't worry if you got out of step, you can view the complete source code - or download the VB project along with wave file images and the icon.

Play the game

Happy Programing
David Brebner