How are you encapsulating User-Inputs

Started by
18 comments, last by Angelic Ice 7 years, 3 months ago

Hello forum!

I thought about on how to poll and control user-inputs (e.g. key pressing, window actions, mouse-inputs ...).

At the moment, I have one input-module that just polls for all possible inputs that will ever be used in the game.

Once one of these inputs is committed, I trigger an event (event-listener-system) and everyone who needs to know is informed.

I felt I could reach a high encapsulation with this, but I'm also worried about the longer becoming polling in the future.

There are of course moments, when I do not want to allow scrolling or zooming, example would be while being in the settings.

But the polling still happens.

Back then, I used to poll within game-states. Every state only polling for possible inputs (e.g. menu-state polls for a mouse click but not for a zoom-key-instruction).

What key is being assigned to what is being described in a file that gets loaded (e.g. "W"-key is an input assigned to "walk forward").

I was curious on how you tackle this, as high encapsulation is an important goal for me and I would like to see alternative implementation possibilities.

I'm using SFML, if you have any special advice for that.

Thanks for taking your time to read through my post : )

Advertisement

So far, I don't. I call the correct sub-system that knows what to do with the input. That might change if I ever add user-configurable inputs though :)

I don't think you need to be worried about execution time. User input typically happens never, in terms of CPU ticks. You can, say, enter 100 inputs / second (big exaggeration, less than a handful is more likely), a CPU runs more than 1,000,000,000 ticks in that time. (ie 1GHz, which is a slow processor.) Now while you have to do some other stuff too in those ticks, you'll have to spend an enormous amount of time in user input handling to make it anywhere near significant.

Hi

I based my input system on this article: https://www.gamedev.net/blog/355/entry-2250186-designing-a-robust-input-handling-system-for-games/

Hope it helps!

I only check whats required in a given function. In Caveman 3.0, that works out to FPS input (typical shooter input), action mode input (gamespeed, and cancel, like an acition in the sims), and anytime input (stats, in game menu, world and local maps, etc). everything goes though an input mapper: UI stuff like menus and dialog boxes only check for a mouse click or string input (in the case of getstring() ).

so for example:

if control_pressed(IN_USE) // they pressed the interact / use key in FPS mode ("E" by default, like skyrim).

pretty much all input is processed immediately. so for example do_fps_input gets the mouse position and updates the player's yr right then. nothing really requires deferred processing, so there's no need for a messaging system or storing input data for deferred processing later - its just overkill for this situation, more work to code, runs slower.. if they hit esc, it just calls ingame_menu(). if they press attack, it calls the try_attack routine.

it designed with simplicity and minimum lag in mind. encapsulation is not even a consideration.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

I've used this approach very successfully in the past, and add my endorsement, It clearly and accurately separates out the key responsibilities of an input system, while providing for enough flexibility in implementation to create something catered to your specific needs.

It's possible, depending on how creative you get with handling input "contexts" and mapping, to end up with an input system implementation you really do only have to write once, and can utilize with all sorts of game types.

Thinking about this question a bit more, later it occurred to me that I already encapsulate everything. I've been doing it for so long, i don't even think about it.

For example, my basic mouse input routine in my in-house gamedev library is getmouse(&x,&y,&b) i've been using it since before mice had three buttons. it was originally driven off of dos interrupt 31H as i recall. it now runs the windows message pump and tracks mouse state changes, reporting back the current potiion and the pressed state of buttons 1 and 2. so getmouse() and the newer getmouse2(&x,&y,&b1,&b2,&b3) and mousewheel() routines encapsultate the mouse input of the underlying OS. same idea for keypressed(VK_CODE). its a wrapper that encapsulates the functionality of GetAsyncKeyState(). back in the DOS days, it took an unsigned char instead of a VK_CODE, and hooked the keybaord hardware interrupt to do its own hardware interrupt driven keyboard input..There also used to be an analog joystick API as well.

so input is encapsulated via:

getmouse()

getmouse2()

mousewheel()

keypressed()

i don't really require joystick or game controller input for anything i'm working on at the moment.

the next input layer up from keypressed,getmouse, getmouse2, and mousewheel is control_pressed(IN_FORWARD), which is part of the input mapper API. it has a bunch of predefined inputs like IN_FORWARD IN_ATTACK, IN_BLOCK, IN_USE, IN_JUMP, etc. these are mapped to keys or mouse buttons. when you call control_pressed(IN_WHATEVER), it in turn calls keypressed, getmouse, getmouse2, or mousewheeel, depending on what input that "move" is mapped to.

