AdventureFar - Input system
game development input event code
<<< You probably don't want to read this - unlike my other entries, I wrote this more as a debrief for myself. It's rather dull. >>
I'm currently working on AdventureFar's input system. I want to support custom key-bindings for players, foreign keyboards, and gamepads or other input devices. Sometime in the future, after AdventureFar's release, I might even want to do a port for iPads or Android or Win 7 tablets. But that last one is just speculation of how things go.
The engine of AdventureFar, and it's method of handling input, is coming along well.
The engine itself is unnamed, since I don't consider it a separate project, even though it is isolated in code by the way I organize my source files - I think I've mentioned this before. If I do refer to it, I call it "The AdventureFar engine", as in, "The engine AdventureFar uses", but not as a proper name. I'll probably give it a real name once AdventureFar is complete and I use the same engine for a second project.
This (using the same engine for a second game) is not pie-in-the-sky thinking either - Since the engine is being passively developed as a side-effect of working on an actual game, it is very capable of being used easily for a second or third game of the same genre (2D turn-based RPG), even if the games themselves are quite different from each other. I strongly encourage this method of game 'engine' production, as I'm seeing real results from it (as opposed to building an engine to make a game, where I see no results from my prior attempts). But wisdom is justified by her children, so we'll see whether it all works out in the end, after I actually ship the game.
But I'm sidetracked. Back in focus: Input system.
The input system, while not being the best in the world, I feel happy with. It's actually not even part of the 'Engine' body of code, but is self-contained in the 'Common' code body (see afore mentioned link). If it ends up working well, I think it would be worthwhile to spin it off as it's own self-contained library released as open-source (after some heavy re-architecturing, methinks. The code is clean, but the classes code be designed better)... but only if it can last through the fire of an actual shipped game, otherwise it might turn out to be a hindrance more than a help.
Currently, how it works is I have a series of classes who, through inheritance, describe the different types of input. Using multi-inheritance, they also provide a common interface to the input types that are similar - I grouped the input type into three or four groups. The primary one is the "Button" style interface, of which mouse-buttons, gamepad buttons, and keyboard keys all fall into. Each class has a uint32_t mask, making each gamepad button, keyboard key, mouse button, gamepad joystick axis, mouse scrollwheel (up or down) uniquely identified. I use the masks for comparison.
That's all independent of whatever API provides the input. I have an API specific translator to convert to my own input. Normally, I'd just stick with the input of whatever API I'm using, but I feel like this is a necessary step I have to take, though I'm wary of NIH syndrome and am slightly uneasy about the multiple-inheritance - it feels a bit like inheritance abuse, so I'm wary of taking it too far, and have thought of some non-inheritance methods I may switch to.
The cool thing, that I am happy with, are CommandTriggers and MovementPlaneMaps. All that abstract input stuff is simply to make the input API-independent, and to provide a common interface to common input 'styles' (like buttons).
A 'CommandTrigger' is a class that, using the uint32_t mask, triggers a string command (I'd like to eventually switch to string hashes, but it might not be needed, so I'll leave it as strings for now). It's the string command (A 'Command' struct that contains the string and one other piece of data; hence 'CommandTrigger' - it 'triggers' the release of a 'Command') - It's the string command that is actually processed by the game itself. It's passed to the game states, which can use it, discard it (so other states can't process it), or just ignore it.
But the big deal with this is... Player key bindings are as simple as: <uint32_t abstracted input mask> = <command string>. In fact, that's exactly how I intend to save it to config files for storing the custom key-bindings.
A 'MovementPlaneMap' is the other side of this. A MovementPlane is a two-dimensional 'plane' of relative movement, from -1.0 to 1.0 on both horizontal and vertical. A MovementPlane can be used for multiple purposes... My intent, and we'll see if it works out or not, is to use MovementPlanes for both moving the player in-game, and for panning the camera when in debug mode. However, due to it's generalized nature, I can easily picture it be applicable to 3D games as well, controlling the 'look up/down', and 'rotate left/right'. That's a good coding sign, I feel, when you come up with a solution that is immediately applicable to your code, but that also seems applicable to future scerarios, regardless of whether you intended it to work for those future occasions.
Now a 'MovementPlaneMap', on the other hand, is what binds input to a MovementPlane. Using the abstracted input, I can bind gamepad axes just as easily to a MovementPlaneMap as I can bind the arrow keys, or any other key.
To (hopefully!) handle international keyboards, I have what I call a 'Keyboard Descriptor File'. It's a simple plain-text file that maps API-specific keycodes to generalized keycodes. At the same time, it also maps the visible text name of the key in your local language. Adding new languages is just a matter of adding a new file. Adding a new foreign keyboard should hopefully be that easy as well, just changing the API keycodes (if the API itself supports international keyboards).
Here's an example of a keyboard descriptor file for SFML 1.6 (the API I'm using currently), in the english language, for Qwerty keyboards. (It's currently my only keyboard descriptor file... but the simplicity of the solution is what I'm banking on for getting other keyboards functioning later on).
A = 97 B = 98 C = 99 D = 100 E = 101 F = 102 G = 103 H = 104 I = 105 J = 106 K = 107 L = 108 M = 109 N = 110 O = 111 P = 112 Q = 113 R = 114 S = 115 T = 116 U = 117 V = 118 W = 119 X = 120 Y = 121 Z = 122 0 = 48 1 = 49 2 = 50 3 = 51 4 = 52 5 = 53 6 = 54 7 = 55 8 = 56 9 = 57 Escape = 256, LControl = 257 LShift = 258 LAlt = 259 LSystem = 260 RControl = 261 RShift = 262 RAlt = 263 RSystem = 264 //And more...
There's only one minor flaw with this solution, and that's that if the player switches his keyboard his previous key bindings are invalid. But if he's switching his keyboard, that's something I'd expect to happen anyway.
I'm please with the solutions, though I'd love to re-implement the solutions with better code architecture. The code itself (my coding style) is clean, but again, the class architecture could use some work. It's not up to the quality of the rest of the code base. On the other hand, it is completely isolated from the rest of the code base also, so I can refactor it in isolation.
Whether or not it'll actually work for international keyboards is unknown to me. I don't have an international keyboard available to test on, nor is it a real priority right now... the primary goal was to get (American) input handling in place. Again, wisdom is justified by her children, so this might blow up in my face... but I wont be any worse off if it does, 3 or 4 days work, isolated code, if I have to scrap it, fine! At least I can move on to more important objectives (I had to get something implemented here before I could move on). And besides, even if I never support international keyboards, I know for a fact that this will support language translations (for Latin-derived languages), and that's something I definitely want for my game.
Okay, boring post over! I just needed a place to de-brief.
Input system: Functional
Time cost: 3-4 days
Structural integrity: Barely passable (for half of it)
Code cleanliness: Normal quality
On another note, my GameState system, which was 'Passable' in structural integrity, I scrapped and rewrote to my satisfaction. So the overall structural integrity of the entire code base is pleasing to me, with the poor spots (such as the above one) being rare. There are a few 'scaffolding'* code areas, but that's to be expected at this state.
*Temporary code required to move forward, that will by it's very nature have to be removed before the game ships. Kinda 'placeholder' code, that, if the game is to function at all, must be replaced.
Since I rewrote the GameState system (a week ago? two weeks?), I've outlined the game states needed by the game, and even implemented (copy and pasting skeleton classes) the skeletons of those GameStates into the 'Game' section of the codebase. My goal is to fill out the first several states in the next week, and to re-implement walking around the game world in the two weeks following.
Basically, the next two months:
- Main menu state
- Walking around world (again! I had this functional a full year ago, but alot has shifted since then)
- Fully functional game editor (Again. =( Previously it was a stand-alone tool. Now it'll be integrated with the game itself. I also have a few more map features I need to implement to meet the graphical goals of the game)
But I'm not bothered by having to re-tread already walked ground. That's the price of learning. Plus, things are stabler, more flexible, and easier to work with this time around. Development is occurring faster, and walking the same ground the second time always occurs in less time than the first initial exploration.