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.



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.

Print | posted @ Monday, July 10, 2006 5:39 PM

Comments on this entry:

Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by thorkia at 7/11/2006 4:17 AM

A I the only one commenting? I thing these are really good tutorials. I like the way you handle the keyboard tracking.
Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by George at 7/11/2006 6:24 AM


thorkia -

Thanks for taking the time to comment again. So far it's you and one other individual who have been commenting.

That's good enough motivation for me to keep doing them. I just wanted somebody to be interested in them and since I have two, I guess I met my goal ;)

Hopefully in the future, more people stumble across them as well. If you tutorial it, they will come. I think. Maybe. But they won't leave comments.

Guess it's time to start wrapping up Tutorial #4.
Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by galantir at 7/17/2006 5:22 AM

Hmm tried to comment but they aren't showing.
Great tutorials, although it still leaves me to wonder how to do continuous movement with keyboard.
This only moves you one step at a time.
Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by George at 7/17/2006 8:22 PM


galantir -

not sure why your comments weren't coming through, but I'm glad this one did. thanks for taking the time to leave a comment and I'm glad you are enjoying the tutorials.

it's also nice to see that you're already changing the code and playing with some movement. the next tutorial (which i'm hoping to get posted in the next couple of days) covers movement, but...it's still just going to show you the one step at a kind of movement.

but i don't want you to have to wait for me to get a tutorial up for you so if want to get the smooth, hold the key down to move type of movement, you'll want to use the "KeyPressed" check instead of "KeyTriggered" when checking the keys for movement.

However, to prevent the sprite from just moving instantly out of the viewing area, you'll need to incorporate some timing to limit how far the sprite moves in a given time frame.

I'm planning on covering that type of movement in Tutorial 05, but I didn't want you to have to wait that long to have an answer.

Hope that helps your development. Let me know if you have more questions, I'm more than glad to help in any way I can.

Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by galantir at 7/18/2006 1:28 AM

Thanks for the info it sure will help.
I'm still very new to VB(only been using it for 5 months now) but managed to create usefull programs with it already(removable drive test application, tool to make disk unrecovarable).
With your first tutorials i managed to create a graphics tool which draws by reading simple commands stored in a textfile(so i hope your going to do a bit of 3d too in the future so i can enhance that tool with 3d drawing).
Ow and i figured out why my comments weren't showing.
the code you need to enter is case sensitive which i forgot. So that's my mistake.
Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by brucedude at 7/21/2006 8:49 AM

These tutorials are fantastic. I have been working with DirectX as a hobby for some time - since it was called the Games SDK with VC++ 4.2 and I was looking for some info on the managed stuff as I left C++ a few years ago. Since I do most of my work in C# now, I will be porting all of this code to that. One thing I can defintely say, is that the managed code is so incredibly fast to write in comparison, and the runtime speed doesn't seem to suffer all that much. I will do a sample with loading up the screen with 100+ sprites and see how that fares. Also couldn't hurt to try a scrolling tile background ala Mario Bros., etc.

I do have a question though, regarding the main game thread. Do you think it is a better approach to simply have a loop that executes as often as it can and updates the frames? Or a timer that executes every X milliseconds? With multi-core processors, I guess a backgound thread that compiles the next frame in the background and blits it on the timer update is possibly the best approach, but I wanted your opinion.

By the way, do you think you will be doing any DirectInput (joysticks) and DirectSound stuff?
Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by George at 7/21/2006 9:59 AM


brucedude -

thanks for taking the time to show some appreciation. lol, i find it very funny that you will be converting my tutorials to C#. i have a link on my main page in the sidebar to a guy who has a bunch of C# tutorials that i converted to VB. it's awesome that the languages are at a point that such conversions are even possible.

if you do end up converting the tutorials to c# and want to share with the community. i'd be more than glad to host the project files for others who might appreciate your time and effort.

in response to the main game thread. i think you just have to make a decision on what your game needs. there are a variety of ways to make game loops. the one i started with in my tutorial is just a start and we'll eventually be modifying it once i introduce the timer class.

i myself prefer a game loop that updates as often as it can, but there are inherent problems with that approach as well as having a game loop that only updates every x milliseconds.

i do play on doing some DirectSound tutorials, but they will definitely be at a later point. I will consider doing a joystick or gamepad tutorial since you've mentioned it.

Thanks again for stopping by and thanks for the feedback and suggestions. Good luck in your conversion and any future projects.
Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by Poldie at 10/24/2006 1:16 PM

Nice article. I used this code to get my first vb.net directx project off the ground. I'll revisit it when I get further into it, but it's working great for now.

One thing though - I have to comment out the line:

mKeyboard.Unacquire()

otherwise I get the error:
---
Object reference not set to an instance of an object.
---

which I find odd, as clearly mKeyboard isn't nothing, and the Dispose call doesn't cause an error.

Any ideas what this could be?

Also, if I'm just reading the keyboard, so I actually need all of this code?
Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by Ben from New Zealand at 11/4/2006 4:39 AM

Fantastic tutorials! I found these through google while looking for a good way to do keyboard input. I have a top-down sprite style game i am creating in C# - it's fun! I might switch to VB, as funnily enough, i prefer the VB syntax.

One question i had ( and a comment ) do you think the usual Timer control for Windows forms would be acceptable to use for the main game loop? I am guessing there is a better alternative in the DX framework.

Thanks again - wonderful tutorials, and i'll be visiting regularly.
Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by havatchu at 3/19/2007 12:11 PM

Having done a few tutorials for AutoCAD, I know how time consuming they can be. I appreciate the time and effort it took to relay these tutrials in such a concise and easy to read manner. I'm a hobbyist programmer and these have helped a great deal. Thank you again!
Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by Jamison Henthorn at 3/17/2008 7:48 PM

Must have more tutorials!

I absolutely love your tutorials, and find that they give a sense of how to not only work within MDX, but also how to setup effective techniques for programming in general.

My only issue: You stopped! Please tell me you have more of them squirreled away somewhere and just never got around to uploading them. =)
Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by jd at 4/9/2008 6:35 PM

if i understand this correctly, one has to use a timer object to constantly look at the keyboard (as fast as possible not to miss any fast keypresses) to get the state of the keys? is there any way to have more a reactive setup?
Gravatar # re: Managed DirectX Tutorial 03: Keyboard Input
by Jonatan Nyqvist at 5/22/2008 8:43 AM

Great tutorials!

This really helped me a lot starting to work with directx!
But please tell me there's more from where these three tutorials came from! What about the tiled background or something else as important?

Your comment:

Title:
Name:
Email:
Website:
 
Italic Underline Blockquote Hyperlink
 
 
Please add 6 and 2 and type the answer here:
 
Twitter