I wanna make a fighting game! A practical guide for beginners — part 6

Andrea "Jens" Demetrio
14 min readOct 10, 2021

Input buffers — or how to read and make sense of that list of mashed buttons

Welcome back to this tutorial series on how to approach the development of a fighting game! Today we will go through another of the big hurdles for beginners in the genre: input buffers and why they are needed for a smooth player experience.

But, first, a very important disclaimer: much like my article on determinism in fighting games, this one will be fairly technical. It is still part of this series because it is a question many developers asked me in the past, and something I personally had to fight against while building my engine. While I wrote this article because I wished there were more resources available, it’s by no means an easy topic to deal with. My suggestion for complete beginners is to just browse through this article and the previous one to get a general idea of the topic and then come back after getting more experience on the coding side.

My next articles will be about more “beginner-level” subjects (such as hitstun and combos, blocking and parries, grabs…), so, if you think you aren’t ready enough for this topic, you can directly skip to them!

Input buffers?

In most fighting games, pressing a button while the character is idle, usually results in an immediate action being performed. Be it a light punch, a hard punch or a roundhouse kick, the simple act of pressing a button translates 1:1 to activating a move. If we factor in character states, this seems nice and dandy and not very difficult to handle crouching and standing standard moves: if an attack button is pressed, check for the state the character is in and perform the corresponding move.

If we feel braver, we can start using the so-called “command normals” or directional inputs too. Sure, pressing the hard punch button is fine, but what if by pressing forward plus hard punch I get a different attack? This case is also quite easy to handle: we check if the character state is right, we check if the correct direction is pressed, we check if the attack button is pressed and — boom — command normal overhead!

Where this approach fails completely, it’s when you have to handle anything more complex than the above: let’s suppose you want to detect a fireball motion — typically a quarter-circle forward followed by a button press. Since there are four different inputs in total (down, down-forward, forward, punch), you cannot check for just one frame of overlapped buttons anymore. You need to keep a record of the buttons pressed in at least the last four frames. Moreover, what happens if your player inputs it as e.g. down, down-forward, forward, down-forward, punch? Do you still accept the input? What if forward and punch were pressed at the same frame, reducing the string to three inputs, instead of the four you were expecting?

To handle this and more edge cases, you need what is called an input buffer. If you haven’t heard of this until today, strap on your seatbelt and get ready for a crash course in the subject!

Imagine having to parse THIS without an input buffer. [1]

Virtual buttons are the way

Before going on, there is one, universal, solid advice that I beg you to consider: don’t map your game actions to physical gamepad buttons. Even if you are developing with an X-Box controller in mind and every PC as of 2021 supports them, don’t fall into the comfort trap of directly using e.g. X, Y, A, B buttons in your code “as is”. This will make it extremely difficult to implement button remapping, accepting new controllers or even making your system more flexible for other peripherals, like keyboards.

You need to create a layer between the “physical” buttons and the inputs the game receives. Your game doesn’t need to know that the X button was pressed: Your game needs to know that the light punch button was pressed.

Create a set of generic buttons representing the functions you need and then handle which physical button is meant to activate which function separately.

As an example of C++ code, you can use an enum which maps each “logical button” to a number, for ease of manipulation.

An example right out of the source code of my game Motionsickness. Virtual buttons are mapped to unsigned integer to use the power of bitwise operations

In flowchart terms, what happens is the following:

  • your input layer detects the press of the Y button on the controller;
  • your input layer searches its internal lookup tables to see which action is mapped to the Y button; it finds out that, according to the current settings, options and controller plugged, the Y button is mapped to the hard punch logical input;
  • your game engine is now notified that a hard punch button has been pressed and adds it to the list of inputs to process.

Not having the input mapping layer will damage you in the long run, removing flexibility and making things way harder to change. So, please, do not underestimate the importance of this step and plan ahead!

How not to do it and how to do it. Please, please build a translation layer! It will make things so much easier when you will be invariably asked to add button remapping!

Storing the commands

Now that we are on the same page about how to process the raw inputs, let’s delve a bit more into the topic on how to access and read them. One disclaimer: there are multiple ways to achieve the same result. The one I will show here is one feasible way that is proved to work, but don’t be afraid to do your own research and see what else is used around!

As said before, when you need to process longer inputs, you need to have measures in place to “go back in time” and see what was pressed and when.

One possible implementation of this concept is using the equivalent of a container (list, vector, deque) in your programming language/engine of choice. Ideally, you would keep one such container per character. Every frame, when you update the game logic, you read the physical input for each device, map it to the logical input and add it to the correct container.

I suggest storing inputs only if they weren’t pressed before, instead of continuously, to make things easier during the readout. There are other strategies, but for the rest of this article we will assume that buttons aren’t recorded again as long as they are kept pressed.

An example of the above. Each frame number in the container is mapped to the set of buttons pressed at that specific frame.

Storing inputs this way shouldn’t require a lot of memory: Keeping the full input history in your device’s RAM is perfectly doable, if you are developing for a modern console or PC. Where performance is critical, however, there are solutions like circular buffers, which can help easing the burden on the hardware.

Reading the buffer

Now that we have all our inputs stored, it’s time to read them in order and let your character perform the correct move! For this, we will be still need a couple additional tricks:

  • give each move a unique priority;
  • give each move a set of alternative inputs/shortcuts;
  • give each move a specific input time window;
  • give each move a set of conditions that make it usable, reducing the number of entries to check against at any given time.

Priority is relatively straight-forward: moves with higher priorities will be checked against the buffer before moves with lower priorities. This makes sure that inputting e.g. the sequence [forward, down, down-forward, forward, punch] results in a Shoryuken instead of a fireball.

Alternative inputs are a way to ease input detection and accept “dirty” or incomplete inputs, for a better user experience. Examples might include allowing for one additional direction before the button is pressed or to check if the direction and the button where pressed at the same time, and still accept the input as valid. For example, in the Street Fighter series, the so-called 360 motion (pressing all directions in a clockwise or counter-clockwise fashion) is accepted even if one performs a half-circle-motion followed by a upper diagonal direction. This GameFaqs post contains an interesting list of shortcuts accepted by Super Street Fighter 4, and might be useful to get an idea on how to read the buffer for specific motions.

Potential input variants for a fireball motion. Depending on how lenient you want to be, you can actually accept them all.

The third bullet point can be described as how far in the past will your game look when searching for inputs. This can be set in terms of frames and contributes to the overall leniency of the buffer. It is suggested to give some leniency to normal moves too, which allows players to input them slightly before their character becomes active (for example, while guarding) and have the move come out at the first possible active frame.

Finally, with “conditions to check”, I mean things like “super moves should only trigger if enough super meter is available” or “this move can only be performed while crouching”. Perform a check before going through the full move list and you will end up with only a handful of “legal moves” to actually run through your input buffer.

A practical example

To explain better how this works, let’s say we have the following moves:

  • A super move that requires two bars of meter and has the input [down, forward, down, forward, kick], has priority 5 and a time window of 15 frames;
  • A special move that has no meter requirements and has the input [down, down-forward, forward, kick], has priority 3 and a time window of 5 frames;
  • A normal move that has simply [kick] as an input, priority 1 and a time window of 3 frames.

We will see how these three interact in various cases.

Case 1: The input of the player is [down, forward, down, forward, kick] with full super meter and performed in 8 frames

There are two viable options here: the super move and the normal move, as our special move needs also a missing down-forward input. Among the two, the super move is checked first because of the higher priority. All conditions are fulfilled, so this is the move which is returned by the buffer reader.

Case 2: The input of the player is [down, forward, down, forward, kick] with full super meter and performed in 17 frames

The meter condition is fulfilled, but the input was too slow. Therefore, the parser will skip the super move. The special move doesn’t match the input for the missing down-forward direction. The only viable move is now the standard normal kick, which is then selected.

