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