How to detect if player is holding a button?

Hi there.

I have a small problem, that is actually really hard for me to solve. My question is “How can I detect if player is still holding a button?” Let’s get into the details.

local SpeedValue_1 = 25
local SpeedValue_2 = 10
local SpeedValue_3 = 0

Character.Humanoid.Running:Connect(function(SpeedValue_4)

SpeedValue_3 = SpeedValue_4

print(SpeedValue_3)

end)

game:GetService('ContextActionService'):BindAction("ShiftSprint", function(actionName, inputState, inputObj)
    	if inputState == Enum.UserInputState.Begin and SpeedValue_3 > 1 then
    		Running = true
    		Character.Humanoid.WalkSpeed = SpeedValue_1
    		CamOut:Play()
    		CamIn:Pause()
    		RunAnimation:Play()
    	elseif inputState == Enum.UserInputState.Begin and SpeedValue_3 < 3 then
    		Running = false
    		Character.Humanoid.WalkSpeed = SpeedValue_2
    		CamIn:Play()
    		CamOut:Pause()
    		RunAnimation:Stop()
    	elseif inputState == Enum.UserInputState.End then
    		Running = false
    		Character.Humanoid.WalkSpeed = SpeedValue_2
    		CamIn:Play()
    		CamOut:Pause()
    		RunAnimation:Stop()
            JumpAnimation:Stop()
            ClimbAnimation:Stop()
            SwimAnimation:Stop()
    		FallAnimation:Stop()
    	end
end, false, Enum.KeyCode.LeftShift)

Here is the problem:
When player starts walking and then starts holding LeftShift, he will start playing run animation, but if player stops walking or running(WASD) and holds LeftShift, run animation will still play, which I don’t want to happen. When player’s current speed is 0 or player is in idle form, but he is still holding a button for running, he won’t play run animation. So, I need some sort of help, to detect or track player’s speed in the function for Input. I don’t want to use loops for tracking, because loops grow lag in the game.

I tried placing the function that tracks player’s speed in the function for Input. Though, once I release the shift button and start walking, my player won’t walk, he will run (play run animation).

Is there any possible way how am I going to track player’s speed? If you think there is, please reply!
Thank you.

6 Likes

To answer the question about finding out if the player is still holding a button, use IsKeyDown of the UserInputService. To get a players speed you can do something like this:

local speed = (character.HumanoidRootPart.Velocity).magnitude;
1 Like

You can use UserInputService.InputBegan and UserInputService.InputEnded to do this. Here is an example:

local UserInputService = game:GetService("UserInputService")

UserInputService.InputBegan:Connect(function(inputObject, gameProcessed)
    if gameProcessed then return end

    if inputObject.KeyCode == Enum.KeyCode.LeftShift then
        -- start sprinting
    end
end

UserInputService.InputEnded:Connect(function(inputObject, gameProcessed)
    if gameProcessed then return end

    if inputObject.KeyCode == Enum.KeyCode.LeftShift then
        -- stop sprinting
    end
end
3 Likes
Character.Humanoid.Running:Connect(function(SpeedValue_4)
    SpeedValue_3 = SpeedValue_4
	print(SpeedValue_3)
end)

local function IsShiftKeyDown()
	return UserInputService:IsKeyDown(ShiftKey)
end

local function Input(input, gameProcessedEvent)
	if not IsShiftKeyDown() then
		print("NO")
	elseif IsShiftKeyDown() and SpeedValue_3 <= 1 then
		print("YES")
		RunAnimation:Stop()
		Running = false
		Character.Humanoid.WalkSpeed = SpeedValue_2
		CamIn:Play()
		CamOut:Pause()
	end
end

UserInputService.InputBegan:Connect(Input)

I tried something like this, but function always ‘updates’ when I ‘press’ any button on the keyboard or mouse. I also tried other events like InputEnded, which I thought would work, because when I hold shift and release the button where I’m walking(WASD), it will update and do stuff in the if statement, but it updates when I release another button, after releasing (WASD).

local speed = (character.HumanoidRootPart.Velocity).magnitude;

Getting players speed doesn’t really help me out, because it doesn’t update every time.

1 Like

What problems were you having with using UserInputService.InputEnded? Would you mind showing me the code you used?

If you only want it to update when you press shift then just check the InputObject.KeyCode is equal to shift. What is the end goal here? Are you trying to create a run script?

2 Likes

For example, when I start walking(holding WASD) and then start holding LeftShift, when I release WASD, it does nothing, until I press and release another button (It could be WASD again)

That’s right. The only problem is when I hold LeftShift and run with any of control buttons and when I release any of control buttons again (I’m still holding shift), my character will be in an idle form, but he will still play a run animation.

Here’s another option where you can track which keys are pressed and update the character/whatever you need on RenderStep.

-- Services
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local shiftKey = Enum.KeyCode.Shift
keyDown = {}

-- Local functions
local function inputBegan(input, gameProcessedEvent)
	if gameProcessedEvent then return end

	if (input.InputType = Enum.UserInputType.Keyboard) then
		keyDown[input.KeyCode] = true
	end
end

local function inputEnded(input, gameProcessedEvent)
	if gameProcessedEvent then return end

	if (input.InputType = Enum.UserInputType.Keyboard) then
		keyDown[input.KeyCode] = false
	end
end

local function updateControls()
	if keyDown[shiftKey] then
		-- Action here
	else
		-- Other action here
	end
end

-- Connections
UserInputService.InputBegan:Connect(inputBegan)
UserInputService.InputEnded:Connect(inputEnded)

RunService:BindToRenderStep("UpdateControls", 1, updateControls)
2 Likes

I doubt that checking the player speed each frame will affect performance too much, so could you do something like this?

local character = game.Players.LocalPlayer.Character;
game:GetService("RunService").RenderStepped:Connect(function(dt)
    -- Check if player is running.
    if (character ~= nil) then
        local speed = (character.HumanoidRootPart.Velocity).magnitude;
        if (speed == 0) then
            -- Stop animation.
        end
    end
end);
2 Likes
Character.Humanoid.Running:Connect(function(SpeedValue_4)
    SpeedValue_3 = SpeedValue_4
	print(SpeedValue_3)
end)

game:GetService("RunService").RenderStepped:Connect(function(dt)
    if (Character ~= nil) then
        local SpeedValue_3 = (Character.HumanoidRootPart.Velocity).Magnitude
       if (SpeedValue_3 == 0) and Running == true then
            Running = false
			Character.Humanoid.WalkSpeed = SpeedValue_2
			CamIn:Play()
			CamOut:Pause()
			RunAnimation:Stop()
        end
    end
end)

This could be the solution, but I still have a tiny question. If I add ‘print(STOP)’ inside the last if statement, it prints it just like in a loop. I’m wondering, does the whole code in the last if statement loops always until I release LeftShift?

RunService.RenderStepped is firing every frame. In other words it is firing around 60 times per second.

Not too sure what you’re asking here. If it checks if Running is equal to true then you set running to false, somewhere else there must also be setting Running to true or else that wouldn’t run.

game:GetService(“RunService”).RenderStepped:Connect(function(dt)

See: https://developer.roblox.com/en-us/api-reference/event/RunService/RenderStepped. Since you are listening to the RenderStepped event, the connected function will fire every frame. So yes, if the “if” condition is hit, it will be entered every frame until you release.

I also recommend using RunService:BindToRenderStep() instead of a connection to RenderStepped.

2 Likes
game:GetService('ContextActionService'):BindAction("ShiftSprint", function(actionName, inputState, inputObj)
	if inputState == Enum.UserInputState.Begin and SpeedValue_3 > 1 then
		Running = true
		Character.Humanoid.WalkSpeed = SpeedValue_1
		CamOut:Play()
		CamIn:Pause()
		RunAnimation:Play()

If I hold shift, running will be equal to true, so it connects to that function.

Using the ContextActionService is a much better idea for binding a function (action) to a specific input like pressing the Shift key.

You can then also listen and act upon the end of input:

local ContextActionService = game:GetService("ContextActionService")

local SHIFT_KEY = Enum.KeyCode.Shift

local function shiftSprint(actionName, inputState, inputObj)
	if inputState = Enum.UserInputState.InputBegan and SpeedValue_3 < 1 then
		Running = true
		Character.Humanoid.WalkSpeed = SpeedValue_1
		CamOut:Play()
		CamIn:Pause()
		RunAnimation:Play()
	elseif inputState = Enum.UserInputState.InputEnded and Running then
		-- stop running
	end
end

ContextActionService:BindAction("ShiftSprint", shiftSprint, false, SHIFT_KEY)

If you ever need to unbind the action use ContextActionService:UnbindAction("ShiftSprint"). Also, I’m not sure how you’re setting SpeedValue_3 but I just copied your conditional statement

4 Likes

Can’t you just check the speed when the player holds shift? I might not understand exactly what you’re saying. Is SpeedValue_3 the speed of the player?

Or if I am understanding correctly wouldn’t it be a better idea to have a isRunning variable but also a isRunAnimation variable to separate the animation from the running itself? This way you can still hold shift without the run animation playing when you’re at a stand still.

Honestly you shouldn’t be using RenderStepped for anything that isn’t a camera update. Especially not in this case. Use Stepped as it fires before physics simulation and is probably what you need, considering Humanoid movement is a physics-based task.

cc @Nucl3arPlays


@RetributionVI

This is highly dependent on the client and there is no guarantee of 60/second frames. Some users can have lower framerates and thus bring down the amount of times per-frame functions are ran.

2 Likes