[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.
!(https://geekswithblogs.net/images/geekswithblogs_net/clingermangw/5055/r_CropperCapture%5B28%5D.Jpeg)
Referenced Dlls:
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.
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.
