Hello Everyone, I am writing to talk about fighting game inputs, I’ve made a system to take in inputs in a buffer then reset the buffer using this article, the issue is, it’s hard to store multiple inputs in a single frame whilst using Roblox’s Engine.
Current Source Code:
local Player: Player = game.Players.LocalPlayer
local Character: Model = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart: Part = Character:WaitForChild('HumanoidRootPart')
local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Melee = require(game.ReplicatedStorage.Melee).new()
Melee:SetKeyConfigs(Melee.DefaultKeyConfig)
Melee:SetMoveset(Melee.ExampleMoveset)
local currentFrames = 0
local bufferClearInterval = 0.3 -- Half a second
coroutine.resume(coroutine.create(function()
UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
if gameProcessedEvent then return end
if input.UserInputType == Enum.UserInputType.Keyboard then
Melee:RecordInput(input.KeyCode)
end
end)
end))
coroutine.resume(coroutine.create(function()
local ForwardKeyConfig = {
[Enum.KeyCode.H] = "Punch",
[Enum.KeyCode.J] = "Kick",
[Enum.KeyCode.W] = "Up",
[Enum.KeyCode.S] = "Down",
[Enum.KeyCode.D] = "Forward",
[Enum.KeyCode.A] = "Back"
}
local BackwardKeyConfig = {
[Enum.KeyCode.H] = "Punch",
[Enum.KeyCode.J] = "Kick",
[Enum.KeyCode.W] = "Up",
[Enum.KeyCode.S] = "Down",
[Enum.KeyCode.D] = "Back",
[Enum.KeyCode.A] = "Forward"
}
RunService.Heartbeat:Connect(function()
if Melee.GlobalFunctions.Round(HumanoidRootPart.Orientation.Y) == 90 then
Melee:SetKeyConfigs(ForwardKeyConfig)
else if Melee.GlobalFunctions.Round(HumanoidRootPart.Orientation.Y) == -90 then
Melee:SetKeyConfigs(BackwardKeyConfig)
end
end
end)
end))
local lastBufferClearTime = tick() -- Initialize with the current time
local function UpdateInputs()
currentFrames += 1
print("Inputs for Frame " .. tostring(currentFrames) .. ":", Melee:GetBuffer())
-- Clear the recorded inputs for the next frame
local currentTime = tick()
if currentTime - lastBufferClearTime >= bufferClearInterval then
lastBufferClearTime = currentTime
local lastBufferBeforeClear = Melee:GetBuffer() -- Store the current buffer before clearing
-- Look Through Moveset to find the matched sequence and start the Action function.
for _, moves in pairs(Melee:GetMoveset()) do
if moves.Name == Melee:ProcessBuffer(lastBufferBeforeClear) then
print(Melee:ProcessBuffer(lastBufferBeforeClear))
task.spawn(function()
moves.Action(Player, moves.AnimationId)
end)
Melee:ClearBuffer()
end
end
Melee:ClearBuffer()
print("Buffer cleared at " .. currentTime .. "s")
end
end
RunService.Heartbeat:Connect(UpdateInputs)
(I apologize for the spaghetti)
Is there any other way I can implement this? Because when I for example do jump (Which is the W key), it has a noticeable delay between the input and the action, but this delay is needed to handle multiple input moves, what can I do about this?
Here’s my poor attempt. User input gets shoved into the input buffer which every 30 frames gets processed
GIF contains example outputting the contents of the input buffer and the attack returned (e.g pressing D then W results in {Forward, Up}: Uppercut).
Any comments in all caps require edits to be made to the code if you choose to utilize it.
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local COMBINATIONS = {
["Uppercut"] = {"Forward", "Up"},
["Slam"] = {"Forward", "Down"}
}
local FORWARD_KEY_CONFIG = {
[Enum.KeyCode.H] = "Punch",
[Enum.KeyCode.J] = "Kick",
[Enum.KeyCode.W] = "Up",
[Enum.KeyCode.S] = "Down",
[Enum.KeyCode.D] = "Forward",
[Enum.KeyCode.A] = "Back"
}
local BACKWARD_KEY_CONFIG = {
[Enum.KeyCode.H] = "Punch",
[Enum.KeyCode.J] = "Kick",
[Enum.KeyCode.W] = "Up",
[Enum.KeyCode.S] = "Down",
[Enum.KeyCode.D] = "Back",
[Enum.KeyCode.A] = "Forward"
}
local function requestAttack(attackName)
print(attackName)
end
--[[This makes sure that if a user inputs
a combo and an unrelated move afterwards in the same frame,
it performs the combo and ignores the unrelated move]]
local function containsCombo(combo: {string}, table2: {string}): boolean
local matches = true
for index, action in combo do
if action ~= table2[index] then
matches = false
break
end
end
return matches
end
local inputBuffer: {string} = table.create(6)
local function flushInputBuffer(): string?
local copyBuffer = table.clone(inputBuffer)
table.clear(inputBuffer)
for comboName: string, combo: {string} in pairs(COMBINATIONS) do
if containsCombo(combo, copyBuffer) then
return comboName
end
end
--If no combo was peformed, return the first attack in the input buffer
return copyBuffer[1]
end
local function processInput()
local attack = flushInputBuffer()
if attack ~= nil then
requestAttack(attack)
end
end
--STOP LISTENING FOR INPUT WHILE THE USER IS PERFORMING AN ATTACK OR STUNNED, DEAD ETC
local function onInput(input: InputObject, proccessed: boolean)
--Protect our quasi-buffer from "overflowing", we don't need to handle more than 6
--inputs per-frame anyway, are you seriously gonna have a combo longer than that?
if #inputBuffer >= 6 or proccessed then
return
end
--ADD YOUR LOGIC TO SWITCH BETWEEN FORWARD AND BACKWARD KEY CONFIG HERE
local attack = FORWARD_KEY_CONFIG[input.KeyCode]
if attack ~= nil then
table.insert(inputBuffer, attack)
end
end
UserInputService.InputBegan:Connect(onInput)
while true do
--[[If we processed input every frame, it would be impossible to perform combos
(as you would have to hit all the keys within the same frame, which is not humanly possible)]]
for i = 1, 30 do
RunService.RenderStepped:Wait()
end
processInput()
end
Oh, cause I was trying to not have a delay for that, since in games like Street Fighter, when they jump it’s instantaneous, whilst also having multiple input frame delay for the moves.
You could try having some specific keys (such as ‘W’ for jump) jump instantly, and then if they input another key during the jump, cancel the jump and peform the combo.