the basic GUI components - getstring, [popup] menu, and [multi-iine] message - just call keypressed or getmouse.

the first thing i do when i start using a new library is write a wrapper api that encapsulates just the functionality i need. as small and as simple as possible. so my keybpoard APi is just keypressed(), and my mouse API is covered with just getmouise2 and mousewheeel.

as an example, here's my wrapper API for xaudio2. i'm quite pleased, it came out nice and simple. but then again audio API's usually do.

void Zunpause_voice(int i); // i is voice #
void Zpause_voice(int i); // i is voice #
void Zshutdown_audio();
int Zloopwav(int i); // returns: Zvoice # on success. -1=create source voice error. -2=submit source buffer error. -3=start voice error.
int Zvoice_is_playing(int i); // returns: 0=no. 1=yes.
void Zstop_voice(int i); // i is voice #
int Zplaywav(int i); // returns: Zvoice # on success. -1=create source voice error. -2=submit source buffer error. -3=start voice error.
int Zloadwav(int i,char *s); // loads wav into slot i. s is filename. returns: 0=success. 1=open wav error. 2=read wav error.
int Zinit_audio(); // returns: 0=success. 1=Xaudio2 create error. 2=create master voice error.
void Zloadwavs(char *s2); // loads all wavs listed in the file wavs.dat in the current folder.
void Zdestroy_finished_voices();

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

My input system is at the game level and not at the gamestate level.

I am using sfml so the window causes events to fire when input is provided (more than likely using polling, but I haven't looked into the code)

Gamestates have a boolean value, AcceptInput and AcceptGameInput. AcceptGameInput is always true if AcceptInput is true, but AcceptGameInput can be false without AcceptInput being false. If AcceptInput is false than no is passed to any systems... if AcceptGameInput is false than the GUI/Pause etc will accept input but player action inputs aren't processessed.

I created a map from Key/Modifiers hash to messages so when a key is pressed, it looks to see if there is a message attached to that key and if there is it triggers a message to the messaging queue. The message does not contain any information about the input (such a which key it was or which modifiers) but it contains information about the action. So when I hit "W" it sends a "Move Forward" Message...

Each gamestate can create a unique keyboard map... which is both a blessing and a curse, because now II have to put "W" "Move Forward" in every gamestate... but it does allow some screens to have specific input/keys/actions that other game states don't... such as pressing "L" on the overworld map to "Land" your "Airship".

The Mouse gets a bit weird... when the mouse is clicked, it first tests if it is over any GUI Element... if it is then it triggers the OnClick Event for the GUI Element the mouse is hoving over. The OnClick Event can then dispatch a message to initiate an action the same way pressing a key will. Sometimes it gets a touch convulted... such as when a player presses "1" instead of trigger the action associated with the first actionbar slot it triggers a message that causes a "fake" click on the actionbar which then dispatches the actual input action message.

Then, individual systems look for the action message. I like this because with this configuration, only the GUI and the PlayerInput system have to deal with input directly.

It has been awhile since I really researched input... but I am fairly certain that you shouldn't really worry too much about the overhead of polling for input... this should be a fairly constant cost... and as far as I am aware, even if you rely on events from a framework it is highly likely that the framework is using polling behind the events.

The way I like to handle input is something like this:

  • Low-level input system polls or queries keyboards, mice, gamepads, and other devices, and observes the OS message queue if necessary and does a few things:
    • Poll devices once a frame, and store pressed/unpressed values for buttons, store values for continuous inputs
    • Generate events for state changes (mouse movements, button down/up) and send to the app's/game's message queue
    • Wrap the values for alternative uses if necessary (e.g. a trigger may be an analogue value, but you might just want to query it for off/on as well)
  • Game state code receives input events, and knows how to handle them:
    • This is often a good time to map the physical event to a logical input, based on your defaults and/or player bindings. (e.g. "KEY_SPACE" to "Player1_Jump", "Button X" to "Primary Select", etc.)*
    • Pass them to the UI to see if that needs the first shot at handling them (e.g. clicking on an icon, changing a menu selection, typing into a focused text-box). The UI will usually have its own hierarchy of widgets that may or may not handle the event.
    • If the UI didn't consume the event, the game state will get a chance to handle it, doing something like firing a weapon, selecting something from the inventory, whatever.
    • If the game state didn't handle the event, pass it up to the application in case it's global functionality (e.g. a debug key to take a screenshot)
    • I don't like trying to handle instantaneous actions by using functions like "ButtonPressedThisFrame" (e.g. Unity's GetButtonDown); that puts the burden of checking for state-changes into your game code. I'm happier having the input system transform these thing into events which I can easily collect and log, whether they're handled or not.
    • And I don't try and duplicate the up/down/pressed/released state in game code, because it breaks horribly when a button is held down during a transition or a UI popup or whatever. If you work from the events, passing them on if they're not handled, you avoid most bugs like that.
  • Game states also handle continuous data such as joystick/thumbstick positions or triggers, in the Update function or equivalent
    • Again, there will probably be a mapping from physical (e.g. Left Thumbstick Vertical Axis, Right Trigger) to logical (e.g. Acceleration, Walk Speed)
    • Your input library will ideally need to be set up to return values from 0 to 1 or -1 to 1, and sometimes to invert the value
    • That's about it - you rarely need to worry about these inputs being used in the wrong context (e.g. trying to steer the car while the Pause Menu is up has no effect, because the game state is paused and nothing is polling the Steering Axis anyway)

