LoveBuffer - Simple buffer system for your fighting games!

Hey! I’ve decided to make a buffer system as one of the common mistakes people new to combat systems make is not having a input buffer! This makes your game feel way more responsive and makes it feel way less like the game is against the player.

Keep in mind that this is my FIRST community resource so please do not be too harsh on me, however do give feedback please! I’ll try to fix any bugs you find.

All the documentation and everything you need is inside the module, if you need help with anything ask me!

Here’s the module!
https://create.roblox.com/store/asset/96014038264759/LoveBuffer

You should use this module INSTEAD of user input service! And if you like using ContextActionService, then unfortunately you can’t use that with this module currently :frowning:

Update: I’ve now changed the module to work somewhat like ContextActionService. You register keys into the key registry so the module listens to the keys you register and does the callback with returning what key was fired.

If you want to look into the module without actually putting it in your game yet then here you go

local lb = {}
lb.__index = lb

local userinputservice = game:GetService("UserInputService") -- yeahhhh
local runService = game:GetService("RunService")

local Signal = require(script.Signal) -- im using this for the callback

--[[
    __                    ____        ________         
   / /   ____ _   _____  / __ )__  __/ __/ __/__  _____
  / /   / __ \ | / / _ \/ __  / / / / /_/ /_/ _ \/ ___/
 / /___/ /_/ / |/ /  __/ /_/ / /_/ / __/ __/  __/ /    
/_____/\____/|___/\___/_____/\__,_/_/ /_/  \___/_/     
                                                       

DO
NOT
MAKE
MULTIPLE
BUFFERS
PLEASE

- JUST MAKE ONE BUFFER PER PLAYER PLEASE!! YOU DON'T NEED TO MAKE A NEW ONE EVERY TIME THEY DIE!! IT'S UNNECESSARY MEMORY LEAKS!!

- Buffers inputs yada yada yada you get the deal elden ring has it etc it makes ur game feel more responsive and allat

- inputCallback is your main guy you should use that instead of contextactionservice this module basically replaces your contextactionservice while adding buffer to it

- if you ATTEMPT to run this on the server I will HUNT YOU DOWN IRL!!!!

--]]

local BUFFER_SETTINGS = {
	Buffer_Decay = .25, --[[ basically how long inputs stay in the input buffer before they get deleted, if you set this to 0 then it is infinite but
	if someone spams attacks it'll have them repeating them for a long time ]]
}

function lb.new()
	local self = {}
	
	self.inputCallback = Signal.new()
	self.bufferStack = {}
	self.registry = {}
	
	--[[ BUFFER MODE ]]--
	
	-- 1 is the default, basically 1 is where you set bufferPause through the -**- function to true and while bufferPause is true it'll make callbacks wait until bufferPause is false
	-- 2 is the second, it's where you manually set different keys to on or off and it checks if any keys are set to on and while any key is set to on it makes callbacks wait until no keys
	-- are set to on
	
	-- honestly i reccomend 1 as the way i used it in my games is i usually used like a using attack value somewhere and i'd set the buffer's bufferPause to the usingAttack value
	-- therefor making it automatically buffer yeah yeah yeah but ok ima stop yapping ok
	self.bufferMode = 1
	
	-- mode 1 variable
	self.bufferPause = false
	
	-- mode 2 variable
	self.activeKey = nil
	
	--[[ main stuff ]]--
	
	-- stack continuer
	runService.Heartbeat:Connect(function()
		local canContinue = false

		if self.bufferMode == 1 then
			canContinue = not self.bufferPause
		elseif self.bufferMode == 2 then
			canContinue = self.activeKey == nil
		end

		if canContinue then
			if #self.bufferStack > 0 then
				self.inputCallback:Fire(self.bufferStack[1].input)
				table.remove(self.bufferStack, 1)
			end
		end
	end)
	
	local function addtostack(input)
		local stackEntry = {input = input} -- i have to wrap these so they're differentiated in memory
		
		table.insert(self.bufferStack, stackEntry)
		
		if BUFFER_SETTINGS.Buffer_Decay ~= 0 then
			task.delay(BUFFER_SETTINGS.Buffer_Decay, function()
				for i, entry in ipairs(self.bufferStack) do
					if entry == stackEntry then
						table.remove(self.bufferStack, i)
						break
					end
				end
			end)
		end
	end
	
	-- stack adder
	userinputservice.InputBegan:Connect(function(i,g)
		if g then return end
		
		if self.registry[i.KeyCode.Name] ~= nil or self.registry[i.UserInputType.Name] ~= nil then
			addtostack(i)
		end
	end)
	
	return setmetatable(self, lb)
end

--[[ REGISTER KEY ]]--
-- this registers a key and makes the buffer listen to the actual key!
function lb:registerKey(input:InputObject)
	local iName = input.Name
	
	if self.registry[iName] then
		warn("key is already registered!")
		
		return
	end
	
	self.registry[iName] = input
end

--[[ UNREGISTER KEY ]]--
function lb:unregisterKey(input : InputObject)
	local iName = input.Name
	
	if not self.registry[iName] then
		warn("input not found in registry!")
		
		return
	end
	
	self.registry[iName] = nil
end

--[[ BUFFER PAUSE ]]--
-- use this with mode 1 only
function lb:setpause(v)
	self.bufferPause = v
end

--[[ KEY ]]--
-- use this with mode 2 only
function lb:setactivekey(v)
	self.activeKey = v
end

return lb

Also here’s the testing script I used for testing the buffer:

-- make a new buffer
local lb = require(game:GetService("ReplicatedStorage"):WaitForChild("LoveBuffer")).new()

-- register our keys
lb:registerKey(Enum.KeyCode.E)

-- our main callback that we will use INSTAED OF USERINPUTSERVICE OR CONTEXTACTIONSERVICE
lb.inputCallback:Connect(function(input : InputObject)
	if input.KeyCode == Enum.KeyCode.E then
		print("E was pressed!")
		
		lb:setpause(true)
		
		task.spawn(function()
			task.wait(0.65)
			
			print("press now to see if buffer works")
		end)
		
		task.wait(1)
		
		lb:setpause(false)
	end
end)
3 Likes