I was recently tasked with a new system to handle any number of combos. Basically it needs to be easy to define a given combo and easy to fire off an event when that combo is successful. Starting off I had 27 various combos with 13 different attack types. For example ( Light hit, Light hit, Light hit ) would be one combo. First I wanted to define the limitations of the combo system.
Once a combo has been reached the attack chain is cleared. For example ( Light hit, Light hit, Light hit, Uppercut ) would be 1 combo with the first 3 hits and then the chain would be cleared, and uppercut would begin the next sequence. This is important because the designers need to be aware of the types of combo’s they define. If it was absolutely necessary to extend the combo, I think it would still be possible, but my solution wouldn’t work as well, because a person could in theory just do light hits constantly and be notified of a combo.
The next thing to define is what causes the combo to be broken or cleared. In this case it is so much time since last attack, the player has taken damage, or a combo has been reached. The other interesting thing is, the designers wanted a move to only count towards the combo if it connected with an enemy.
So I started thinking about different solutions to this problem and what I came up with is a pretty elegant way to handle it.
I created an Attack Stack that the player uses, each attack has a unique id as well so when a hit is detected I can make sure the same move is not pushed on twice. The attack sequence goes like this:
- Begin Attack: Increments attack id and assigns it to the current move
- Perform attack: any contacts are pushed on the attack stack and a timer is saved for the last time an attack connected
- End Attack:
The attack stack that I used is basically a list that can be used to push or iterate through. Each combo is defined by its moves so I have the number of moves, and the sequence of the attacks. The list of combo’s is sorted by the total value of the combo (each move has a unique value associated with it). A quick linear search will find the first move that has an equal value as the one being checked. The only problem is this number needs to be calculated from the minimum number of moves to make a combo to the maximum number required. So an attack stack that has 7 moves on it could in theory be a 2, 3, 4, 5, 6, or 7 hit combo. So 6 values will need to be generated, this can be decreased if the maximum number of moves in a combo is defined as lower than 7 and/or more than 2. Combo’s can possibly have the same value so you need to check values in the list until the total value is no longer equal. Then its very simple comparison checks to see if the combo exists in the attack stack. (Note each attack is defined as an enumeration, as is each combo type). Once a combo is detected an event fires that passes in the index to the type of combo that was detected.
There are some improvements that could be made if the number of combo’s started to become excessive and a little extra memory was available to spare. The first thing I could see doing is bucketing the combo list based on the number of moves. So each combo doesn’t have to be touched when checking for only a 2 hit combo. Another improvement could be doing a quicker search algorithm, although at linear time with only 27 combo’s the average case is only 13 or 14 checks. But thats possibly 14 checks times 6 possibilities, so that starts to get pretty high. In my case I was working on a range of 3 to 5 so its 14 checks times 3 possibilities so its a much lower number and it works in my case. I would like to revisit the system and improve it to make it more flexible and performance friendly.
I would be incredibly interested in other solutions to this problem. Another idea I had is to generate a graph based on the combos that can occur, and mark the final location for the combo as a special node. Then just navigating the graph would determine if the combo has been obtained. I feel like this solution could probably be made to work, but it feels the complexity outweighs the actual gains.