[Note:] The assumption is being made that previous tutorials have already been completed and that a project is
available and in a state to make the changes to be laid out in the current tutorial. That way the same classes and
references do not need to be made in each tutorial, but instead new classes can be added and existing classes and
procedures can be expanded upon.
2D Game Development with Managed DirectX: Tutorial 03 - Keyboard Input
This tutorial will walk you through setting up communication with the keyboard. By the end of the tutorial you will be
able to detect whether a key was pressed and use that to exit the application.
::Add the DirectX References::
To have communication with the keyboard, we need to add another DirectX dll. This time it will be the DirectInput dll.
Just as a side note, if you read the Microsoft forums, most peopel would advise you to stop using the DirectInput dll
because it will most likely not be supported in future versions of DirectX, but due to its ease of use and with the
announcement that there will not be a new version of DirectX (it's moving to XNA now), I find this kind of a moot point.
The basics of working with something that captures keyboard input will always be the same and it's the rare case where you
will end up something at a low API level to do that interaction with the keyboard yourself. So what I'm saying is whatever
you end up working with in the future, the concepts being introduced will be the same so I sleep easy at night giving a
tutorial using DirectInput. *phew*, I feel better getting that off my chest.
Referenced Dlls:
Microsoft.DirectX.DirectInput
Again, I've provided the exact version of the dll that I use in my tutorials to prevent unexpected errors from occuring
when you try to duplicate what I show in my tutorial. Upgrading to a newer version of the dll may give unexpected results
and or errors but they should be easily corrected with a little research and elbow grease.
::Creating the DXKeyboard class::
First, add a new folder to the project called "Input". This is where we will place classes that deal with, well, I guess
"Input". The first of these will be keyboard inpust so go ahead and make a new class in that folder called "DXKeyboard."
This class will manage interaction with the keyboard and keep track of keys that have been pressed.
The class level objects will be added first.
Class level objects in DXKeyboard
'Device for the Keyboard
Protected mKeyboard As Device = Nothing
'Indicates if the device is lost
Protected mDeviceLost As Boolean = False
'The Keys just triggered
Protected mKeysTriggered() As Boolean = Nothing
'The Keys being pressed (the ones currently held down)
Protected mKeysPressed() As Boolean = Nothing
The first object is the device object for the keyboard. This is the device that will handles communicating with the
keyboard.
The second object is a boolean variable to store whether communication with the keyboard has been lost.
Next, we have two arrays, KeysTriggered and KeysPressed. These stored keys that have been pressed and keys that are
currently being pressed.
Time to create the class constructor and begin initializing these objects.
Class constructor for DXKeyboard
Public Sub New(ByVal theOwner As Control)
'Initialize the key structure arrays
ReDim mKeysPressed(256)
ReDim mKeysTriggered(256)
'Create a new Device with the keyboard guid
mKeyboard = New Device(SystemGuid.Keyboard)
'Set the data format to keyboard data
mKeyboard.SetDataFormat(DeviceDataFormat.Keyboard)
'Set the cooperative level to background and non-exclusive
mKeyboard.SetCooperativeLevel(theOwner, CooperativeLevelFlags.Background Or CooperativeLevelFlags.NonExclusive)
'Attempt to acquire the keyboard for use by the application
Try
mKeyboard.Acquire()
Catch ex As InputLostException
mDeviceLost = True
End Try
End Sub
The constructor takes one parameter. "theOwner" indicates what control (in our case a form) is attempting to establish a
communication with the keyboard. The constructor then proceeds to initialize the Keys Pressed and KeysTriggered arrays and
then initialize the Keyboard device object.
The data format for the device is then set to keyboard. Had the direct input device object been created with a different
guid (say for example a mouse or a joystick, gamepad, etc) a different data format (one that corresponded to the guid
used) would have been set. In our case we're setting up a DirectInput device object to talk with a keyboard so we created
the DirectInput device object with the system guid for our keyboard and we set the dataformat for the object to indicate
we are using a keyboard.
Now we have to indicate the CooperativeLevel. Different flags can be joined together to change the way an application
communicates with a keyboard. You might make it so your application is the only application that can talk with the
keyboard for example or you might want to disable the WindowsKey. By using different flags, you can create that kind of
behavior.
Here's a short little synopsis of what all the flags mean.
Cooperative Level Flags and Descriptions
One of these...
Background - always receive communication from the keyboard
Foreground - only give communication from the keyboard if theOwner has focus
...can be combined with one of these...
Exclusive - tell the keyboard we only want it talking to us or else
Non-exclusive - have more of an open relationship with keyboard and don't get jealous if we
find it chatting up someone else.
...which could be combined with this if you want.
NoWindowsKey - basically tells the keyboard to ignore the windows key being pressed. This
is important when running in
full screen mode so that the game doesn't lose focus when this key is pressed.
For now, I'm using non-exclusive and background, this means that we don't want exclusive control of the keyboard (meaning
other applications can communicate with it too) and that we always want to be receiving information from it (that's the
background part). This basic allows other applications to have the maximum control over the keyboard becase we like to
share and play nice.
Now back to discussing the constructor. The last step taken in the constructor is to try and acquire communication with
the keyboard. This is wrapped in a Try..Catch in case an error occurs so that the Device is Lost variable ca be set and an
attempt to acquire a communication with the keyboard can be tried again at a later point in time (like when the user
realizes they just kicked out their cord and scurry under their desk to plug it back in)
The next procedure to make is one that reads the keyboard state and stores the keys pressed and those being currently
pressed.
UpdateKeyboardState procedure in DXKeyboard
'Description: Read the keyboard state and reacquires access to the keyboard if it
is lost
Public Sub UpdateKeyboardState()
'Check the device
If mKeyboard Is Nothing Then
Return
End If
'Try to get the current state
Try
'If communication with the keyboard has been lost then
'try to re-acquire a dialog with the keyboard.
If mDeviceLost = True Then
mKeyboard.Acquire()
Else
'Get all of the keys that have been pressed from the
keyboard
Dim aPressed As Key() =
mKeyboard.GetPressedKeys
'Update the triggered keys array
UpdateKeysTriggered(aPressed)
'Update the pressed keys array
UpdateKeysPressed(aPressed)
End If
Catch ex As InputLostException
'An error occurred, the a dialog with the keyboard has been
lost
mDeviceLost = True
End Try
End Sub
Basically this procedure retrieves all of the keys that have been pressed from the keyboard (it likes to tell on them and
we like to listen to the latest gossip about keys being pressed). The keys being pressed are then stored in the
KeysPressed and KeysTriggered arrays via two functions. UpdateKeysTriggered and UpdatesKeysPressed which we will look at
next.
UpdateKeysTriggered an UpdateKeysPressed procedures for DXKeyboard
'Description: Updates the KeysTriggered array
Protected Sub UpdateKeysTriggered(ByVal thePressedKeys() As Key)
'Reset the KeysTriggered
For aCounter As Integer = 0
To mKeysTriggered.Length - 1
mKeysTriggered(aCounter) = False
Next
'Store the keys
For aCounter As Integer = 0 To thePressedKeys.Length - 1
Dim aIndex As Integer =
thePressedKeys(aCounter)
If mKeysPressed(aIndex) = False Then
mKeysTriggered(aIndex) = True
End If
Next
End Sub
'Description: Updates the KeysPressed array
Protected Sub UpdateKeysPressed(ByVal thePressedKeys() As Key)
'Reset KeyStrokes
For aCounter As Integer = 0
To mKeysPressed.Length - 1
mKeysPressed(aCounter) = False
Next
'Store the keys
For aCounter As Integer = 0 To thePressedKeys.Length - 1
Dim aIndex As Integer =
thePressedKeys(aCounter)
mKeysPressed(aIndex) = True
Next
End Sub
These procedures add to an maintain the arrays that store the keys currently being pressed and those that have been
pressed. The next two procedure provide a way to query the Keyboard class to determine if a particular key has been
pressed or is being pressed.
KeyPressed and KeyTriggered procedures in DXKeyboard
'Description: Checks if a certain key is pressed
Public Function KeyPressed(ByVal
theKey As Key) As Boolean
Return mKeysPressed(Convert.ToInt32(theKey))
End Function
'Description: Checks if a certain key has just been activated
Public Function KeyTriggered(ByVal theKey As Key) As Boolean
Return mKeysTriggered(Convert.ToInt32(theKey))
End Function
By passing a particular key into these procedures, it can be determined if that particular key has been pressed or is
currently being pressed. Now the only thing left in the Keyboard class is cleanup.
Class destructor and Dispose procedures in DXKeyboard
'Description: Class destructor
Protected Overrides Sub
Finalize()
Call Dispose()
MyBase.Finalize()
End Sub
'Description: Disposes of any objects created in the class
Private Sub Dispose()
If Not (mKeyboard Is Nothing) Then
mKeyboard.Unacquire()
mKeyboard.Dispose()
End If
mKeyboard = Nothing
mKeysTriggered = Nothing
mKeysPressed = Nothing
End Sub
With good cleanup practices done, that's all there is to the DXKeyboard class. Next, we'll move on to actually
implementing and using the class. We will be doing that in the GameEngine class.
::Adding the DXKeyboard object to the GameEngine class::
Begin by adding a class level object and property to GameEngine.
Add the Keyboard object and Property to the GameEngine class
Private mKeyboard As DXKeyboard
Public ReadOnly Property
myKeyboard() As DXKeyboard
Get
Return mKeyboard
End Get
End Property
This property will be used to access the keyboard from other classes in the application. Next, we need to initialize the
keyboard object and we will do that i the class constructor of the GameEngine class.
Initialize the Keyboard object in the GameEngine constructor
'Description: The class constructor. Create the objects for the class
Public Sub New(ByVal theRenderTarget As Control)
mGraphics = New DX3DEngine(theRenderTarget, True)
'Create a new spirte object from a file
mFirstSprite = New DXSimpleSprite(mGraphics.myD3DDevice, "Textures/MyFirstSprite.PNG", True, 64, 64)
'Create a new sprite object from a resource stream
'mFirstSprite = New DXSimpleSprite(mGraphics.myD3DDevice,
"CreatingA2DSprite.MyFirstSprite.PNG", False, 64, 64)
'Initialize the keyboard object
mKeyboard = New DXKeyboard(theRenderTarget)
End Sub
Next, we need to make sure that the keyboard object gets updated regularly. We will add this piece of code to the render
code since we know that it gets fired regularly. At a later point, I'm sure this will be moving, but it works fine to
update the keyboard at this point for now.
Update the Keyboard object in the Render procedure
'Description: Setup the device for rendering
Public Sub Render()
mGraphics.BeginRender()
mGraphics.DrawSprite(mFirstSprite)
mGraphics.EndRender()
'Update the keyboard
myKeyboard.UpdateKeyboardState()
End Sub
Now the only thing left to do is dispose of the new object properly and we're done.
Dispose of the new Keyboard object
'Description: Dispose of any object created in the class
Private Sub Dispose()
mFirstSprite = Nothing
mGraphics = Nothing
mKeyboard = Nothing
End Sub
So now we have a Keyboard class, we've got an object in our GameEngine for that class, let's go ahead and put that object
to work now. We'll be modifiying Main next.
::Enabling the "Escape" key to exit the application::
In main, we'll add some code to see if the "Escape" key has been triggered, if it has then we will dispose of the form and
the main game loop will then exit and the application will close.
Checking for "Escape" to exit the application
'Description: The main entry point for the project. Sets up the game engine and
contains the game loop
Public Shared Sub Main()
'Create a form to attach the DirectX device to
Dim aMainForm As New
Windows.Forms.Form
'Create the Game engine
Dim aGameEngine As New GameEngine(aMainForm)
'Display the form
aMainForm.Show()
'Continue the game loop while the form is still running
Do While aMainForm.IsDisposed = False
'Give the computer a chance to process some other things
Application.DoEvents()
'Render the scene
aGameEngine.Render()
'Check to see if the Escape key has been triggered, if it has, then dispose of the form
so that the game loop
'will end and the game will exit
If
aGameEngine.myKeyboard.KeyTriggered(Microsoft.DirectX.DirectInput.Key.Escape) = True Then
aMainForm.Dispose()
End If
Loop
'Destroy the Game Engine
aGameEngine = Nothing
'Destroy the Main form
aMainForm = Nothing
End Sub
Now that we can close the application by hitting the escape key, running in fullscreen mode becomes a lot easier because
shutting the application down is as simple as hitting "Escape".
In Summary
That's it. You can now run the application (full screen or windowed) and by hitting the "Escape" key, the application will
close. While I begin work on the next tutorial, I'd recommend playing around with detecting different keys being pressed
and learning the differences between "KeyPressed" and "KeyTriggered". You still do want another tutorial right? I just
thought you might like to see that stick man move around and now that we can detect that keys are being pressed...
Download the Source Code
The source code for the tutorial is available here.
Encouraging Feedback
I'm still open to and strongly encourage you to leave any feedback you may have. I am learning from these tutorials myself
as I go, but it is encouraging to know that other people are reading them as well. Also, if you have any difficulty with
the tutorials, feel free to let me know and I'll do what I can to help you out.