Managed DirectX Tutorial 03: Keyboard Input

[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.

This article is part of the GWB Archives. Original Author: George Clingerman

New on Geeks with Blogs