But all of the above is subject to change so that I make the best use of what the engine or OS provides. For example, Unity provides a lot of input mapping code so it makes sense to use it. And SFML tracks key-up and key-down events so you don't need to synthesise those, and can instead work directly with what that library provides.

I find it's essential to have a system that permits the UI (or the game state, or whatever) to say "I have handled this event" so that you don't get get "clicking through the UI" bugs or similar. It's also handy to have a system that allows you to ignore that if necessary - e.g. to explicitly handle an event twice - for logging or for other esoteric uses, although often you can just wedge that sort of functionality in at the event loop level.

*The best way to handle input mapping can be a bit fuzzy - if you do it at the app level then that makes it awkward when you have different scenes that need to handle input differently. It is sometimes useful to have a hierarchy or stack of maps, where there are default maps for the whole game - e.g. certain gamepad buttons are almost always mapped to 'Select' and 'Back' - and specific override maps in certain game states - e.g. where those buttons might operate weapons or character behaviour. You might combine this with a stack of input event handlers, since the 2 are likely to be closely entwined.

The best way to handle input mapping can be a bit fuzzy - if you do it at the app level then that makes it awkward when you have different scenes that need to handle input differently. It is sometimes useful to have a hierarchy or stack of maps, where there are default maps for the whole game - e.g. certain gamepad buttons are almost always mapped to 'Select' and 'Back' - and specific override maps in certain game states - e.g. where those buttons might operate weapons or character behaviour. You might combine this with a stack of input event handlers, since the 2 are likely to be closely entwined.

I've implemented this before as a prioritized list of input contexts/maps. The baseline context that is always active (camera controls, pause, etc.) has the lowest priority, and higher priority contexts can be activated or deactivated by the game depending on the current game state. If a higher priority context doesn't have a physical event in its map, then it checks the next lowest priority context, until it's either caught by the baseline context or simply ignored. I've also found it helpful in certain special-case situations to allow a physical input event to generate multiple logical events, by allowing the pass-through behavior to be configured on a per-mapping basis.

However input mapping is by far the trickiest component to implement. What I've found is that there can be a lot of gray area with how certain input behaviors or situations are handled, and how you go about implementing everything will depend heavily on the type of game(s) you're writing, what kind of functionality and interface you want to provide to the game layer, and your own personal preference (or that of the designers and players). There's very little in terms of "standard" behavior, and the rest comes down to what works best for you and your game. That being said, you don't want to be rewriting your input layer for every game, so it helps to identify a decent input model up-front that you can merely tweak with additional functionality as games require.

The best way to handle input mapping can be a bit fuzzy - if you do it at the app level then that makes it awkward when you have different scenes that need to handle input differently.

a good point. Caveman 3.0 operates in two modes or game states: fps mode, and "The sims" action mode. each has its own unique input mappings. for example, in FPS mode, the number keys are user defined weapon selection hotkeys - like in all shooters. while in action mode, they control accelerated time, while your caveman completes an action, like cooking dinner in the sims, or chopping wood or mining ore in skyrim. that's why i split the input up into three parts, FPS, action mode, and both (in-game menu, stats, skills, maps, etc).

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

This topic is closed to new replies.

Advertisement