Hello everyone, so recently I’ve been reworking my input system for a traditional fighting game. My current system goes as follows:
- Create a variable called “tickAtInput” which will store the current tick when the player enters an input.
- Read and store (translated) player inputs alongside the
tickAtInput
variable, thus resetting the tickAtInput variable. - Use RunService and constantly check
if (tick() - tickAtInput) >= MAX_TIME_BEFORE_PROCESSING
- If the condition is met, check if there’s any actual inputs in the inputs table, if so follow the next steps
- Sort the input table into moves and times. Store the difference between the first input’s time and the last as
howLong
- Run through all possible sequences in a determined moveset, check if any of the sequences match the inputs in the inputs table
- If there’s one or more valid sequences, check their priority. Find the highest priority move and check to see if their leniency variable is greater than
howLong
, if not then execute the move’s function. If their leniency variable is lower thanhowLong
, then find the lowest priority move and execute their function.
Now that you know the process, what’s the actual problem? Well, when I input proper commands such as {"Down","Down_Forward","Forward","LightPunch"}
, it takes four frames (yes I’ve counted) before the move’s function actually ran. This might seem not too bad, but other fighting games such as street fighter have their moves executed on the exact same frame.
Here is an example script that uses the process listed above:
local Melee = require(game.ReplicatedStorage.Melee).new()
local Translator = Melee.Translator.new({
W = Vector2.new(0,1),
D = Vector2.new(1,0),
A = Vector2.new(-1,0),
S = Vector2.new(0,-1)
})
local InputProcessor = Melee.InputProcessor.new()
local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local inputs = {}
local tickAtInput: number = tick()
local testMoveset = {
["Uppercut"] = {
Sequences = {"Forward", "Down", "Forward", "LightPunch"},
Priority = 6,
TimeWindow = 15,
Activation = function()
print("Uppercut!")
end,
}
}
UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
if gameProcessedEvent then return end
if input.UserInputType == Enum.UserInputType.Keyboard then
local translatedInput = Translator:Translate(input)
if translatedInput then
tickAtInput = tick()
table.insert(inputs, {translatedInput, tickAtInput})
end
end
end)
RunService.RenderStepped:Connect(function(deltaTime: number)
if InputProcessor:TickToFrames(tickAtInput, tick()) >= 12 then
if #inputs >= 1 then
local nameOfInputs = {}
local validMoves = {}
local howLong = InputProcessor:TickToFrames(inputs[1][2], inputs[#inputs][2])
for _, inputData in pairs(inputs) do
table.insert(nameOfInputs, inputData[1])
end
for name, moveData: {Sequences: {}, Priority: number} in pairs(testMoveset) do
if InputProcessor:Search(nameOfInputs, moveData.Sequences) then
table.insert(validMoves, moveData.Priority)
end
end
if #validMoves > 1 then
local highestPriorityMove = InputProcessor:FindMoveByPriority(testMoveset, InputProcessor:FindMaxValue(validMoves))
local lowestPriorityMove = InputProcessor:FindMoveByPriority(testMoveset, InputProcessor:FindMinValue(validMoves))
if highestPriorityMove.TimeWindow > howLong then
highestPriorityMove.Activation()
else
lowestPriorityMove.Activation()
end
else
local move = InputProcessor:FindMoveByPriority(testMoveset, validMoves[1])
move.Activation()
end
end
end
end)
Here is the searching algorithm:
local function computePrefixTable(sequence)
local m = #sequence
local pi = {}
local k = 0
pi[1] = 0 -- pi[1] is always 0
for i = 2, m do
while k > 0 and sequence[k + 1] ~= sequence[i] do
k = pi[k]
end
if sequence[k + 1] == sequence[i] then
k = k + 1
end
pi[i] = k
end
return pi
end
-- Function to check if sequence exists in inputTable using KMP algorithm
function InputProcessor:Search(inputTable, sequence)
local n = #inputTable
local m = #sequence
local pi = computePrefixTable(sequence)
local q = 0
for i = 1, n do
while q > 0 and sequence[q + 1] ~= inputTable[i] do
q = pi[q]
end
if sequence[q + 1] == inputTable[i] then
q = q + 1
end
if q == m then
return true
end
end
return false
end