Dealing With Designer Input Latency
Input responsiveness is one of the most important aspects of any game. Often overlooked in favor of more glamorous features, a snappy input system is one of those things that the average gamer doesn’t realize they need until they pick up a game that fails to provide it.
When you bring up the subject of input lag most people think in terms of hardware and software processing latency – that is, the time it takes to translate a physical button press into something the game code can actually react to. For most of us the hardware side is out of our control, and on the software processing side the biggest factor is often pure update rate. You can get a lot more granularity out of 60hz than 30hz, which is why many games put their input handles on a separate thread. It’s important to keep both of these latency sources in mind, but what about the design side of latency? The decisions we make when creating our game mechanics can have a significant impact on the responsiveness of our controls and it’s critical that we think carefully about our decisions in this area.
For the sake of simplicity, in this article we’ll be dealing specifically with standard console game pad buttons. Motion controls, touch screens and joysticks have their own unique challenges that are outside the scope of our discussion here. Some basic, standard state terms for buttons are:
- Button Up (state) – the button is not being held down. Typically the neutral state.
- Button Down (state) – the state of a button when it’s being held down.
- Button Pressed (event) – the act of pushing the button down. More specifically, the button was in the up state on the previous update and is in the down state on this update.
- Button Released (event) – the act of releasing a button. The button was in the down state on the previous update and is in the up state on this update.
Using these 2 events and 2 states you can define most of the core button actions. We’ll leave aside things like analog triggers, as they can be hammered into this digital framework if need be.
How do we create designed latency?
Ideally, the most responsive way to hook into our game pad’s button is to utilize the button pressed event. This event represents our earliest opportunity to handle an input, and as a result we’re guaranteed that all of our latency is on the hardware and processing side of the equation. For the purposes of this article we can consider this event to have zero latency.
Of course, there are two common situations that keep us from just limiting ourselves to button pressed events and calling it a day: player state changes and button holds. Pretty much every game has the former and many games (though not all) have the second as well. The way in which we design latency into these two systems is quite different, and each one requires a different set of tools to handle smoothly.
Dealing with holds.
To begin, let’s take a look at Halo’s famous Plasma Pistol. The weapon has two methods of firing: a standard semi-automatic projectile that fires if you repeatedly pull the trigger and a charged blast that fires if you hold the trigger down. This combination of inputs means that we can’t rely on the button pressed event to make a decision on what to do – we don’t yet know if the player wants to fire a single shot or start charging for the blast attack. Instead, we need to use the button released event, something that introduces a finite amount of latency into the system. No matter what, the player can’t both press and release the button in the same update cycle. At best, we’re dealing with a minimum of one update cycle’s worth of added lag and in reality we’re almost certainly talking about at least 100 milliseconds before the average player can get his finger back off the button.
What’s more, it’s often not viable to choose the smallest possible window to detect a hold state. In the case of a simple firearm it’s probably safe to hew close to the minimum, but if the accuracy of our hold time is important (in, say, a variable length jump) we need to be a little bit more generous. If we want to be casual friendly, the situation gets worse – it’s not unreasonable to expect some inputs to need as much as 200 milliseconds for a casual gamer to be able to intentionally choose a hold instead of a tap.
Hide your shame.
We’ve got a few different ways to contain the damage. The first option is to try and hide it, and this is a technique that applies particularly well to fighting games. In Namco’s popular Soul Calibur series, character attacks are never instantaneous – there’s always some amount of windup, even if it’s very short, so that opposing players have a small window in which to react. As a result, it’s possible to hide the input latency of the many charge attacks inside these windup animations. If the player has released the button by the time the attack window arrives, the move completes its animation. If the player is still holding the button, the move transitions into a hold state, usually a slow-motion animation or a pretty particle effect.
This technique is especially powerful because it turns a weakness (needing to wait on additional input data) into a strength. Once the player enters the hold state, you can allow them to vary the duration of the hold or cancel the hold into a different move altogether to create interesting mixups and mind games.
Fake it ‘til you make it.
Another option is to just start doing whatever it is you’d do if the player decided to tap the button and then make adjustments later if it’s determined that, instead, he kept the button down. This is a technique that’s often applied to lofted jumping in platforming games: when the button is pressed the character immediately begins a standard jump, and then a few hundred milliseconds later that jump gets a boost if the button is still held down. The result is a jump that feels very responsive when tapping but still allows for some in-flight adjustment of the distance and height.
This works well enough, but its applicability depends somewhat on the type of game you are. In a more cartoony universe like Super Mario Bros. or Ratchet and Clank, this sort of technique blends in pretty well. In a more realistic setting, such as Red Dead Redemption or Uncharted, this sort of floaty solution sticks out a lot more. As with most solutions in game design, your mileage may vary.
Utilize your latent psychic powers.
A third approach is to try to mitigate the impact of the latency by building a more robust set of detection rules. As a simple example, if your character’s jump action fires on the button released event but the player doesn’t get his thumb off of the button before he runs off a ledge, you could easily have the character automatically jump instead of letting him fall to his death. How generous you decide to be is a function of both how friendly you want your controls to feel and how confident you are that your predictions are correct.
Prediction like this is not without risks. Particularly if you have overloaded inputs – such as a button that doubles as reload and pick up weapon – you run the risk of guessing wrong and doing something the player doesn’t expect. The more complex and context sensitive your input is the more risk involved in trying to guess what the player wants to do. Generally speaking you can just avoid putting hold states on buttons with widely varying functions but, in case you do, it’s something to keep in mind.
A note on double tap inputs.
I didn’t include this in the description of a “hold” state because they’re not especially common, however their impact on latency is essentially same and, as a result, you can deal with them using the same techniques. For example, in Crysis 2 you switch to grenades by tapping the weapon switch button twice in rapid succession. Since both a single tap and a double tap result in a weapon switch action (and thus it’s just a question of which weapon you end up swapping to) this problem is easily handled via the “fake it ‘til you make it” technique.
Dealing with state changes.
When it comes to input latency, the only state changes we care about are the ones that require us to respond in different ways to player input. For example, the player might be able to press the “crouch” button while running or standing idle but not while soaring through the air (due to a jump or getting thrown by an explosion). Unfortunately, if what the player actually wanted to do was start crouching right when he hit the ground, this intent is lost unless we decide to keep the input around. Ignored inputs are often very frustrating for casual players, especially if they were ignored during a finite-duration state (again, like a jump) toward the very end of that state’s window.
Although not latency in the traditional sense, ignoring inputs feels very similar. The player wanted to do something, they asked the game to do it in a way consistent with their expectations, and the game did not react in a timely manner. There are two ways of dealing with this problem: buffering and interruption.
Buffering is helpful (unless you’re RealPlayer.)
Buffering input is pretty straightforward: we record incoming inputs but don’t respond immediately. Consider a standard player jump – unless your game has a double jump action, it just doesn’t make sense to let the player jump again before he reaches the ground. Of course, if he’s trying to do something particularly tricky (like a rapid series of timed hops) ignoring a jump request could end up ruining his day. Instead, we can buffer that input and then act on it the moment the player is back on the ground, maintaining the player’s intent without changing our mechanics to allow for a double jump.
Actually recording the input is also simple: we remember a certain number of inputs for a defined window of time. Your buffer can be as large or small as your problem requires. Fighting games like Street Fighter and Tekken have buffer windows that can hold a half dozen or more inputs, and most action games will buffer at least one input when appropriate. How long you keep those inputs around also varies. In the case of short, finite duration states it’s usually best to keep them for the entire state. If the state is particularly lengthy, however, it’s possible that the player will “forget” about that input by the time you get around to processing it. For this reason, time sensitive inputs are typically dropped after a designated duration.
Despite the fact that buffered inputs are far friendlier, by their very nature they introduce a source of latency. Buffering is better than ignoring, but we’re still acting on these inputs well after they were actually received, and if your buffer window is large the character may end up responding to inputs in a way that feels unpredictable. In addition, unless you have some sort of visual reaction to a buffered input it’s likely that more casual players won’t even be aware that it’s happening (which is why intentional buffering is an advanced technique in fighting games). Since it’s often not possible to provide this feedback in a way that doesn’t feel artificial it’s best to keep your input window small.
As was mentioned earlier, great care must be taken when applying buffering to highly time sensitive inputs. For example, if your player was firing his weapon and it ends up auto-reloading when he runs out of ammo, he might decide to hit the weapon switch button to use a different one. The player’s intent is to swap to a weapon that has ammo, but if your game buffers that input the result will be a frustrating mess: the weapon finishes reloading, which is annoying enough since no firing could occur during that time, and then the moment the weapon becomes usable again it starts playing a weapon switch animation that further delays the desired shooting!
Of course, all inputs are time sensitive to some degree, but recognizing the extent to which this is true in each case is critical for determining the correct way to handle them. When dealing with time sensitive inputs, the more appropriate response is to allow a state to be interrupted.
I’m really happy for you, and I’mma let you finish, but…
Continuing with our reloading example from before, if the weapon switch input was acted on immediately (instead of being buffered) then the reload state would end and the player would switch to his usable weapon. What happens in this case is up to your design discretion – in most FPS games, interrupting a reload means that the weapon remains empty when you switch back to it (to avoid exploits). It’s probably more newbie friendly to let the weapon reload anyway, and which one you choose depends entirely on your audience and the particular actions in question.
State interruptions are not an all or nothing matter – there’s no particular reason why you have to allow interruptions at every point in the state, and defining interruption windows can create some very compelling mechanics. For example, in Soul Calibur many attacks can be cancelled pre-hit – that is, their animations can be interrupted at any time prior to actually doing damage. Advanced players often use this mechanic to bait their opponents into a disadvantaged, punishable situation by starting an attack and then cutting if off to create a fake. In Guilty Gear, some moves can have their recovery (post-hit) animation cancelled to allow for guaranteed combos, and doing so is so strong that it actually requires the use of a finite player resource (the Tension bar).
I could easily write an entire article just on the topic of state interruptions and buffering, but I don’t need to because Eric Williams – a designer on the first God of War title – already did! His excellent article Combat Cancelled is required reading for all game designers, and I encourage you to absorb its deep wisdom if you haven’t already.
They go together like lamb and tuna fish.
Both buffering and interruption have their uses, and they’re most effective when used for their respective strengths. Buffering works well if you have a more complicated input possibility space – like most fighting games – or if you have a state where interruption simply doesn’t make sense (such as jumping during a jump). Interruption works well for time sensitive inputs and in situations where waiting for a buffered input to fire might result in unpredictable or undesirable behavior.
Further, there’s no reason that you can’t use both techniques simultaneously. Why not have a state with a defined interruption window and allow the actual input that causes the cancel to be buffered prior to the window? You get the best of both worlds: the latency minimizing aspects of an interrupt with the granular designer control of a buffer!
Wrapping it up.
As we discovered in the introduction, having your inputs react on the button pressed event is always the most responsive option. The addition of hold inputs forces you to react on button released events instead, and as a result introduces latency. When pondering adding hold inputs to your game, be sure to ask yourself if the benefit (another input) outweighs the cost (designed lag).
Even when dealing with normal button presses, player states can muck with your ability to respond immediately – adding buffering and interruptions whenever possible goes a long way toward alleviating this pain. Buffering input should be the rule, not the exception, and if you choose to ignore player inputs it should be a careful and considered design decision.
I didn’t mention it before, but even if true buffering isn’t an option, you should always check your inputs when exiting a state; in that case, we’re looking for the button down state as opposed to the button pressed event. This should go without saying, which is why I didn’t dwell on the point previously, but I wouldn’t be doing my job if I didn’t at least point it out.
Finally, it all comes down to feedback. Even when you can’t respond immediately to an input, it’s critical that you do something to let your player know what’s happening. Hiding your latency behind windup animations, particle effects or sounds can help the player feel like something is happening even if the important part of the action hasn’t yet occurred.