Need help/advice for making an input buffer

I’m trying to make an input buffer for my fighting game and would like some help with improving what I currently have which will hopefully put me in the right direction I’m aware this is quite an advanced subject, but I’d like to learn and give it an attempt anyways. I’m getting the majority of my info from this article I think it’s pretty resourceful (you can read the second section “Input Buffers?” which explains things well) I’m just having difficulties being able to translate what’s being said into code and it doesn’t help that the code being used is C++ haha.

Quick side note I’ve made a working input buffer script in the past which works quite well and I used another article (which was sadly taken down) to make it so I’ll share the code for anyone curious LuaU Input Buffer - Pastebin.com. I’d say the meat and potatoes of this module is the addCommand(), update(), and findBestActionMatch() functions everything else is just kind of utility.

Now for the main topic my new input buffer system, I wanna improve on the old one there are a few issues such as continuously adding the same input instead of just once when it’s first pressed, not really tracking what frame it was first pressed on or how many frames the button was pressed for, etc. So the article I’m currently reading from what I’ve dissected from it you can kind of break down the input buffer into 3 sections Virtual buttons, Storing the commands, and Reading the buffer. For the Virtual buttons, it basically talks about how you shouldn’t map game actions to physical buttons “Create a set of generic buttons representing the functions you need and then handle which physical button is meant to activate which function separately.” and I think I managed to get this part done.

Code Explanation: Everything in Enums.InputButtons is basically just a number Up_Direction = 4, Right_Direction = 8, Down_Direction = 2, etc and then in self.keymap I map those numbers to keys and then when one of those keys are pressed I just add the value to self.inputs so if W and D is pressed (which is the UpRight action) self.inputs will be 12 since 4 + 8 is 12 and now I know W and D AKA UpRight is pressed.

Enums.InputButtons = {
	Neutral_Input = bit32.lshift(1, 18),
	None = 0,
	Down_Direction = bit32.lshift(1, 0),
	Left_Direction = bit32.lshift(1, 1),
	Up_Direction = bit32.lshift(1, 2),
	Right_Direction = bit32.lshift(1, 3),
	DownLeft_Direction = bit32.bor(bit32.lshift(1, 0), bit32.lshift(1, 1)),
	DownRight_Direction = bit32.bor(bit32.lshift(1, 0), bit32.lshift(1, 3)),
	UpLeft_Direction = bit32.bor(bit32.lshift(1, 2), bit32.lshift(1, 1)),
	UpRight_Direction = bit32.bor(bit32.lshift(1, 2), bit32.lshift(1, 3)),
	TaptoHoldButtonShift = 19,
	HeldDown_Direction = bit32.lshift(bit32.lshift(1, 0), 19),
	Guard_Button = bit32.lshift(1, 4),
	Punch_Button = bit32.lshift(1, 5),
	Kick_Button = bit32.lshift(1, 6),
	Tech_Button = bit32.lshift(1, 7),
	Trigger_Button = bit32.lshift(1, 8),
	Selection_Button = bit32.lshift(1, 9),
	Cancel_Button = bit32.lshift(1, 10),
	Modifier_Button = bit32.lshift(1, 11),
	Menu_Pause_Button = bit32.lshift(1, 12),
}
function Input.new()
	local self = setmetatable({},Input)

	self.inputs = 0	-- Players inputs 
	self.buttonCounters = {} -- Store currently pressed buttons
	self.inputBuffer = {}
	self.buttonsPressedLastFrame = {}

	self.keybinds = {
		["Left"] = Enum.KeyCode.A,
		["Right"] = Enum.KeyCode.D,
		["Up"] = Enum.KeyCode.W,
		["Down"] = Enum.KeyCode.S,
		["LightAttack"] = Enum.KeyCode.H,
	}
	self.keymap = {
		[self.keybinds["Left"]] = inputButtons.Left_Direction,
		[self.keybinds["Right"]] = inputButtons.Right_Direction,
		[self.keybinds["Up"]] = inputButtons.Up_Direction,
		[self.keybinds["Down"]] = inputButtons.Down_Direction,
		[self.keybinds["LightAttack"]] = inputButtons.Punch_Button,
	}

	UserInputService.InputBegan:Connect(function(inputObject, gameProcessed)
		self:inputBegan(inputObject, gameProcessed)
	end)

	UserInputService.InputEnded:Connect(function(inputObject, gameProcessed)
		self:inputEnded(inputObject, gameProcessed)
	end)	

	return self
end

function Input:inputBegan(inputObject, gameProcessed)
	if gameProcessed then 
		return 
	end

	if inputObject.UserInputType ~= Enum.UserInputType.Keyboard then
		return
	end
	
	-- gonna be something like 8, 2, 4, etc
	local input = self.keymap[inputObject.KeyCode]
	
	-- set the input bit in the inputs number
	self.inputs = bit32.bor(self.inputs, input)
end

function Input:inputEnded(inputObject, gameProcessed)
	if gameProcessed then 
		return 
	end
	
	if inputObject.UserInputType ~= Enum.UserInputType.Keyboard then
		return
	end
	
	local input = self.keymap[inputObject.KeyCode]

	-- unset the input bit in the inputs number
	self.inputs = bit32.band(self.inputs, bit32.bnot(input))
end

After this, the next part would be to store the commands/input which is what I’m currently stuck on I feel like the code I currently have kind of does what it’s supposed to I can now track what frame a button was pressed on, and for how long, but it’s still continuously added, but unlike my last input buffer the input is at least limited to 1.

function Input:isBitSet(bit)
	return bit32.band(self.inputs, bit) == bit
end

-- Update the button counters
function Input:updateButtonCounters()
	-- Loop through all buttons
	for keyCode,bit in pairs(self.keymap) do
		local isPressed = self:isBitSet(bit)
		
		if isPressed then
			-- If the button is pressed, increment its counter
			if self.buttonCounters[bit] == nil then
				self.buttonCounters[bit] = 0
			end
			self.buttonCounters[bit] = self.buttonCounters[bit] + 1
		else
			-- If the button is not pressed, reset its counter
			if self.buttonCounters[bit] and self.buttonCounters[bit] > 0 then
				print(bit,"was held for",self.buttonCounters[bit],"frames")
			end
			self.buttonCounters[bit] = 0
		end
	end
end

-- Add a button press to the input buffer
function Input:addButtonToInputBuffer(inputBuffer, bit, framesHeld)
	-- Check if the button was pressed for a certain number of frames
	if framesHeld >= 1 then
		-- Add the button press to the input buffer
		table.insert(inputBuffer, {action = bit, framesHeld = framesHeld})
	end
end

function Input:processInput()
	local inputBuffer = {}

	-- Update the button counters
	self:updateButtonCounters()

	-- Loop through the button counters
	for bit, framesHeld in pairs(self.buttonCounters) do
		-- Add the button press to the input buffer
		self:addButtonToInputBuffer(inputBuffer, bit, framesHeld)
	end

	if #inputBuffer > 0 then
		print(inputBuffer)
	end

	return inputBuffer	
end

function Input:update(currentFrame)
	local buffer = self:processInput()
end

Screenshot 2023-02-07 215017

3 Likes

i know it might be offtopic but im curious what are the pros of using a metatable for this fighting game you are creating?

This code which is old (1 year old) is probably from a time (which still exists today) in witch people believed that metatables = OOP

There’s not a single metamethod being used in the code which makes the use of metatables not necessary at all.

either way OP’s goal is this:

local inputs = {
    [Enum.KeyCode.ButtonY] = function()

   end)
}

or this

local ACTION = "Punch"

ContextActionService:BindAction(ACTION, function() end), true, Enum.KeyCode.ButtonY, ...)

according to the links in the main post.

1 Like

Yeah I think thats the simpestly way to do it and It’s also responsive too