Jump to content
  • Advertisement
  • 03/19/13 09:14 AM

    Designing a Robust Input Handling System for Games

    General and Gameplay Programming

    ApochPiQ
    A common question for those designing a new game engine is "how do I handle input?" Typically, there's a few core issues that pretty much every game faces, and unlike many areas of game implementation, input is one where we can more or less build a one-size-fits-all framework. Fortunately, this is a pretty straightforward thing to do, and even is portable across APIs and platforms pretty easily. For the purposes of this article, we'll focus on the platform-independent side of things. Getting input from the underlying hardware/OS is up to you; for Windows, Raw Input is pretty much the de facto standard; XInput might be useful if you want joystick/controller support. For other platforms, research and select the API or library of your choice. Got a way to get pure input data? Good. Let's take a look at the overall input system architecture.

    Designing a Robust Input Handling System

    We have a few goals here which should make this system (or ones based on it) applicable for pretty much any game, from a simple 2D platformer to an RTS to a 3D shooter:
    • Performance is important; input lag is a bad thing.[/*]
    • It should be easy to have new systems tap into the input stream.[/*]
    • The system must be very flexible and capable of handling a wide variety of game situations.[/*]
    • Configurability (input mapping) is essential for modern games.[/*] Thankfully, we can hit all of these targets with fairly minimal effort. We will divide the system into three layers:
      1. Raw input gathering from the OS/etc.[/*]
      2. Input mapping and dispatch to the correct high-level handlers[/*]
      3. High level handler code[/*]
      The first layer we have already decided to gloss over; its specifics aren't terribly important. What matters is that you have a way to pump pure input data into the second layer, which is where most of the interesting stuff happens. Finally, the third layer will implement your specific game's responses to the input it receives.

      Contexts

      The central concept of this system is the input context. A context defines what inputs are available for the player at a given time. For instance, you may have a different context for when a game menu is open versus when the game is actually being played; or different modes might require different contexts. Think of games like Final Fantasy where you have a clear division between moving around the game world and combat, or the Battlefield series where you get a different set of controls when flying a helicopter versus when running around on the ground. Contexts consist of three different types of input:
      1. Actions[/*]
      2. States[/*]
      3. Ranges[/*]
      An action is a single-time thing, like casting a spell or opening a door; generally if the player just holds the button down, the action should only happen once, generally when the button is first pressed, or when it is finally released. "Key repeat" should not affect actions. States are similar, but designed for continuous activities, like running or shooting. A state is a simple binary flag: either the state is on, or it's off. When the state is active, the corresponding game action is performed; when it is not active, the action is not performed. Simple as that. Other good examples of states include things like scrolling through menus. Finally, a range is an input that can have a number value associated with it. For simplicity, we will assume that ranges can have any value; however, it is common to define them in normalized spans, e.g. 0 to 1, or -1 to 1. We'll see more about the specifics of range values later. Ranges are most useful for dealing with analog input, such as joysticks, analog controller thumbsticks, and mice.

      Input Mapping

      The next feature we'll look at is input mapping. Simply put, this is the process of going from a raw input datum to an action, state, or range. In terms of implementation, input mapping is very simple: each context defines an input map. For many games, this map can be as straightforward as a C++ map object (aka a dictionary or table in other languages). The goal is simply to take an identified type of hardware input and convert it to the final type of input. One twist here is that we might need to handle things like key-repeat, joysticks, and so on. It is especially important to have a mapping layer that can handle ranges intelligently, if we need normalized range values in the high-level game logic (and I strongly recommend using normalized values anywhere possible). So an input mapper is really a set of code that can convert raw input IDs to high-level context-dependent IDs, and optionally do some normalization for range values. Remember that we need to handle the situation where different contexts provide different available actions; this means that each context needs to have its own input map. There is a one-to-one relationship between contexts and input maps, so it makes sense to implement them as a single class or group of functions.

      Dispatching

      There are two basic options for dispatching input: callbacks, and polling. In the callback method, every time some input occurs, we call special functions which handle that input. In the polling method, code is responsible for asking the input management system each frame for what inputs are occurring, and then reacting accordingly. For this system, we will favor a callback-based approach. In some situations it may make more sense to use polling, but if you're writing game code for those scenarios, chances are you don't need any advice on how to build your input system The basic design looks like this:
      • Every frame, raw input is obtained from the OS/hardware[/*]
      • The currently active contexts are evaluated, and input mapping is performed[/*]
      • Once a list of actions, states, and ranges is obtained, we package this up into a special data structure and invoke the appropriate callbacks[/*] Note that we specifically might want to allow more than one context to be valid at once; this is often useful for cases where basic activities (running around) are always available to the player, but specific activities need to be restricted based on the current scenario (what weapons I'm carrying, perhaps). I recommend implementing this as a simple ordered list: each context in the list is given the raw input for the frame. If the context can validly map that raw input to an action, state, or range, it does so; otherwise, it passes on to the next context in the list. This can be done effectively using something like a Chain of Responsibility pattern. This allows us to prioritize certain contexts to make sure they always get first crack at mapping input, in case the same raw input might be valid in multiple active contexts. Generally, the more specific the context, the higher priority it should carry. The other half of this scenario is the callback system. Again there are several ways to approach this, but in my experience, the most powerful and flexible method is to simply register a set of general callbacks that are given input every frame (or whenever input is available). Again, a chain of responsibility works well here: certain callbacks might want first crack at handling the mapped input. This is again useful for special situations like debug modes or chat windows. Have the input mapper wrap up all of its mapped inputs into a simple data structure: one list of valid actions, one list of valid states, and one list of valid ranges and their current values. Then pass this data on to each callback in turn. If a callback handles a piece of input, it should generally remove it from the data structure so that further callbacks don't issue duplicate commands. (For instance, suppose the M key is handled by two registered callbacks; if both callbacks respond to the key, then two things will happen every time the player presses the M key! Oops! So if the first callback to handle the key "eats" it from the list, then we don't have to worry, and we can use a simple priority system to make sure that the most sensible callback gets dibs on the input.)

        High Level Handling

        Once the input is available, we simply need to act on it. For actions and states, this is just a matter of having our callbacks investigate the data list and take action appropriately. Ranges are similar but slightly more complex in that we have to turn the input value into something useful. For things like joysticks, this is easy: use a normalized -1 to 1 value and just multiply that by your sensitivity factor, and poof, you have a mapped range of input. (Try using a logistical S-curve or other interpolator for better results than just multiplication.) For mice, you can use the value to tell you how far to move the cursor/camera, again possibly by using a scaling factor for sensitivity purposes. The specifics of this third layer are really up to your game's design and your imagination.

        Putting Everything Together

        So, let's recap the basic flow of data through the system:
        1. The first layer gathers raw input data from the hardware, and optionally normalizes ranged inputs[/*]
        2. The second layer examines what game contexts are active, and maps the raw inputs into high-level actions, states, and ranges. These are then passed on to a series of callbacks[/*]
        3. The third layer receives the callbacks and processes the input in priority order, performing game activity as needed[/*]
        That's all there is to it!

        A Word on Data Driven Designs

        So far I've been vague as to how all this is actually coded. One option is certainly to hard-code everything: in context A, key Q corresponds to action 7, and so on. A far better option is to make everything data driven. In this approach, we write code once that can be used to handle any context and any input mapping scheme, and then feed it data from a simple file to tell it what contexts exist, and how the mappings work. The basic layout I typically use looks something like this:
        • rawinputconstants.h (a code file) specifies a series of ID codes, usually in an enumeration, corresponding to each raw input (from hardware) that we might handle. These are divided up into "buttons" and "axes." Buttons can map to states or actions, and axes always map to ranges.[/*]
        • inputconstants.h (a code file) specifies another set of ID codes, this time defining each action, state, and range available in the game.[/*]
        • contexts.xml (a data file) specifies each context in the game, and provides a list of what inputs are valid in each individual context.[/*]
        • inputmap.xml (a data file) carries one section per context. Each context section lists out what raw input IDs are mapped to what high-level action/state/range IDs. This file also holds sensitivity configurations for ranged inputs.[/*]
        • inputranges.xml (a data file) lists each range ID, its raw value range (say, -100 to 100), and how to map this onto a normalized internal value range (such as -1 to 1).[/*]
        • A code class called RangeConversions loads inputranges.xml and handles converting a raw value to a mapped value.[/*]
        • A code class called InputContext encapsulates all of the functionality of mapping a single context worth of inputs from raw to high-level IDs, including ranges. Sensitivity configurations are applied here. This class basically just exists to act on the data from inputmap.xml.[/*]
        • A code class called InputMapper encapsulates the process of holding a list of valid (active) InputContexts. Input is passed into this class from the first-layer code, and out into the third-layer code.[/*]
        • A code class (usually a POD struct in C++ versions of the system) called MappedInput holds a list of all the input mapped in the current frame, as covered above.[/*]
        • Each frame (or whenever input is available), the first layer of input code takes all of the available input and packs it into an InputMapper object. Once this is finished, it calls InputMapper.Dispatch() and the InputMapper then calls InputContext.MapInput() for each active context and input. Once the final list of mapped input is compiled into a MappedInput object, the MappedInput is passed into each registered callback, and the high-level game code gets a chance to react to the input.[/*] And there you have it! Complete, end-to-end input handling. The system is fast, easily extended to handle new game functionality, easily configurable, and simple to use. Go forth and code some games! If you'd like to see an example of how this works in action, check out the Input Mapping Demo at my Google Code repository.


      Report Article


    User Feedback


    I really like your approach here.   Does the input mapper always dispatch input or can it drop (eat?) events in the case of dealing with repeat keys?

    Share this comment


    Link to comment
    Share on other sites

    I really like this tutorial. Personally I prefer tutorials that provide code as an optional thing to look at but provide you the theory to work from so you can adapt the technique to any language you are working in.

     

    I was hoping to learn a new technique but as I looked through it I realized I am already doing this but I got to learn some new nomenclature. I still call that a win. It helps us as programmers communicate better amongst ourselves.

     

    Quick point though. Could you define a POD struct in your tutorial. I had to google it and once I read about it I realized that I knew what it was, just new terminology. But it is nice if you don't have to google everything.

    Share this comment


    Link to comment
    Share on other sites

    I really like your approach here.   Does the input mapper always dispatch input or can it drop (eat?) events in the case of dealing with repeat keys?

    Not sure if this is what you're asking, but you can use the return value of the callbacks to decide whether you should keep dispatching.

    Share this comment


    Link to comment
    Share on other sites


    Create an account or sign in to comment

    You need to be a member in order to leave a comment

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

  • Advertisement
  • Advertisement
  • intellogo.png

    Are you ready to promote your game?

    Submit your game for Intel® certification by December 21, 2018 and you could win big! 

    Click here to learn more.

  • Latest Featured Articles

  • Featured Blogs

  • Advertisement
  • Popular Now

  • Similar Content

    • By Genius-xi
      Hello everyone I would like to announce that I am working on a fighting game that will be released in 2019 as a full game.I would like to take some of your time and ask that you check it out and give me some feedback.If you like the game please follow me on https://web.facebook.com/PlayMechs/





    • By Entex
      printf("Hello %s", "follow game makers!");
      I'm about to make a game engine, and looking for advice!
      Background
      I have a game engine project I've been thinking about A LOT lately. I know it's gonna be hard, in fact the most challanging programming challange i can think of. With that said, I'm willing to put down the time and effort.
      I'm looking for some advice to keep the project up and running and I'm asking you, game makers! I've so much passion about this project. I've tried making a game engines before, but they have all halted. Some failed because of lack of focus, some failed because of unorganised structure, some failed because of lack of experiance, too big scope, unclear destination... you get the point.
      Now I'm taking a different approach. I'm doing the boring part, pre-planning, researching, etc. That's partly why I'm here, asking you. I'll lay out my plan for you guys. 
      Prerequisites
      I'm gonna try to keep technical terms to a minimum.
      So no spoiling what graphical API's or libraries I'm going to use, that's just asking for political warfare. This is more about the project management, avoiding pitfalls and such.
      The engine is gonna be a 2D engine. When i feel finished (probably in a couple of years) I will expand to 3D, but that's for another time.
      Because it's a game engine it should handle any type of 2D game, sidescrolling, top-down, hell even click-adventures!
      Disclaimer
      Sorry if my english is a bit wacky. Don't judge!
      The Game list(You'll read about it soon.) is just for experience purpose. I don't wanna fall in any kind of legal action because i stole some idea and thus only for personal use. My own ÜBER-awesome-final-game, if ever completed, will be released to the public.
      I first posted this on stackoverflow and was shutdown pretty hard because of too broad topic, understandable. Hoping this is the right forum, I'm just looking for some friendly advice. Kinda hard to get on this internet thingamabob...
      The Plan
      Start simple, work my way towards a more and more advanced game engine. In the end and my long term goal is my very own advanced 2D game(of course built on my engine). As a bonus, I might release the sourcecode of the game engine if I'm happy how it turned out.
      I believe in short term goal too keep my motivation and the feel of progress. But also have major goals to strive for, too always keep myself challanged, get bits and pieces to be proud of and most important have something to look forward to.
      Some of my older tries failed because i lost focus and stopped coding for a while. This time around i think it's best to atleast get a few lines of code every week. My "average goal" is to code for atleast a couple of hours every weekend. Just so i don't stop coding, the worst pitfall (i think).
      My strategy is a list of games to make on my journey. Trying to always have the list as a unit testing tool (Surely I'll have to redo older games when my engine gets up to speed). The list looks a bit like this. 
      Game list, Major hits
      1. Pong
      2. 1 Level platformer (Extremly restricted)
      3. Extended 1 level platformer with screenscrolling, jumping, etc.
      4. Same level with added Sprite/Animation.
      5. Same level with Goomba-like enemies and a finish line.
      6. Multiple levels!
      7. Super Major. A complete, short, single player mario-like game, with different enemies, levels and of course a boss.
      8. Top down 2D game. Bomberman-like
      9. Bomberman-like multiplayer
      10. ... This goes on for a while. Some smaller games, some Super Major

      Smaller technical milestones to start with (I know i said "no technical talk", but this is the extent of it)
      101. Graphical window (Ok, it's not a game but i have to start somewhere right?)
      102. Draw a triangle [Draw objects]
      103. Pong, very hardcoded (No help from the game engine to make collision or so)
      First game PONG
      201. Textures
      202. Simple physics (gravity, friction) collision
      203. Player Controller
      204. ...
      First Platformer: Have a 1 Level platformer were i can jump onto objects and stuff. No enemies, no screenscrolling. Just a super simple platformer.
      301. Animation
      302. Add Screenscrolling
      303. Static enemies
      304. Super simple AI. (Move toward player)
      305. ... Keep on adding so i can complete my list of games
      This is of course not the full list, i just don't want to TL;DR.. If you are still here, you are the GREATEST!
      Some concerns 

      The more I complete games on my list, the longer it will take to complete the next one. The more powerful function, the longer it will take. Multiplayer for instance, is no easy task...
      ADVICE
      Am i on the right track?
      Would you do something different?
      What do you think will work, what is risky?
      Have you tried making a game engine yourself? What kind of pitfalls did you encounter?
    • By Mitja Prelovsek
      Do you want to combine Unreal Engine's content with other content in Lightact media server? Sit back and watch this 4 min tutorial to learn how:

      Alpha Blending Unreal Engine 4 with Lightact: 
       

      View full story
    • By Mitja Prelovsek
      Do you want to combine Unreal Engine's content with other content in Lightact media server? Sit back and watch this 4 min tutorial to learn how:

      Alpha Blending Unreal Engine 4 with Lightact: 
       
    • By judeclarke
      For my test case I have a sphere sliding across a horizontal plane that will eventually come in contact with a vertical plane. I have figured out the correct way to deal with a moving sphere to plane for either sliding to resting contact or bouncing off of it. However, it was always with only one rigid body object to one static collision. Now there are two static collision objects. The way the static collisions are added are 1) Vertical plane 2) Horizontal plane. As a result it find the collision with the horizontal plane to slide across it (index 1) but it will find collision with the vertical plane (index 0) once it reaches it. With the logic I have below, it will never consider the collision with the vertical plane. How should I ensure that it will respond to collision with it?
       
      float fAccumulator = 0.0f; while(fAccumulator < fElapsedTime && mRigidBodyObjects.size() > 0) { F32 left_time = fElapsedTime - fAccumulator; for(unsigned int i = 0; i < mRigidBodyObjects.size(); ++i) { int j1 = -1; RigidBodyCollisionResult crFirstCollisionResult; crFirstCollisionResult.fCollisionTime = FLT_MAX; RigidBodyCollisionResult crCollisionResult; for(unsigned int j = 0; j < mStaticObjects.size(); ++j) { crCollisionResult = mRigidBodySolver.Collide(mRigidBodyObjects[i], mStaticObjects[j], left_time); if(crCollisionResult.enCollisionState == WILL_COLLIDE) { if(crCollisionResult.fCollisionTime <= crFirstCollisionResult.fCollisionTime) { crFirstCollisionResult = crCollisionResult; j1 = j; } } else if(crCollisionResult.enCollisionState == HAS_COLLISION || crCollisionResult.enCollisionState == RESTING_CONTACT) { crFirstCollisionResult = crCollisionResult; j1 = j; } } if(crCollisionResult.enCollisionState == WILL_COLLIDE || crCollisionResult.enCollisionState == NO_COLLISION) { mRigidBodyObjects[i]->ApplyGravity(); } if(j1 != -1) { if(crFirstCollisionResult.enCollisionState == WILL_COLLIDE && crFirstCollisionResult.fCollisionTime <= fElapsedTime) { mRigidBodyObjects[i]->Update(crFirstCollisionResult.fCollisionTime); mRigidBodySolver.HandleCollisionReponse(mRigidBodyObjects[i], mStaticObjects[j1], crFirstCollisionResult, crFirstCollisionResult.fCollisionTime); fAccumulator += crFirstCollisionResult.fCollisionTime; } else if(crFirstCollisionResult.enCollisionState == HAS_COLLISION || crFirstCollisionResult.enCollisionState == RESTING_CONTACT) { mRigidBodySolver.HandleCollisionReponse(mRigidBodyObjects[i], mStaticObjects[j1], crFirstCollisionResult, left_time); mRigidBodyObjects[i]->Update(left_time); fAccumulator += left_time; } else { mRigidBodySolver.HandleCollisionReponse(mRigidBodyObjects[i], mStaticObjects[j1], crFirstCollisionResult, left_time); mRigidBodyObjects[i]->Update(left_time); fAccumulator += left_time; } } else { mRigidBodyObjects[i]->Update(left_time); fAccumulator = fElapsedTime; } } }  
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!