Case 3: The input of the player is [down, forward, down, down-forward, forward, kick] with full meter and performed in 10 frames

All three moves are viable in this specific situation, and the special move has a perfect overlap with the second part of the input. However, since we are checking the super move first, due to its higher priority, and all required inputs of the super are stored in the list in the correct order, the super move is the one which is selected. Notice that if you do not allow for outliers between valid inputs, the special move will be selected instead. It is a valid design choice, albeit a little bit frustrating from a player perspective, but you are definitely allowed to consider it.

Case 4: The input of the player is [down, forward, down, down-forward, forward, kick] with no super meter and performed in 10 frames

In this case, the special move will be selected, as the super is out of the picture due to the missing activation condition. The normal move matches the input too, but has a lower priority than the special move.

Case 5: The input of the player is [down, forward, down, forward, kick] with no super meter and performed in 10 frames

In this case, the normal move will be selected, as in Case 2, since one of the activation conditions for the super move aren’t fulfilled and we are missing an input for a valid special move.

As complexity of input goes, some developers just want to watch the world burn…

One significant alternative: per-button time buffer

It was brought to my attention that most Capcom games do not use a flat input buffer window for the whole move, but instead have a shorter buffer window after each button press. Let’s consider for example a fireball motion [down, down-forward, forward, punch]: When the player presses [down], the engine waits for a fixed amount of frames for [down-forward], which in turn is valid for a fixed amount of frames for a following [forward], which in turn (guess what?) is valid for a fixed amount of frames, while waiting for [punch]. This allows for much greater fine-tuning, precision and granularity in how the input is processed, but is definitely harder to implement. More details about how this was implemented in M.U.G.E.N. by the community is available here (special thanks to Kamekaze for teaching me about this possibility).

Charge inputs, negative edge and additional amenities

While the bones of the system are perfectly serviceable as described in the last few sections, there is still a couple specific input types that are somehow common and would need to be considered if we wanted to build e.g. a Street Fighter clone.

Charge inputs are a prominent one: to perform moves with a charge input, you need to keep a button pressed (usually, down or back) for a specific amount of time, before releasing it and performing the rest of the motion. Common charge motions are [charge down, up, attack] and [charge back, forward, attack], but there are too many flavors to count. Charge motions are usually best suited to defensive characters or for extremely good special moves that you don’t want players to perform on a whim.

How do we handle “charge” in our input buffer? One possible solution is to have a separate container that stores the number of frames each button has been kept pressed: every frame, you increase the counter for each pressed button by 1, and you zero it if the button is released. When this happens after N frames of continuously keeping it pressed, you can add a [button released after N frames] event to the input buffer, as if it was just another logical input, and use the same detection schema as discussed before. Treating charge events as “logical buttons” has the advantage of not having to change your move detection logic just for them.

Balrog’s Turnaround Punch [TAP] is an infamous example of “press, release” input.

The same approach can be applied for moves like Balrog’s Turnaround Punch and Cody’s Zonk Knuckle: both specials need the player to keep some buttons pressed for at least a specific amount of frames, before releasing them for unleashing the attack. By keeping track of the amount of time the buttons were kept pressed, one can add the same type of [button released after N frames] event to the queue and match the move against it.

Incidentally, adding “button release” events to the input buffer can be used for also implementing the so-called “negative edge”: some fighting games allow special moves to be triggered on a button press, as well as on a button release. These “release” input events are ignored for normal moves, but accepted as valid for both specials and super moves, allowing for an improved level of leniency in how these moves can be performed.

Handling simultaneous button presses

The last thing we have to deal with is how to handle simultaneous button presses. Many games use simultaneous inputs to trigger special moves, super moves or special character states (e.g. two punches button at once, all kick buttons…). Simultaneous presses, though, can be hard and inconsistent, especially because, if the game logic is updated at 60 fps, this means that the two buttons need to have been pushed in the span of 16.6ms. While this could be considered one case of “git gud scrub”, I am for simplifying life to players, at least when it comes down to performing basic game actions.

