Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
COMMUNITY QUESTION

Why does ContextActionService fire UserInputState.Begin twice on keypad input?

Long Title for such a small thing.

Why does ContextActionService fire UserInputState.Begin twice on keypad keys and once on normal keys? It should only be firing once with Enum.UserInputState.Begin and once with Enum.UserInputState.End when you release a key.

< It does this when you connect a 'normal' key from your keyboard.

Here's my small experimental chunk of local code:

local ContextActionService = game:GetService("ContextActionService")

ContextActionService:BindAction("keypad", print, false, Enum.KeyCode.KeypadFive)
ContextActionService:BindAction("normal", print, false, Enum.KeyCode.H)

Some months ago when I was testing this, it didn't fire UserInputState.End on keypad key release at all, now it fires twice Begin and one End ._.

thanks

2 answers

(Strange... it only fires one Begin and one End for me. I've found the UserInputService to sometimes fire events twice in other scenarios, however.)

To fix when Roblox is sending out duplicate events, use 'debounce'. ex, here is a modified print that will only print its argument if it's not been printed in this time frame before:

do
	local args
	local lastTime = -1
	function printOnce(arg)
		if lastTime ~= workspace.DistributedGameTime then
			args = {}
			lastTime = workspace.DistributedGameTime
		end
		if args[arg] then return end --already printed this
		args[arg] = true
		print(arg)
	end
end
printOnce(1) --Prints 1
printOnce(2) --Prints 2
printOnce(1) --Duplicate, so doesn't print anything
wait()
printOnce(1) --Time has passed, so prints 1

To prevent having to write this code repeatedly, you might use a wrapper function like these:

function CallFuncOncePerTime(func) --pass in the function
	local lastTime = -1 --this local variable is created for each function you pass in
	return function(...) --this is the function to return; it accepts any number of arguments
		if lastTime == workspace.DistributedGameTime then return end --we want the function to do nothing in this condition
		lastTime = workspace.DistributedGameTime --update lastTime
		func(...) --call the original function with any arguments passed in
	end
end
function CallFuncOncePerTimeArg(func) --func will run only if its first argument is unique for this time frame
	local lastTime = -1
	local args
	return function(...)
		if lastTime ~= workspace.DistributedGameTime then --reset args
			args = {}
			lastTime = workspace.DistributedGameTime
		end
		local arg = {...} --get first arg
		arg = arg[1]
		if args[arg] then return end --this is a duplicate; return
		args[arg] = true
		func(...)
	end
end

--Usage example
function test()
	print("Test")
end
test = CallFuncOncePerTime(test)
--or
test = CallFuncOncePerTime(function()
	print("Test")
end

--example 2:
function test(arg)
	print("Test", arg)
end
test = CallFuncOncePerTimeArg(test)
--or
test = CallFuncOncePerTimeArg(function(arg)
	print("Test", arg)
end)

You can thereby modify as many functions as you need so that they all exhibit this behaviour. The idea of a wrapper function is to "wrap" a function in another function. The Roblox article on debounce gives another example of this idea (under "Advanced Notation").

2 comments

0

I held the keypad key for like 1-2 seconds when it printed another Enum.UserInputState.Begin. Thanks for your debounce suggestion, I'll look into it but don't think I'll make it use time (because there might be some time between the state firing a second time) but just use kind of bools like if table[key] ~= inputstate then table[key] = inputstate code() end!

0

That was important information you didn't mention. Anyway, you're correct that using a table like that will work. I'd use: dict[key] = true/false (recording whether the button is currently pressed down or not), and only fire the 'OnDown' function if it's not already pressed down.

I recomend ContextActionService, its a eaiser version of UserInputServer

local createMobileButton = true -- if u want mobile buttons
local actionName = "CallFuncOncePerTimeArg"

local function onMouseDown(actionName,state,info)
	if state == Enum.UserInputState.Begin then
			function CallFuncOncePerTime()
    local lastTime = -1
    return function(...) 
        if lastTime == workspace.DistributedGameTime then return end
        lastTime = workspace.DistributedGameTime
        func(...) 
    end
end
	elseif state == Enum.UserInputState.End then
		print("Stopped Using Func")
	end
end

contextAction:BindAction(actionName,onMouseDown,createMobileButton,Enum.UserInputType.MouseButton1)

This discussion is preserved as it was. New answers, comments, and votes are closed.