Introduction

There are few things more annoying than a game that forces you to use certain keys, decided on by the developers, to navigate the game. This is especially true for FPS and RPG type games, where movement throughout the world is important. I know that if I sat down at my brother’s computer and tried to play Doom 3, I would go absolutely CRAZY with his movement keys the way they are. In general most games allow you to change which keys perform which actions in what’s called a “Key mapping” or “key binding” system. The term “key binding” comes from the old iD Software game Quake, where they introduced a console that utilized a “bind” command to say “I want to bind this key to this action”.

Other games aren’t so flexible, but still allow key binding/mapping through the use of an interface window where they have all of the actions you can perform in the game, with boxes that indicate which key is bound to that action. Some notable games, just off the top of my head, that feature the ability to map keys to actions: Rainbow6, Rogue Spear, Raven Shield, Doom, Doom2 ,Doom3, Quake, Quake2, Quake3, Ultima Online, WoW.

Some games don’t require key mapping. Warcraft, Warcraft 2, and Starcraft did not allow for it, mainly because it wasn’t necessary. Each unit had its hotkey, and each command was intuitive. B S was “build smith”... B B was “build barracks”, etc... So obviously this isn’t ALWAYS a necessary thing, but in general if you’re making an FPS or RPG, or any game where you need to move around a 3D space, you will want to provide such a system for your users.

Starting Concepts

When creating my own key binding system, I went through about 5 different methods to make it work. Some of these methods were far too complex, and didn’t work that well. What I finally settled on was a method where I had a few different indexed arrays of “TV Key”s.

I have three main arrays: InterfaceKeys, ControlKeys, and ActionKeys. Each of these is an indexed list (or “Dictionary” object in .NET 2005). Others may know this as a hashtable.

For the indexes, I used Enums. With these two basic things, you can make a full-fledged keybinding system.

The Enums

The purpose of the enums is to allow you to create named numbers. For example, my movement control keys are declared like so:

<Serializable()> Public Enum CharacterControlKeys As Byte
  Forward
  Backward
  StrafeLeft
  StrafeRight
  TurnLeft
  TurnRight
  LookUp
  LookDown
  Run
  Walk
  Dbl
End Enum
<Serializable()> Public Enum OneTimeActions As Byte
  Screenshot
  Jump
End Enum

Of course CharacterControlKeys.Forward = 0, .Backward = 1, and so on. So instead of having to remember that index 0 of the array is “forward”, I just reference it with the enum.

Of course the actions enum is not quite as standard. For your game it may be “cast a spell” or something... for other games it may be “press down on the car’s gas pedal”.

The Arrays

The arrays are basically a way for one array to meet up with another array. It’s like the old matching A thru Z with 1 thru 26 you used to see on tests in school. 1 matches up with N, 2 matches with R, etc... The Dictionary object in .NET, using enums as the index, allows us to do this.

  Public InterfaceKeys As New Dictionary(Of InterfaceToggles, CONST_TV_KEY)
  Public ControlKeys As New Dictionary(Of CharacterControlKeys, CONST_TV_KEY)
  Public ActionKeys As New Dictionary(Of OneTimeActions, CONST_TV_KEY)
  Public CustomBindings As New Dictionary(Of CONST_TV_KEY, Bind)

It’s pretty straight-forward for the first three. Basically, you’re setting up a relationship between keys and actions, where any key can go to any action.

Setting up your default keys

I personally have a class named “InputSystem” that I use, which contains my TVInputEngine object. I also have my key handling functions here. My “New” sub initializes the TVInputEngine, and sets up the default keys. You want to do this prior to reading in any saved keybinding files... so if a keybind isn’t saved, you have a default already in place.

