|
Controlling Your Game
Using the Joystick, Mouse and Keyboard
User Input
So far we have learned how to
move, animate and do some other stuff to sprites. We have also
looked into the area of sound playback in games. This is all very
nice if you want to create a game with no human interaction.
Since this is most likely not the kind of game you want to make,
then you need to allow some sort of user input. The three Input
Devices, which we will consider are the Keyboard, the
Mouse and the Joystick, we will also look at ways to
use the user input in special circumstances. Download All Samples
Note: Be sure to check out the other tutorials in this series on the Tutorials page and
the many other resources on the VB Game Programming Resources page.
The Keyboard
The keyboard is the primary
input device in most applications and games. Even those that
provide joystick play will make some attempt to also provide
keyboard commands and shortcuts. The reason for this is simple
-you can be sure that there is a keyboard of some type integrated
on most PC's.
In the typical Visual Basic
application you could use a forms KeyPress, KeyDown and
KeyUp events for registering user input via the keyboard.
This is a very easy approach and works well in most scenarios,
but not in games. In games you often want to have several keys
pressed at the same time to perform a given action (such as
moving diagonly by pressing the up and right arrow keys). Using
the normal key events of a form to register these inputs, you
would have to set flags for each key press, indicating that a
given key is pressed, and then release these flags when the key
is released. The game loop itself would then have to check the
flags to see if a given key -or multiple keys- are pressed. This
is a very cumbersome and ugly way to handle keyboard input in
games. To make matters worse it also presents a problem with our
game loop. The game loop sits in a Do
Loop structure, while
the inputs would be received in other events. This would very
likely cause the inputs to get out of synch with the animation
and drawing functions in the game loop.
So then...what should we use to
handle the keyboard input? This is one of those many times when
the API comes to our rescue. The exact function we are looking
for is the GetKeyState() API function:
Declare Function
GetKeyState Lib "user32" (ByVal nVirtKey As Long)
As Integer
The one parameter that the
function expects is a virtual key-code that represents the
key whose state must be checked. These keys are already declared
constants in the Visual Basic environment, with the prefix of vbKey[Keyname).
You can get a complete list on the names of theses constants from
VB's online help.
The return value from the
function is an integer. If bit 1 in the return value is set, then
the key checked has been toggled. This is only relevant for keys
such as Caps Lock, Scroll Lock and Num Lock. If the
fourth bit in the integer is set then the key we are checking has
been pressed. Using this knowledge we can make two constants to
help us with this function:
Const KEY_TOGGLED As
Integer = &H1
Const KEY_PRESSED As
Integer = &H1000
By using the Boolean AND
operator with the return value from a function call and the KEY_PRESSED
constant we can check to see if a given key is pressed.
The sample project Keyboard
demonstrates this. Everything interesting is in the main game
loop for the application RunMain. There are no time
restrictions on this loop, since we only want to demonstrate the
usage of the GetKeyState() function.
Private Sub RunMain()
'Show and refresh the form
Me.Show
Me.Refresh
Do
If (GetKeyState(vbKeyA) And KEY_DOWN) Then
lblKeyA.Caption = "A is Pressed"
Else
lblKeyA.Caption = ""
End If
If (GetKeyState(vbKeyLeft) And KEY_DOWN) Then
lblKeyLeft.Caption = "Left Arrow Key is Pressed"
Else
lblKeyLeft.Caption = ""
End If
If (GetKeyState(vbKeyCapital) And KEY_DOWN) Then
lblCapsLock.Caption = "Caps Lock is pressed"
ElseIf (GetKeyState(vbKeyCapital) And KEY_TOGGLED) Then
lblCapsLock.Caption = "Caps Lock is toggled"
Else
lblCapsLock.Caption = ""
End If
DoEvents
Loop Until TimeToEnd
End Sub
|
In each loop we check each of
the selected keys by getting the state and AND
it with our KEY_PRESSED constant. If this
operation comes out with a True result notify the user by writing
a message in the respective label. As you can see then we do two
checks on the Caps Lock key (vbKeyCapital), the first checking
for a key press and the next checking to see if it is toggled.
This about sums it all up for
the keyboard and us. Pretty simple?
The Mouse
As with the keyboard, then the
mouse is an essential input device for games. You do not see many
games in Windows that do not use the mouse either in the game
mode itself or as a user-interaction-helping device. We employ
the same kind of scheme with the mouse, as with the keyboard, so
the input will be synchronized with our game loop. The function
we will use is the GetCursorPos() API function:
Declare Function
GetCursorPos Lib "user32" (lpPoint As POINTAPI) As
Long
This function will get the
position of the mouse cursor (or pointer) and store it in the
POINTAPI structure passed to it.
The POINTAPI structure is
declared as such:
Type POINTAPI
x As Long
y As Long
End Type |
The X and Y member of the
structure is the coordinates of the mouse, in relation to the
screen. Since we usually want the position in relation to our
window or form, we must convert the structure from screen
coordinates to client coordinates, where the client is our
window. We could do this manually, but since there is an API that
already does this, we might just as well use it:
Declare Function
ScreenToClient Lib "user32" _
(ByVal hwnd As Long,
lpPoint As POINTAPI) As Long
The hwnd parameter is the
hWnd of the window we the position to. The lpPoint
parameter is the position we want converted. The resulting value
is relative to the upper-left point of the client window, which
is considered to (0, 0).

So now that we can get a fix on
the position of the mouse cursor, we might also want to know when
the user clicks one of the mouse buttons. Surprisingly enough we
have to use the same function as we did to get the input from a
key, namely the GetKeyState() API function. The constants
for the mouse buttons are declared in Visual Basic, just as with
the normal keys:
Left Button: vbKeyLButton
Right Button: vbKeyRButton
Middle Button: vbKeyMButton
This is all the information we
need to make the mouse useful to our games. The sample project
MOUSE demonstrates this using the mouse. We have our usual game
loop in the RunMain() procedure:
Private Sub RunMain()
Dim MousePoint As POINTAPI
'Show and refresh the form
Me.Show
Me.Refresh
Do
'Get the position of the cursor
GetCursorPos MousePoint
'Convert the point to our forms coordinates
ScreenToClient Me.hwnd, MousePoint
'Show the position
lblXPosition.Caption = MousePoint.x
lblYposition.Caption = MousePoint.y
'See if there are any mouse clicks
If (GetKeyState(vbKeyLButton) And KEY_DOWN) Then
lblLeftClick.Caption = "Left Mouse Button Clicked"
Else
lblLeftClick.Caption = ""
End If
'get the right button
If (GetKeyState(vbKeyRButton) And KEY_DOWN) Then
lblRightClick.Caption = "Right Mouse Button Clicked"
Else
lblRightClick.Caption = ""
End If
'Get the middle button
If (GetKeyState(vbKeyMButton) And KEY_DOWN) Then
lblMButton.Caption = "Middle Button Clicked"
Else
lblMButton.Caption = ""
End If
DoEvents
Loop Until TimeToEnd
End Sub
|
The MousePoint variable
is a POINTAPI structure. It will be used to get the information
on the position of the mouse with the GetCursorPos() API
function. The ScreenToClient() call is, as stated above,
made to ensure that the information we from the GetCursorPos()
function are translated so they become relative to the upper-left
most point of our window. Each of the mouse buttons is also
checked to see if they were pushed.
Joystick
When you are talking about
gaming, you have to talk about the joystick. It the thing that
most people associate with gaming, even though the majority of
games today does not use joysticks.
Using a joystick through the
Win32 system is a bit more complicated than what we have covered
in this chapter so far. With the mouse you have a free-moving
device, meaning that the device can be moved all over the screen,
giving you the coordinates of the cursor when ask for them. With
the keyboard you have but choices for each key, pushed or not
pushed. The joystick is a bit different to use, since with a
joystick you have a minimum and maximum value for both the Y- and
X-axis. These values sets the possibilities of movement with the
joystick, so that you can set the values you retrieve for the
actual position of the handle of a joystick relative to these
maximum and minimum values. Look at the following illustration:

The box represents the movement
space for the joystick. The point (-X, -Y) represent the lowest
possible values for the joystick, and the point (X, Y) represents
the highest possible values for the joystick. The mid point (0,
0) represent the joystick in its calibrated mid position. In
order to get the joystick into this position it usually has to be
calibrated so each of the noticeable values (X, -X, Y, -Y) will
be recorded. To calibrate a joystick you use the game controllers
program in the control panel of Windows.
In Visual Basic we have to go
through the API (surprised?) to get hold on any information on
the joystick. The basic function is the joyGetDevCaps() API.
This function returns the basic information that you need to
start using the joystick:
Declare Function joyGetDevCaps
Lib "winmm.dll" Alias "joyGetDevCapsA" _
(ByVal id As Long, lpCaps As
JOYCAPS, ByVal uSize As Long) As Long
Parameters:
id: The id of a joystick. If the
system only has one joystick then this number is 0. If the system
has two joysticks then this number is either 0 or 1
lpCaps: the structure to put the
information about the joystick into. The structure is declared as
such:
Private Type JOYCAPS
wMid As Integer
wPid As Integer
szPname As String * MAXPNAMELEN
wXmin As Long
wXmax As Long
wYmin As Long
wYmax As Long
wZmin As Long
wZmax As Long
wNumButtons As Long
wPeriodMin As Long
wPeriodMax As Long
End Type
wMid: The identification number of the manufacturer
wPid: The product identification umber
szPname: The product name
wXmin: The minimum X position of the joystick
wXmax: The maximum X position of the joystick
wYmin: The minimum Y position of the joystick
wYmax: The maximum Y position of the joystick
wZmin: The minimum Z position of the joystick
wZmax: The maximum Z position of the joystick
wNumButtons: The numbeer of buttons supported
wPeriodMin: The minimum polling interval when the joystick is captured
wPeriodMax: The maximum polling interval when the joystick is captured
|
Of the 12 members of this
structure we are really only interested in five of them, the
positions of the X and Y coordinate. These values represent the
extremes of the joystick, meaning that the value in wXmax
is the value that is returned for the X-coordinate when the
Joystick is at its rightmost position. The value in wXmin
is the value the Joystick will return when it is at its leftmost
position. For the Y coordinates the joystick is at its maximum
value when it is drawn backwards and at minimum when it is pushed
forward.
Put into perspective with the
illustration presented above, then the values in wXmin and
wYmin are the (-X, -Y) coordinate, and the values in wXmax
and wYmax are the (X, Y) coordinate. The point (0, 0) is
then the medium value of these two coordinates, which is also
what the joystick will return when it is centered (presuming that
it is calibrated correctly).
The last member of the
structure, which if of interest to us is the wNumButtons
member. This member holds the number of buttons the joystick has.
Each of the buttons are stored in a bit field, which represents
the currently pressed button(s). The constant declarations of the
buttons look like this:
Public Const JOY_BUTTON1 = &H1
Public Const JOY_BUTTON2 = &H2
Public Const JOY_BUTTON3 = &H4
Public Const JOY_BUTTON4 = &H8
Public Const JOY_BUTTON5 = &H10&
Public Const JOY_BUTTON6 = &H20&
Public Const JOY_BUTTON7 = &H40&
Public Const JOY_BUTTON8 = &H80&
|
With the constant declaration
you can do an AND operation on the returned value from the
polling of the joystick, if you get a true result that particular
button is pressed.
Anytime you want you can get
information on the joystick, by calling the joyGetPos()
function:
Declare Function joyGetPos Lib "winmm.dll" (ByVal uJoyID As Long, pji As JOYINFO) As Long
Parameters:
uJoyID: The ID number of the joystick, should be either JOYSTICK1 or JOYSTICK2
JOYINFO: The structure where the information on the joystick will be returned in. The structure
is declared as such:
Private Type JOYINFO
wXpos As Long
wYpos As Long
wZpos As Long
wButtons As Long
End Type
The wXpos and the wYpos is the current X and Y position of the joystick. Both of these values
are within the interval defined by the wYmin, wYmax and the wXmin, wXmax values from the
joyGetDEvCaps() function. The wZpos value is not used in this context.
|
The sample project JOYSTICK
demonstrates the use of a joystick to move a crosshair around the
main window. This is not the ideal use of the joystick, since the
mouse would do this just as well, if not better. But it serves
its purpose of demonstrating the use of a joystick.
The crosshair is a bitmap, which
is loaded using the usual scheme of creating device contexts with
bitmap data and drawing from them using the BitBlt()
function.
The first thing we do in the
load event of the form is to check whether there is a joystick
connected to the system, both a driver and the actual joystick.
This is done by a call to the joyGetPos() function. This
function will return certain error values if something went
wrong.
Private Sub Form_Load()
Dim rt As Long
Dim JoyTestInfo As JOYINFO
Dim JoyStickCaps As JOYCAPS
'Test to see if a Joystick is connected
rt = joyGetPos(JOYSTICK1, JoyTestInfo)
If rt <> JOYERR_NOERROR Then
If rt = JOYERR_UNPLUGGED Then
MsgBox "No joystick connected" & vbCrLf & "Finishing..."
ElseIf rt = MMSYSERR_NODRIVER Then
MsgBox "No Joystick driver on system" & vbCrLf & "Finishing..."
Else
MsgBox "Unknown Error" & vbCrLf & "finishing..."
End If
Unload Me
Exit Sub
End If
'Get the max and min position on the joystick
joyGetDevCaps JOYSTICK1, JoyStickCaps, Len(JoyStickCaps)
With JoyStickCaps
MaxX = .wXmax
MinX = .wXmin
MaxY = .wYmax
MinY = .wYmin
End With
'Load the images
DCSpriteBlack = GenerateDC(App.Path & "\crossblack.bmp")
DCSpriteInner = GenerateDC(App.Path & "\crossinner.bmp")
DCSpriteRed = GenerateDC(App.Path & "\crossred.bmp")
DCMask = GenerateDC(App.Path & "\crossm.bmp")
RunMainGame
End Sub
|
If any errors are reported, we
notify the user and exit the program.
The next thing we do is to get
the minimum and maximum values of the various coordinates on the
joystick with the joyGetDevCaps() function. These values
are stored in public variables, as we will be using them in the
game loop. Lastly the graphics are loaded and the main game loop
is started.
Since the values obtained from
the joyGetDevCaps() function are not at all compatible
with the size of our drawing area, we have to somehow make them
relative to our drawing area. This is done in the Resize event of
our form.
Private Sub Form_Resize()
'calculate the relative values
RelativeX = MaxX / Me.ScaleWidth
RelativeY = MaxY / Me.ScaleHeight
End Sub
|
The relative values are, as you
can see, calculated by dividing the maximum values we obtained
from the joystick by the current scale size of the window. We use
the scale sizes since the BitBlt() function only accepts
pixel values.
Private Sub RunMainGame()
Dim X As Long, Y As Long
Dim JoyInformation As JOYINFO
Me.Show
Do
'clear the form
Me.Cls
joyGetPos JOYSTICK1, JoyInformation
X = (JoyInformation.wXpos / RelativeX) - HalfSpriteWidth
Y = (JoyInformation.wYpos / RelativeY) - HalfSpriteHeight
'draw the mask
BitBlt Me.hdc, X, Y, SpriteWidth, SpriteHeight, DCMask, 0, 0, vbSrcAnd
BitBlt Me.hdc, X, Y, SpriteWidth, SpriteHeight, DCSpriteBlack, 0, 0, vbSrcAnd
'Determine if any buttons are down and draw the appropriate image
If (JoyInformation.wButtons And JOY_BUTTON1) Then
BitBlt Me.hdc, X, Y, SpriteWidth, SpriteHeight, DCSpriteRed, 0, 0, vbSrcPaint
End If
If (JoyInformation.wButtons And JOY_BUTTON2) Then
BitBlt Me.hdc, X, Y, SpriteWidth, SpriteHeight, DCSpriteInner, 0, 0, vbSrcPaint
End If
'Show it all
Me.Refresh
DoEvents
Loop Until TimeToEnd
'Delete the generated DCs
DeleteGeneratedDC DCMask
DeleteGeneratedDC DCSpriteBlack
DeleteGeneratedDC DCSpriteRed
DeleteGeneratedDC DCSpriteInner
End Sub
|
In the game loop the first thing
we do is to get information from the joystick, using the joyGetPos()
function. These positions are translated onto our window by using
the relative values calculated in the Resize event. Half the
width and height of the crosshair sprite is subtracted from each
of the values. This is done in order to enable the center of the
crosshair to go to the absolute edge of the window. After getting
the drawing point, we draw the mask. Then we start to examine the
two buttons using the wButton value in the JOYINFO
structure with the AND operator on the defined button constants.
If any of the two buttons are pressed we draw the appropriate
sprite onto the form on top of the mask.
When our loop has ended we
release all the generated DCs, so that we savor valuable
resources.
That is it on input. We will use
these techniques throughout the rest of the book for our samples
and game projects.
[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.
|