I am Guillermo García Subirana, student of the Bachelor’s Degree in Video Games by UPC at CITM. This content is generated for the second year’s subject Project 2, under supervision of lecturer Ricard Pillosu.
Intro to the problem
An input combo system can vary greatly from one video game to another. That’s why we have not established a fixed system to follow when we’re developing a system for our video game.
In this guide we will investigate about the input combos system that some video games have developed, we will discuss the different techniques that can be applied and, finally, we will make step by step our input system combo.
My intention is to make the system as flexible and complete as possible, which can be adapted to any project with a few simple changes. All the development exposed on this page has been done from scratch with techniques developed from the research done before this guide.
Different approaches by different games
First let’s analyze some games that use a input combo system significantly different between them. In this way we can distinguish some key parts of the system more easily.
Street Fighter
Street Fighter - Ryu vs Ken
In Street Fighter, the user is able to perform a variety of combos already defined. Some keys of their input combo system are:
- To make a combo it is not enough to press a defined series of keys correctly, it must be in correct timing and in a limited time.
- The pool of possible inputs can be pretty long.
Heavy Rain
Heavy Rain example
In Heavy Rain they usually use a very interesting input combo system:
- The system stores the events entered by the user and allows you to continue the combo (showing in this case) the new event key to follow.
- The system constantly checks that you are keeping the keys already pressed without taking into account how long it takes to press the next one.
Description in detail
In all the titles that use an input combo system, they use a vector/list or tree (difference explained in detail below) to store the last inputs introduced by the user.
Input Storage
- Vector/List:
If we use a vector or list to store the inputs generated by the user, we will have to require a list of combos already defined to compare in each iteration of the application if there is a match between the “volatile” inputs that have been entered and the combo already defined.
The images below shows a combo list already predefined (the one above) that will be compared with the volatile vector (the bottom one) to know if the combo has been successfully completed at some time.
Volatile vector doesn’t match with the predefined combo
Introced new input in the volatile vector
There is a match between volatile chain and the predefined combo
The volatile combo is cleaned
- Tree
On the other hand we can use a tree to store both the last inputs entered by the user and the combo itself. In this way, every time an input is received we will have to check if we can continue the path towards the end of the combo. In case of not being able to continue with the given input, the combo will fail and will have to start from the beginning. Also keep in mind that the combos system will only be aware of the input that follows.
The tree is build around the combo/s predefined and wait for only the required input
The tree has obtained a new event and stores it temporarily (green mark)
If the tree gets an incorrect input (red mark), the previously stored events are deleted
If the end of the tree is reached, the combo will be made and the temporary events will be cleaned
Circular Buffer
As we already know the memory is not unlimited and, therefore, storage is usually carried out using a circular buffer system that keeps the most recent inputs in memory. In this way, every time a new input is introduced (and therefore the oldest one is deleted) it is checked from the first found event to the last one created and then look for a match between the chain of inputs and one of our combos. It should be noted that conditions can be added to eliminate events such as in a fighting game: the time between events.
In the images below, the time buffer is shown graphically. The blue line marks the time (milliseconds) that must happen for the last input to be deleted. For another band the red mark indicates the time limit that has the following input to be considered as a combo with the previous ones.
The volatile chain with three inputs
The condition of time has caused input “A” to be eliminated. While another input “B” has entered
Resolution of the combo
In case of finding a match, we will eliminate or not the list of our last events and the appropriate movement or action can be carried out by means of a finite state machine.
Our approach step by step
Since in a fight / beat em ‘up genre the input combo system becomes more complex due to the time factor between events, we are going to create a combo system similar to the one that a videogame of this genre would use, although it will be ready to adapt to any other combo system in an easy way. We are going to use vectors and lists to store predefined combos and to organize the volatile chain of events.
Cick here to download the last release
At the end of the steps we will have a result like this
First let’s locate, all the magic of this system will occur in the folder InputComboSystem. In this folder we’ll find three classes:
-
Combo: In this class we will define and load all the necessary inputs to form a combo. From this class we will also check if a combo has been solved or not.
-
Input Event: This class will define the type of input (a direction?, an action?). In addition, each input event will have a timer that will allow us to know when the input entered, the time it has taken between that input and the next and finally, the limit that had from one input to another (used in the inputs of the combo).
-
ctInput Combo: The core of the system. From this class we will collect the inputs, and we will use the two previous classes so that everything works.
Click here to download the project
TODO 1
Objective
- Create the InputEvents from its constructor. Use the “GetInputEventWithActionType” function for each type of input.
- You’ve to start the timer for the new InputEvent and call the constructor of the InputEvent class. Hint: You’ve to choose between two constructors, check InputEvent.h!
- Finally add the InputEvent to the volatile chain!
Solution
If you have built the InputEvents properly, try to generate some InputEvent with the keyboard or gamepad (Controls in the readme). If everything works correctly, they will be displayed at the bottom of the screen.
TODO 2
Objective
We must eliminate the inputs of the volatile chain after a certain time.
- Iterate between all the InputEvents stored in event_chain and compare the timer of those inputs with “GENERIC_TIME_LIMIT”.
- Delete those that go over time.
- Stop the time of the previous input event just before the next one is generated.
Solution
Now the inputs that you generate should be deleted after 2000 milliseconds (GENERIC_TIME_LIMIT) and now the inputevents will save the time that has occurred since they were born until another input has been entered. We just made the whole circular buffer!
TODO 3
Objective
So far so good! It’s time to add some combos to the system. In this case there will be two: Shoryuken and Tatsumaki.
- Generate an InputEvent to add it to the list of combos. Notice that the Combo class is ready to add InputEvents and add them to a vector. Check Combo.h!
- Add the combo formed to the list of combos.
Solution
The help label will indicate if you have correctly entered the combo in the list of combos.
TODO 4
Objective
We arrived at the most complicated part, but if you have arrived here, you will surely achieve it!
- Find if there is a match between the list of events in the combo(this->input_events) and the volatile chain(event_chain)
- Hints: You must iterate both lists and ask:
- Is this combo input equal to event_chain ?
- Is this combo input valid for a time limit ?
- Is the combo completed ?
If all the answers are yes, the combo has been performed correctly. Obviously you have to control the denials!
- Iterate between all the combos in the list
- In case the combo is completed, call the function “App->entities->GetPlayer()->OnComboCompleted()” and pass the combo type. To verify that it works, do one of the two combos.The controls are in the readme.
Solution
If the comparison has been made correctly, the combo movement will be executed.
But wait a second, something is wrong…
TODO 5
Objective
We didn’t clean the volatile input chain, that was it!
- Clean the volatile input chain after the combo is executed. There is already a function to do it!
Solution
Now the function that compares the list of combos and the volatile chain does not make the player receive multi-calls, it only calls it once and then the temporary chain is eliminated.
Optional Homework
- How would you implement a grip combo typical of the fighting genre (A + B)?
- Try to make a longer combo, say ten inputs. Should you delete the circular buffer? Maybe implement the system with a tree storage?
Performance
I have captured the time by iteration of the input combo system module using Brofiler. Specifically, the Preupdate and the Postupdate of the iteration.
- Preupdate: It is the block of code that compares all predefined combos with the volatile input chain. It is the section that consumes the most time in the whole system, specifically up to 0.036 milliseconds. It must be said that there are only two combos predefined in the system and that it is only used when there is some input in the volatile chain.
- Postupdate: In this block, the inputs are received and subsequently those that are outside the circular buffer are eliminated. It has an approximate cost of 0.001 milliseconds, so it is not subject to improvement.
Ways to improve
- Controlled Iteration: In the system of this guide, the input combo module is implemented from the beginning to the cycles of the application. This is not recommended at all, it should be activated or deactivated according to the needs of the user.
- Comparison of combos by recursion: As has already been shown in the performance, the cost of comparing combos can be improved with a better matching detection system which consumes less time, that’s why I think it can be totally viable and that it can improve performance using the recursion in the comparison between chains. Another solution may be to develop the tree system since unnecessary comparisons will be reduced only if few predefined combos are used.
- Use of callbacks: In this guide we have developed some actions for the combos pretty rudimentary. We call a method housed in the player class that tells you what the combo is. In the future this method of warning may not be viable since according to our needs we will need to notify more modules at once as soon as a combo is completed. That’s why I recommend using a system of listeners and callbacks associated with the module input combo.
References
Devs - talks about trees - www.gamedev.net
Devs - handle tons of inputs - www.reddit.com
Commercial indie - What do we do when the combo is completed? - www.reddit.com
Use of queue storage for inputs - shivansite.wordpress.com
Handle input events - www.stackoverflow.com
Devs - talking about ghosting and rollover - www.reddit.com
License
MIT License
Copyright (c) 2018
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.