Here are a few examples of those defaults, and how to set that “key to action” relationship. Keep in mind the action itself is the index, and you’re assigning a key to it.

          InterfaceKeys(InterfaceToggles.BackPack) = CONST_TV_KEY.TV_KEY_B
          InterfaceKeys(InterfaceToggles.CharacterWindow) = CONST_TV_KEY.TV_KEY_C
          InterfaceKeys(InterfaceToggles.MenuWindow) = CONST_TV_KEY.TV_KEY_ESCAPE
          InterfaceKeys(InterfaceToggles.ChatWindow) = CONST_TV_KEY.TV_KEY_RETURN
   
          ActionKeys(OneTimeActions.Screenshot) = CONST_TV_KEY.TV_KEY_SYSRQ
          ActionKeys(OneTimeActions.Jump) = CONST_TV_KEY.TV_KEY_SPACE
    
          ControlKeys(CharacterControlKeys.Backward) = CONST_TV_KEY.TV_KEY_D
          ControlKeys(CharacterControlKeys.Forward) = CONST_TV_KEY.TV_KEY_E
          ControlKeys(CharacterControlKeys.StrafeLeft) = CONST_TV_KEY.TV_KEY_S
          ControlKeys(CharacterControlKeys.StrafeRight) = CONST_TV_KEY.TV_KEY_F

After setting up the defaults, you should load saved keybinds. I use a serialize/deserialize method for saving custom classes to files. That is not part of this tutorial, though.

Handling Input

Handling input is something that’s done every rendered frame. In my custom input handling class, I have a “HandleInput” sub. In that sub, I check various keybinds, using their indexes, to see if certain keys are being pressed.

My character object has booleans for the movement of the character, like “MoveForward” or “MoveBack”.

      Character.MoveForward = Input.IsKeyPressed(ControlKeys(CharacterControlKeys.Forward))
      Character.MoveBack = Input.IsKeyPressed(ControlKeys(CharacterControlKeys.Backward))

As you can see here, I’m “looking up” the actual KEY value using the enum-indexed array. So if “ControlKeys(CharacterControlKeys.Forward) = CONST_TV_KEY.TV_KEY_E”, and the E key is pressed, Input.IsKeyPressed will be checking to see if TV_KEY_E is pressed... or whatever key is stored in “ControlKeys(CharacterControlKeys.Forward)”. Basically, this whole line of code is “If the user is pressing the key assigned to the ‘move forward’ command, set the MoveFoward value to true.”

Those things that you need to check for every frame should use the IsKeyPressed function. Basically, anything that has to happen each frame that the key is held down, and stop when the key is released should use “IsKeyPressed”.

For things like one-time toggles (like a command to do a specific one-time act, such as opening a GUI window), you should check for keys pressed or released since last check. Let’s say you want an action where on keydown, the character does one thing (like, crouching, building up power for a jump). On the key release, the character uses that stored up power to jump based on the length of time the key was held down. This requires recognizing when the key is pressed, and when it’s released.

Thus, the next part of the HandleInput sub uses the GetKeyBuffer function of the TVInputEngine object.

      Input.GetKeyBuffer(buffer, numkeys)
      For I As Integer = 0 To numkeys - 1
          GUIRoot.InjectKey(buffer(I))
          If buffer(I).Pressed Then
              KeyPressed(buffer(I))
          End If
          If buffer(I).Released Then
              KeyReleased(buffer(I))
          End If
      Next

As you can see, I iterate through the keybuffer, and call KeyPressed or KeyReleased based on the TV_KEYDATA I get.

FYI: “buffer” was declared previously as a 256 element array of TV_KEYDATA.

So in the “KeyPressed” and “KeyReleased” subs, you now have the key that was pressed or released, and we need to get the action that goes with the key.

For each of your Dictionary objects that contain your keybindings, you need to check to see if that Dictionary contains the VALUE of the key itself.

      If ActionKeys.ContainsValue(KeyCode.Key) Then

Then go through your actions, and check to see if the key matches an action, and if so, perform that action. Using a “Select Case” type of statement works well here.

      If ActionKeys.ContainsValue(KeyCode.Key) Then
          Select Case KeyCode.Key
              Case ActionKeys(OneTimeActions.Screenshot)
                  RaiseEvent Screenshot()
                  Exit Sub
              Case ActionKeys(OneTimeActions.Jump)
                  Character.Jump()
                  Exit Sub
          End Select
      End If

You’re basically doing a REVERSE lookup of the key in the array. You know a key was pressed that has an action associated with it, so match the key pressed to the action, and take appropriate action.

So now you’ve gone from key pressed, to action performed, without hard-coding a key to a specific action.

 
tutorialsarticlesandexamples/creating_a_key_binding_system.txt · Last modified: 2013/11/22 13:32