2D Fighting Game Input Buffer

Hello Everyone, What is the best way to create a fighting game 2D input buffer?

I’ve already attempted making one which looks like this

-- Constant Variables

local Player: Player = game.Players.LocalPlayer
local Character: Model = Player.Character or Player.CharacterAdded:Wait()
local Humanoid: Humanoid = Character:WaitForChild('Humanoid')
local HumanoidRootPart: Part = Character:WaitForChild('HumanoidRootPart')
local Metadata: Folder = Player:WaitForChild('Metadata')
local StateValue: StringValue = Metadata:WaitForChild('State')
local Animator: Animator

-- Prequisites.

if Humanoid:FindFirstChild("Animator") then
	Animator = Humanoid:FindFirstChild("Animator")
else
	Animator = Instance.new('Animator', Humanoid)
end

-- Test Dataset

local moveSet = {
	["1"] = {
		["Initial"] = {
			['Animation'] = '14465067787',
			['VelocityForwards'] = 1,
			['DirectionAhead'] = 2.5,
			['HitboxSize'] = Vector3.new(5,5,5),
			['KnockBackForce'] = 500 -- Keep relatively small like around 1000
		},
		["23"] = "Right Hook",
		["32"] = "Kick",
		["11"] = "Headbutt",
		["22"] = "DoublePunch"
	},
	['2'] = {

	},
	['3'] = {

	}
}

-- Requires

local Polarix = require(game:GetService("ReplicatedStorage").Polarix).new()
local Keyboard = require(game:GetService("ReplicatedStorage").Polarix.Modules.Util.Keyboard).new()
local Trove = require(game:GetService("ReplicatedStorage").Polarix.Modules.Util.Trove).new()
local Boost = require(game:GetService("ReplicatedStorage").Polarix.Modules.Global.Boost)

-- Runtime Variables

local inputTable = {}
local lastInputTime = 0
local comboString = ""
local comboMatch
local canAttack = false

-- Function to translate keybinds to universal numeric inputs
local function TranslateKeyCode(input)
	local keyReturnTable = {
		[Enum.KeyCode.H] = "1",
		[Enum.KeyCode.J] = "2",
		[Enum.KeyCode.K] = "3"
	}
	return keyReturnTable[input]
end

-- Function to clear inputTable if no inputs within the last 3 seconds

local function ClearInputTableIfIdle()
	if tick() - lastInputTime > 0.3 then
		inputTable = {}
	end
end


-- Function to get an animationTrack to be playable via Animator.
-- Parameters: ID
-- Returns: AnimationTrack

local function getPlayableAnimation(animationId: string)

	local AnimationToPlay = Instance.new('Animation')
	AnimationToPlay.AnimationId = "rbxassetid://"..animationId
	local PlayableAnimation = Animator:LoadAnimation(AnimationToPlay)

	Trove:Add(PlayableAnimation)
	Trove:Add(AnimationToPlay)

	return PlayableAnimation

end


-- Main Code Execution

Keyboard.KeyDown:Connect(function(key)
	local numericInput = TranslateKeyCode(key)

	if numericInput then
		lastInputTime = tick() -- Update last input time
		table.insert(inputTable, numericInput)
		comboString = tostring(table.concat(inputTable))

		print("ComboString:", comboString)

		local lastIndex = string.len(comboString) - 1
		local lastChar = string.sub(comboString, lastIndex, lastIndex)
		local secondLastChar = string.sub(comboString, lastIndex - 1, lastIndex - 1)..lastChar 
		local thirdLastChar = secondLastChar..string.sub(comboString, lastIndex - 2, lastIndex - 2)

		print("LastCharacter:", lastChar)
		print("SecondLastCharacter:", secondLastChar)
		print("ThirdLastCharacter:", thirdLastChar)

		print("CurrentPlayerState:", StateValue.Value)

		if StateValue.Value ~= "ForwardDash" or StateValue.Value ~= "BackwardDash" then
			if lastChar == "" then

				local Data:{} = moveSet[numericInput]["Initial"]
				local AnimationId: string = Data.Animation
				local Velocity: number = Data.VelocityForwards
				local DirectionAhead: number = Data.DirectionAhead
				local HitboxSize: Vector3 = Data.HitboxSize
				local KnockBackForce: number = Data.KnockBackForce

				local AnimationToPlay = Instance.new('Animation')
				AnimationToPlay.AnimationId = "rbxassetid://"..AnimationId
				local PlayableAnimation = Animator:LoadAnimation(AnimationToPlay)

				Trove:Add(PlayableAnimation)
				Trove:Add(AnimationToPlay)

				PlayableAnimation.KeyframeReached:Connect(function(keyframeName: string) 
					
					print(keyframeName)
					
					if keyframeName == "RegisterHit" then
						Boost.AddVelocity(HumanoidRootPart, 600, 0.1)
						Polarix:FireMultiplePackets("CreateInputHitbox", {HumanoidRootPart, DirectionAhead, HitboxSize, KnockBackForce})
					end
				end)
				
				PlayableAnimation:Play()
				
				
				Trove:Connect(PlayableAnimation.Stopped, function()
					Trove:Clean()
				end)
			end

			if string.len(comboString) >= 3 then
				if moveSet[numericInput][secondLastChar] then
					print(moveSet[numericInput][secondLastChar])
				end
			end
		end		
	end
end)

-- Connect a timer to periodically check and clear inputTable if idle
local checkIdleTimer = game:GetService("RunService").Heartbeat:Connect(ClearInputTableIfIdle)

As you can tell it’s pretty janky and pretty flawed, If anyone can tell me any methods, structures, or preferably articles I can take a look at, it would be much appreciated!

4 Likes