Input Buffering, Action Canceling, and also Forbidden Knowledge

Yosi Spring
7 min readJul 17, 2022

--

(Reposted from Twitter because Twitter threads are bad)

Having responsive controls is very important to make games feel fair and enjoyable to play. Input delay due to hardware is unfortunately a large factor, but there are plenty of tricks you can program into the game to make it feel more responsive.

Input buffering is storing players’ inputs for multiple frames. Usually this is done to make performing a sequence of actions easier, like using an attack and then jumping. If the attack takes 20 frames, and the player pressed Jump on frame 19, they should still get to jump.

In PFE (Platform Fighter Engine), you can see the input buffer values in debug mode by pressing F3. The colored bars represent the number of frames since the input was pressed (so when the bars are at the top it means the input was pressed 0 frames ago)

Every time the player attacks, the colored bar in the top corner representing the Attack input shrinks to 0 pixels tall, before slowly growing back to the normal size (the maximum buffer length in frames).

The “input_pressed” script in PFE has an argument for how many frames ago the input could have happened, so you can choose exactly how long the input buffer is for every piece of code.

Input buffering isn’t a perfect solution. If the buffer is too large (Smash Ultimate), it can feel like the game is using inputs that you no longer wanted to happen. For example, if you press Shield, then realize your opponent isn’t attacking and press Attack instead, but the game makes you airdodge because it’s still buffered. This can be prevented by having a smaller input buffer, or giving priority to newer inputs.

Another issue with input buffering is the game treating 1 input as multiple inputs. If a Jump input is stored for 6 frames, but players can double jump 5 frames after leaving the ground, then every Jump input will trigger a grounded jump into a double jump. This can be fixed by allowing the input buffer to be “cleared” (directly setting the value to a higher number). You could have the jump code set the Jump input to be “100 frames ago”, so then it can’t possibly trigger anything else. PFE does this automatically for all inputs.

While input buffering quite useful, some games can get away with something even simpler — hold inputs. If you allow the player to jump as long as the Jump button is held down, they can start holding the button any time and the jump will come out on the first possible frame. Whether hold inputs feel good to use or not depends on the game. You may need to put a cap on how long an input can be held. You can also have a combination of input buffering and hold inputs.

Now, both input buffering and hold inputs cover the times when players press buttons too early. What happens if they press the button too late? This is where action canceling comes in. “Coyote time” is a common example of action canceling found in many platformers. If the player runs off an edge, they enter the “aerial” state in which they shouldn’t be able to jump. But the game allows you to cancel the “aerial” state with a jump for the first few frames.

Action canceling is most useful for games that require players to press multiple inputs at the same time to do something. In PFE, you need to press Jump, Shield, and a direction on the left stick in order to wavedash. If you press Shield and flick the left stick before pressing Jump, you end up rolling. So I put a small cancel window at the start of rolling that starts a wavedash if the player presses Jump:

The player shields, then starts to roll for 1 frame before canceling with a wavedash.
The player shields, then starts to roll for 1 frame before canceling with a wavedash

Some games have even larger windows for canceling, like being able to jump and dodge out of attacks in Breath of the Wild. And some games make action canceling a core mechanic (though it’s less about making the game feel responsive at that point). Action canceling can even be more complex than just checking inputs — in PFE and Rivals of Aether, you can waveland onto platforms even if you’re slightly higher or lower than the platform:

The player airdodges horizontally onto platforms and wavelands successfully, even though their Y coordinate is slightly too high or too low.

Of course, action canceling is more dangerous to implement than input buffering, because it opens more ways for players to break the game. This is primarily a concern for competitive multiplayer games, but it can cause issues in singleplayer games too if you’re not careful.

Input buffering, hold inputs, and action canceling are all great tools for making games more responsive. But there’s still the issue of hardware lag that is impossible to solve. That is, unless you go into the dark magic of Negative Input Delay.

Image of Nagatoro smirking, because it’s funny
(Disclaimer: I don’t think negative input delay is worth implementing in 99% of games and isn’t very useful even when properly implemented, but it’s still fun to talk about)

Negative Input Delay is very closely related to rollback netcode. In fact, PFE uses the same library of functions for online matches, and local matches with negative input delay. (You could even get negative input delay to work online in PFE, but that’s a bad idea). The basic premise is that you predict players’ inputs a certain number of frames ahead of the game, store the current game state in a list, then calculate and render the future frame. When a player presses a button, the game checks if it matches the prediction. If it does, nothing happens. If it doesn’t, the game loads the old game state from the list, overwrites the incorrect input, and then makes a new prediction for the future frames again.

This means that the game is always displaying frames AHEAD of the “correct” game state. It’s always showing players a possible future, not the present. This makes it feel like there is less input delay, because there is a disconnect between the true game logic and the display.

For example, on -2 delay, if you press Jump, the game will add your input to the “correct” frame, and then predict 2 frames forward and render that future frame. So it looks like you skipped 2 frames into your jump arc, as if your input traveled back in time! The longer actions take, the higher chance that the prediction will be correct. If you start an attack that takes a whole second to complete, then of course the prediction 2 frames into the future will be correct, there’s nothing else you could have done in the 2 frames.

Negative input delay has the same issue as rollback though — teleporting. This is whenever the predicted inputs are different enough from the correct inputs that there is a visible difference between the displayed frames back-to-back. Depending on the game, this can get completely out of hand, even if the prediction is only a few frames off:

Ranno from Rivals of Aether teleporting erratically around the screen.
If you’ve played against a high-ping Ranno, you might have seen this

The key difference between negative input delay and standard rollback is that teleports only happen to the remote player on rollback. Using negative input delay on a local game makes every player in the game teleport:

Scalar from Platform Fighter Engine teleporting erratically around the screen.
This is me controlling a player locally with 8 frames of negative input delay

An important thing to realize about rollback and negative input delay is that the frames being displayed are NOT what actually happened (you can watch the replay to see the correct frames), and the frames do not have to happen in a logical order, which results in teleporting. So essentially, negative input delay is making the game lie to players with what it displays. It doesn’t change the game LOGIC at all (as opposed to input buffering, hold inputs, or action canceling).

The reasons why I would recommend against negative input delay are that it’s hard to implement, negatively affects performance, and makes it so what you see on the screen may not be accurate to what is actually happening. Still, it’s a fascinating technique and could be useful in some cases, and in small amounts (1–3 frames of negative input delay won’t cause large teleports in most cases).

Thank you for reading! Hopefully this post is useful for some game developers out there. As usual, here is the obligatory plug: https://springrollgames.itch.io/platform-fighter-engine

Platform Fighter Engine Logo

--

--

Yosi Spring

Occasional game development related articles. Full Profile: https://yosispring.github.io 🎄✝️