Visual Basic Explorer
Visual Basic Explorer
 Navigation
 Home


 Coding
 Source Code

 FAQ Center

 VB Tips

 Downloads

 ToolBox

 Tutorials

 VB Games

 VB News

 VB Award

 VB Forums



 Affiliates
 Planet Source Code

 Rent a Coder

 DirectX4VB


 Misc
 Search

 Feedback

 Advertise

 About


Need to hire
a VB coder?

Please support our sponsor:

 Home 
 Site Map 
 Forums 
 News 
 Feedback 



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 form’s 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.



Home | About | What's New | Source Code | FAQ | Tips & Tricks | Downloads | ToolBox | Tutorials | Game Programming | VB Award | Search | VB Forums | Feedback | VBNews | Copyright & Disclaimer | Advertise | Privacy Policy |

Quick searches: Site Search | Advanced Site Search 

Copyright 2002 by Exhedra Solutions, Inc.
By using this site you agree to its terms and conditions
VB Explorer and VBExplorer.com are trademarks of Exhedra Solutions, Inc.