One possible way to handle this is to accept alternate inputs with buttons slightly “out of phase” (e.g. accept [punch+kick], [punch, kick — 1 frame later] and [kick, punch — 1 frame later] as equivalent for activating the move) and make it so that all moves that might come out by mistake can be canceled into the “proper” simultaneous input moves during its first few frames.

This is incidentally the root cause of how the so called “kara-cancels” work.

Another practical example

To explain better how this works, let’s say we have the following moves:

  • A super move performed by pushing [down, down-forward, forward, light punch + heavy punch] with highest priority and a 1-frame window leniency for the simultaneous press;
  • A special move performed by pushing [down, down-forward, forward, light punch];
  • Light punch and heavy punch normal moves with the lowest priority.

We will see how these three interact in various cases.

Case 1: The input of the player is [down, down-forward, forward, light punch + heavy punch]

The super move comes out as intended.

Case 2: The input of the player is [down, down-forward, forward, light punch, heavy punch — 1 frame late]

For the first frame, the character performs the special move, which is then canceled at frame two into the proper super mode once the [heavy punch] input is registered.

Case 3: The input of the player is [down, down-forward, forward, heavy punch, light punch — 1 frame late]

For the first frame, the character performs the [heavy punch] normal move, which is then canceled at frame two into the proper super mode, once the [light punch] input is registered.

Case 3: The input of the player is [down, down-forward, forward, heavy punch, light punch — 2 frames late]

As the [light punch] input is registered 2 frames after the [heavy punch] input, outside of the leniency window, the light punch move is performed and no cancel occurs.

Kara cancels are still alive and kicking, in some cases even intentional.

Move cancels, rekka and unique followups

As my last point in this article, I want to shortly touch on how to deal with rekka moves and move canceling. In a previous section, we discussed how every move should be given a priority, a leniency and a set of conditional triggers. What we can also do to make things easier for us is to allow each move to keep a list of other moves they can be canceled into. Which means, that if the player character is already performing a move, when we input a new command, only the moves in this list will be prioritized and checked against the input.

Final thoughts and conclusions

If you need an input buffer — which you will need if you want to implement any type of motion input — you need a container to store inputs at each frame. A balance of sensible priority, time windows and activation conditions allow for a very powerful an flexible input reading system. The use of “on release” events makes it relatively straightforward to implement charge moves. All in all, there is a lot to take care of, but once you nail down the basic concepts, your game will feel so responsive that it will repay all of your efforts.

So long for this tutorial — see you next time!

Notes, credits, and additional useful material

[1] Game: Super Dragon Ball Z (PS2), screenshot provided by sleepmode

[2] A Stack Exchange answer by Zinac about the very same topic we are considering here: https://gamedev.stackexchange.com/a/68134/106008

[3] Infil’s fighting game glossary: https://glossary.infil.net/

[4] How M.U.G.E.N. handles inputs: https://mugen-net.work/wiki/index.php?title=Command (special thanks to Kamekaze for linking me this resource)

[5] How the (broken) M.U.G.E.N. interpreter was replaced: https://mugen-net.work/wiki/index.php?title=Deep_Buffering

[6] Input shortcuts in Super Street Fighter 4: https://gamefaqs.gamespot.com/boards/975212-super-street-fighter-iv/55389548 (special thanks to Joeldaho for linking me this resource)

Other articles in the series

  • Part 1 — Introduction;
  • Part 2 — Design decisions;
  • Part 3 — Characters as state machines;
  • Part 4 — Hitboxes and hurtboxes;
  • Part 5 — Determinism;
  • Part 6 — Input buffers and “reading” moves;
  • Part 7 —Hit stun and combo systems;
  • Part 8 — Implementing a simple block/guard system;

If you are interested in more game-making tutorials, you can find me on Twitter at @AndreaDProjects. My DMs are open!

--

--

Andrea "Jens" Demetrio

PhD in Physics, indie game developer, fighting games connaisseur (he/him).