For the longest time as an developer, I have seen many fighting games use an multi-key combo system in their games, I have experimented with this but have never ever come close to fully understanding it. I have made basic implements of this with if arguments but don’t use multiple keys.
For those unfamiliar to what I am saying, this multi-key combo system works like this:
R + L + R + L
L + L + R + L…
In case you don’t understand R and L, it stands for right and left mouse clicks.
And so on. If you’re able to hit a player you carry on with the combo until you hit the last key, if you miss or don’t hit a player for a like 3 seconds, the combo gets canceled. I never really understood how this in general worked and I don’t even know if UserInputType is even able to do such a thing. I was hoping someone here more experienced is able to provide an in-depth example/explanation of how this works,
There’s no built-in way of detecting when a “combo” is executed, and it wouldn’t make sense if there was because the exact mechanics of how it works would be specific to every game.
A very simple way of detecting combos is to just allow a combo to grow when an action is performed within a small enough delay of the previous action. Here’s something I whipped up, it won’t work by itself but hopefully you can get some ideas for your own solution:
local comboPatterns = {
{name = "One-two Punch", pattern = {"R", "L", "R", "L"}},
{name = "Right Jab", pattern = {"L", "L", "R", "L"}}
}
do
local combo = {} --A table describing the combo of actions the player has made
local comboExpireTime = 0.5
local lastActionTick = tick()
--Adds an action to the combo, resetting the combo if it's expired, and returning the combo afterwards
function addToCombo( action )
local t = tick()
--Reset the combo if it's expired (more time passed than comboExpireTime)
if t - lastActionTick > comboExpireTime then
combo = {}
end
lastActionTick = t --Reset the timer
--Add the action to the combo
table.insert(combo, action)
return combo
end
end
function compareCombo( pattern, combo )
--Compare the last action of the pattern and combo, then the second-to-last, etc.
local patternIndex = #pattern
local comboIndex = #combo
repeat
if combo[comboIndex] ~= pattern[patternIndex] then
--If they don't match, return early
return false
end
patternIndex -= 1
comboIndex -= 1
until patternIndex == 0 or comboIndex == 0
--If none of the actions failed to match the pattern, it's a match
return true
end
function findLongestMatchingCombo( combo )
--Find all combo patterns that match the current one
local matchingCombos = {}
for _, comboPattern do
if compareCombo(pattern.pattern, combo) then
table.insert(matchingCombos, comboPattern)
end
end
--Sort them by length, and return the longest
table.sort(matchingCombos, function ( a, b )
return #a.pattern < #b.pattern
end)
return matchingCombos[#matchingCombos]
end
function onActionPerformed( combo )
local longestMatchingCombo = findLongestMatchingCombo(combo)
if longestMatchingCombo then
print(longestCombo.name)
end
end
function onMouse1Clicked()
local combo = addToCombo("L")
onActionPerformed(combo)
end
function onMouse2Clicked()
local combo addToCombo("R")
onActionPerformed(combo)
end
My idea is very simple, you just need to create a recursive function with the table parameter containing the combo after it is completed, delete it and call the function again.
function Combo(allCombo)
local action
local key
for k,v in pairs(allCombo) do
action=v
key=k
break
end
if action==nil then
return
end
local t=0
local Do= false
repeat
t=t+dt
wait(dt)
if input==action["input"] then
Do= true
func['func']()-- doing combo
break
end
until t>=input['time']
if Do then
allCombo[k]=nil
Combo(allCombo)
end
end
Note that you must create functions for each combo and the Combo table must look like this: