0
\$\begingroup\$

I was just learning game programming concepts and was confused on what is the correct way to handle user inputs.

I am used to javascript, so all the below snippets are in js but I assume the concepts should be the same across most of the programming languages.

Lets say our game is a FPS and the playing can use the keyboard arrow keys to move his character around.

This is the sample code that I am playing around with

const FPS = 60
let keyMap: {[key in keyCode]: boolean} = {}
let game
const downListener = (event: KeyboardEvent, keyMap) => {
    event.preventDefault()
    keyMap[event.key] = true
    return keyMap
  }

const upListener = (event: KeyboardEvent, keyMap) => {
    event.preventDefault()
    keyMap[event.key] = false
    return keyMap
}

// update
 const update = (game) => {
   // Handle user input
   if (keyMap["ArrowRight"]) {
     /* Move player right*/
   }
   else if (keyMap["ArrowLeft"]) {
     /* Move player left */
   }
   // Other controls ...

   return game
 }
 
// Game loop
const gameLoop = () => {
    game = update(game)
    game = renderGameScene(game)
}
setTimeout(() => {
    gameLoop()
    setTimeout(() => gameLoop(), 1000 / FPS)
   }, 1000 / FPS)

Let's say the game loop has some very intensive code that takes say 10 seconds to execute (extreme cases), So my game loop will be called every 10 seconds.
And the user pressed arrowRight key at t=2, t=3, t=6. But since my game loop executes every 10 seconds and I only store the last status (keyPress/notPress) of key, none of his inputs will be taken into consideration when my gameLoop actually executes. That is, the gameLoop will only consider the input that was pressed exactly at the time it starts executing.

But what I notice in big games (Counter strike for example) is that if my game is lagging and I press arrowRight 4 times, then in the next render my character abruptly moves 4 units right but in my code above my inputs won't be processed at all. How would I solve this issue in a scalable way? How do big games usually do it?

EDIT: 10 seconds is just an extreme number but I beleieve the same issue will arise when the game loop takes 200-300 ms to execute whereas the user input is captured very quickly

\$\endgroup\$
2
  • \$\begingroup\$ I think your example may be a bit too extreme to get good answers for the kind of game you have in mind. An FPS where a frame sometimes takes 10 seconds would be unplayable. So, a twitchy action game like that will use different techniques (baking-in the assumption that you'll almost always be maintaining 30 FPS or better), than a game that can tolerate/must accommodate longer frame/input latencies (which might use a more involved input queueing system that would be unnecessary complexity for a shooter). \$\endgroup\$
    – DMGregory
    Commented Jan 23, 2022 at 17:49
  • \$\begingroup\$ Ok my bad 10 sec may be a bit extreme but even with a 200-300 ms duration of the game loop will also cause the same issue just not as frequent. The main question that I am trying to ask is how to manage the different rates at which my game loop executes (slow) and the rate at which the input is being captured (fast) because my current data structure to hold the inputs is not suitable to handle lags \$\endgroup\$ Commented Jan 23, 2022 at 18:13

1 Answer 1

1
\$\begingroup\$

There are (at least) two different kinds of user input that we can consider. (I don't know what these are most commonly called — I'm making up terms right now, not using standard ones.)

  • Continuing inputs: like WASD movement controls in a first-person game, or anything from a controller analog stick. These cause something to continue to happen as long as the key/button/stick is held.

  • Command inputs: like taking an action ("shoot gun") or moving one tile on a grid.

The code you have is appropriate for continuing events. In most cases, in a game with free movement that is typically animating smoothly, if the game has a “lag spike” of 10 seconds, the player won't want to have 10 seconds of movement off in some constant direction — that's more likely to send them into a hazard than standing still is. Therefore, the game does not implement arbitrary amounts of motion — it has some cap on how much game time effectively passes (at least for your character, in a multiplayer game) after the hiccup. The way this usually works is that when the game loop checks the real-time clock to decide how much game time to simulate, if more than some maximum amount of time has passed, it pretends that only that amount of time has passed.

On the other hand, if you have a game with movement on a grid, it would probably be more appropriate for each keypress to cause exactly one tile of movement. In this case, the simplest possible approach is to have your key-down listener directly modify the game state. Games where things can only happen after a delay might need some amount of “input queueing” as DMGregory put it, to execute actions after they're allowed to execute. (Generally, for very fast actions, you don't want to ignore user input just because the game wasn't quite done with the previous action. Hence, store the events you receive until they can be processed, perhaps with some time or count limit.)

In a FPS, an example of a command input might be “click to shoot your gun”. In this case, you don't want to not shoot just because a small hiccup caused the mouse-down and mouse-up events to happen in the same frame. So, instead of the game logic checking “is the moue button currently down?”, it's better for your mouse-down event handler to set a flag “should shoot now”, which is cleared when the game logic actually creates the bullet and changes the gun's state and so on. The same principle applies to keyboard keys that take discrete actions, like jumping.

In user interfaces (buttons, menus, etc), either of the last two approaches can be appropriate. It's important that you pick one of the ones that won't discard inputs just because their timing seemed bad, though — imagine if the user opens your game, and the frame rate is terrible, and they need to navigate to the graphics options to reduce some settings, while rendering is still slow. You want to make sure your UI elements react to the inputs as well as possible, without requiring them to do things like "make sure to hold the mouse button down for half a second and not move off this button, then let go and wait another half second without moving the cursor" (actual sort of experience I've had).


So: there's no one way to process input that works for all kinds of games or gameplay actions. Instead, think about what should happen, and use an algorithm that produces that result — one of the common ones I've described above, or whatever you can think of that produces good results. Consider adding a deliberate frame slowdown option to test how your input feels in those conditions.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .