Inputs per frame [2D Fighting Game]

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?

1 Like

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

If I were to do a single input such as “W” for UP, and since neutral UP = Jump, will it have a delay for processing?

Yes, since it only checks the inputs after 30 frames.

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.

How would I handle two movement inputs as one? For example:

Down-Back as
image

But if I do Down-Neutral-Back it’d be two inputs such as:
image
+
image