Disable holding jump button?

Rather than outright remove jumping for the fighting game I’m making, I’d like to make it so that each jump requires an individual input rather than a single, held input.

Is there an easy way to do this by messing with the PlayerModule, or do I need to implement some ugly kind of hard-coded cooldown?

Thanks for reading.

6 Likes

Are you trying to disable auto jump? There is property in humanoid for it.

1 Like

That just determines whether the player will automatically jump when running into a part, not whether or not they can hold the jump button to auto jump.

2 Likes

Right, and unless I’m mistaken, that property only applies when playing on a mobile device.

1 Like

The effort is appreciated, but that’s probably the furthest thing from what I’m looking for.
You know how you can jump repeatedly by holding spacebar?
I want to mess with default controls in such a way that each jump requires its own spacebar press, rather than being able to jump repeatedly by holding it down.

1 Like

I think I have what you’re looking for:

local player = game.Players.LocalPlayer
local humanoid = player.CharacterAdded:wait():WaitForChild("Humanoid")
local UserInputService = game:GetService("UserInputService")
local jump = false
UserInputService.JumpRequest:Connect(function()
	if humanoid.FloorMaterial == Enum.Material.Air then
		jump = true
		humanoid.JumpPower = 0
		repeat wait() until humanoid.FloorMaterial ~= Enum.Material.Air
		jump = false
	end
end)
UserInputService.InputBegan:connect(function(inputObject)
	if inputObject.KeyCode == Enum.KeyCode.Space then
		if jump == false then
			humanoid.JumpPower = 50
			humanoid.Jump = true
		end
	end
end)
21 Likes

I used this code and it works as well. (This should be in a local script inside of starter character scripts)

--Services
local inputService = game:GetService("UserInputService")
local players = game:GetService("Players")

--Variables
local player = players.LocalPlayer
local character = player.Character

if not character or not character.Parent then
	character = player.CharacterAdded:Wait()
end

local humanoid = character:WaitForChild("Humanoid")

local jumpDebounce = true

--Events
inputService.InputBegan:Connect(function(input, gpe)
	if not gpe then
		if input.KeyCode == Enum.KeyCode.Space and jumpDebounce then
			wait(.01)
			jumpDebounce = false
			humanoid.JumpHeight = 0
		end
	end
	
end)

inputService.InputEnded:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.Space then
		jumpDebounce = true
		humanoid.JumpHeight = 11
	end
end)
6 Likes

none of these scripts have support for other jump inputs like gamepad and mobile. idk if this is solved…

a real solution would be if it relied on UserInputService’s JumpRequest

2 Likes

After many, many, many HOURS of experimenting in Roblox Studio and looking through the Roblox Creator Documentation, I’ve come up with a solution that works for multiple input types, including:

  • Keyboard

  • Touch-screen (mobile, laptops with touch-screen enabled, etc.)

  • Controller

  • With the possibility to be compatible with more input types. Please let me know if I’ve missed any other ones that I should add. For example, I am not familiar with the KeyCode that’s used to make the Character jump when using VR.

I’ll explain how everything works, along with brief details about some of the solutions I tried that didn’t end up working, but first, here’s the completed code (that would ideally be placed in a LocalScript in the StarterPlayerScripts container):


Complete LocalScript code

-- (LocalScript Code) Place this into the StarterPlayerScripts container!
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local player = Players.LocalPlayer

---

local jumpKeybinds = {
	[1] = Enum.KeyCode.Space, -- Keyboard
	[2] = Enum.KeyCode.ButtonA -- Controller

--[[
Not necessary to define anything for touch-screen devices!
It works for touch-screen devices by checking the UserInputType later on.
--]]

}

---

local connection
local isJumping
local allowedToJump


local function OnCharacterLoad(Character)
	allowedToJump = true
	isJumping = nil
	
	local Humanoid = Character:WaitForChild("Humanoid")
	Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
	
	
	connection = Humanoid.StateChanged:Connect(function(oldState, newState)
		
		if newState == Enum.HumanoidStateType.Jumping then
			if isJumping ~= true then
				isJumping = true
				allowedToJump = false
				Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, false)
			end
			
		elseif newState == Enum.HumanoidStateType.Landed then
			isJumping = false
			
			local lastInputType = UserInputService:GetLastInputType()
			if lastInputType == Enum.UserInputType.Touch then
				allowedToJump = true
				Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
			end
		end
	end)
	
end

if player.Character then
	OnCharacterLoad(player.Character)
end

player.CharacterAdded:Connect(OnCharacterLoad)
player.CharacterRemoving:Connect(function()
	connection:Disconnect()
	connection = nil
end)


UserInputService.InputEnded:Connect(function(inputObject, gameProcessedEvent)
	
	if allowedToJump == false and not gameProcessedEvent then
		if table.find(jumpKeybinds, inputObject.KeyCode) then
			allowedToJump = true
			
			local Character = player.Character or player.CharacterAdded:Wait()
			local Humanoid = Character:WaitForChild("Humanoid")
			
			if Character and Humanoid then
				Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
			end
		end
	end
	
end)

Explaining how the LocalScript works

I tried a variety of approaches before managing to figure out a viable solution, such as:

  • Listening for UserInputService.JumpRequest to fire, then using Humanoid:SetStateEnabled() to enable or disable the ability to jump depending on the Humanoid.FloorMaterial. However, on its own, this was not able to detect whether or not the player had continuously held down the jump button since the previous request. As a result, it seemed that other events of the UserInputService may be necessary to make this work.

  • Using a combination of the previous strategy, I tried to use the InputBegan and InputEnded events of both the UserInputService and ImageButton in order to detect if a player was holding down one of the jump keys on keyboard / controller, or holding down the on-screen button for a touch-screen. This worked decently well with keyboard (and presumably controller), however, I kept encountering issues with the on-screen jump button. This might have worked as a viable solution, but I think that the strategies I ended up using in the completed code above turned out to be much more intuitive and reliable than this would have been.


With that context out of the way, now I’ll go through each section of the script!

local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local player = Players.LocalPlayer

First, some of the initially important services / objects are defined right away, for ease of access. The UserInputService will be used at the end of the script to check when the player lets go of the jump key for keyboard / controller.


---

local jumpKeybinds = {
	[1] = Enum.KeyCode.Space, -- Keyboard
	[2] = Enum.KeyCode.ButtonA -- Controller
}


--[[ NOTE: The next few lines of code were originally included in the
final product, but before I posted this, I realized that it is not necessary.
I've kept it here to provide more context for what I thought was necessary.
--]]

local jumpButton -- Touch-screens (mobile, laptops with touchscreen enabled, etc.)

task.spawn(function()
	local touchGui = PlayerGui:WaitForChild("TouchGui", 5)
	if touchGui then
		local touchControlFrame = touchGui:WaitForChild("TouchControlFrame")
		jumpButton = touchControlFrame:WaitForChild("JumpButton")
	end
end)

---

This section of code is dedicated to defining how each input type jumps. The jumpKeybinds table will be referenced in the function activated by the InputEnded event at the end of the script. The table includes the default keybinds for those input types, but if you’ve changed any to a custom keybind, then you can update the table to match the new one(s).

Click here for an explanation for the "jumpButton" variable, the few lines of code right below it, and a section of one of the upcoming functions that was also revised. This was removed from the completed version of the LocalScript since it wasn't necessary.

The “jumpButton” variable is where a reference to the on-screen jump button for touch-screen devices is supposed to be stored (and, side note, I learned how to reference the on-screen jump button based on the Cross-Platform Design article on the Roblox Creator Documentation site). Originally, I had used this to detect when a player pressed or let go of the button, however, it’s primarily there now (right before revising this) to make sure that everything would continue working if a laptop user, for example, turns off touch-screen (since the “jumpButton” would disappear, so the code would adjust accordingly).

I created a separate thread with task.spawn() so that the rest of the code would continue to run while it waits for the “JumpButton” to appear. Otherwise, anyone who doesn’t have touch-screen enabled for their device would have to wait for the duration of the timeout period (which is 5 seconds in this case) before the rest of the code in the LocalScript would run.


And it was at that moment after I finished writing that last paragraph that I realized this didn’t account for the probably extremely rare instances where a player would turn on the touch-screen capabilities mid-game… so I edited the script to accommodate that… and then I realized that I didn’t need to check for the button to exist at all!!! It turns out that I just needed to make sure that the last input type used was Enum.UserInputType.Touch because the on-screen jump button would only appear if that input type had been processed…

Anyway, before I removed it entirely, here’s what the section of one of the upcoming functions looked like (in specific, the conditional statement that checks if jumpButton ~= nil:

connection = Humanoid.StateChanged:Connect(function(oldState, newState)
		
		if newState == Enum.HumanoidStateType.Jumping then
			if isJumping ~= true then
				isJumping = true
				allowedToJump = false
				Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, false)
			end
			
		elseif newState == Enum.HumanoidStateType.Landed then
			isJumping = false
			
			local lastInputType = UserInputService:GetLastInputType()
			
			if jumpButton ~= nil and lastInputType == Enum.UserInputType.Touch then
				allowedToJump = true
				Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
			end
		end
	end)



local connection
local isJumping
local allowedToJump

These variables will be used in different functions within the script for the following purposes:

  • The “connection” is where the event connection for the Humanoid.StateChanged event will be stored. This will be disconnected every time the player’s Character needs to respawn and replaced with a brand new one (to be able to reference the Character’s brand new Humanoid). This ensures that the functionality of the script (requiring players to manually press the jump button again) persists after respawning. Otherwise, players could just respawn once and then they would be able to hold down the jump key, which would make this entire script pointless!

  • The “isJumping” variable will be equal to true or false, depending on whether or not the HumanoidStateType of the Character’s Humanoid changes to Jumping or Landed. This makes it easier to keep track of when a player is in the air.

  • The “allowedToJump” variable will be equal to true or false, depending on whether or not the player has already initiated a jump. This variable is SUPER IMPORTANT for keyboard / controller input types because it’s one of the things that helps make sure the player’s Character is not allowed to jump until they let go of the jump key and press it again.


local function OnCharacterLoad(Character)
	allowedToJump = true
	isJumping = nil
	
	local Humanoid = Character:WaitForChild("Humanoid")
	Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
	
	
	connection = Humanoid.StateChanged:Connect(function(oldState, newState)
		
		if newState == Enum.HumanoidStateType.Jumping then
			if isJumping ~= true then
				isJumping = true
				allowedToJump = false
				Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, false)
			end
			
		elseif newState == Enum.HumanoidStateType.Landed then
			isJumping = false
			
			local lastInputType = UserInputService:GetLastInputType()
			if lastInputType == Enum.UserInputType.Touch then
				allowedToJump = true
				Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
			end
		end
	end)
	
end

if player.Character then
	OnCharacterLoad(player.Character)
end

player.CharacterAdded:Connect(OnCharacterLoad)
player.CharacterRemoving:Connect(function()
	connection:Disconnect()
	connection = nil
end)

This next section contains the majority of the functionality. Because it can be quite overwhelming, I’ll also go through this a couple of lines of code at a time.


if player.Character then
	OnCharacterLoad(player.Character)
end

player.CharacterAdded:Connect(OnCharacterLoad)
player.CharacterRemoving:Connect(function()
	connection:Disconnect()
	connection = nil
end)

In order to activate the OnCharacterLoad function in the first place, we check if the player’s Character already exists. If it does, we call the function and send the player’s Character through to it. Otherwise, we use the CharacterAdded event of the Player object to listen for every time the Character respawns, and then activate the function when that happens.

The CharacterRemoving event after it is used to disconnect the connection that was created from the Humanoid.StateChanged event for the Character’s previous Humanoid object. I’ll explain more about that event once we get to it in the function.


local function OnCharacterLoad(Character)
	allowedToJump = true
	isJumping = nil
	
	local Humanoid = Character:WaitForChild("Humanoid")
	Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)

The function begins by updating the allowedToJump variable and the isJumping variable. This essentially “resets” it to its default values, making sure that the new Character model will be able to jump. That’s especially important in the case that the previous Character model was mid-air when it needed to respawn, because that means that those variables were never set back to the values that would indicate that the player is standing on the ground / an object.

Afterwards, we look for the new Character’s Humanoid object and immediately set the “Jumping” HumanoidStateType to true, which allows the player to jump. For reference, when it’s set to false, players using a keyboard or controller can press the jump keybinds, but nothing will happen. For touch-screen devices, the on-screen jump button doesn’t appear when it’s set to false, which is why it’s really important to make sure it is set to true from the moment that they’re supposed to be allowed to jump again.


connection = Humanoid.StateChanged:Connect(function(oldState, newState)

This stores the event connection created by Humanoid.StateChanged so that it can be disconnected at a later point when the player’s Character needs to respawn. newState refers to the current HumanoidStateType, and oldState is the previous one.

Ultimately, I settled on using Humanoid.StateChanged because it seems to be pretty consistent and it is also able to detect when a player jumped and then when they landed. This makes the process of enabling / disabling the ability to jump so much more seamless because you don’t need to guess the timings of how long the player is in the air for and when they land.


if newState == Enum.HumanoidStateType.Jumping then
    if isJumping ~= true then
        isJumping = true
        allowedToJump = false

        Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, false)
    end

-- The function continues...

This first conditional statement checks if the Character is currently Jumping. If so, we’ll make sure the isJumping variable isn’t already set to true (because otherwise, we wouldn’t need to do anything here). Then, we update that to true and then make the allowedToJump variable equal to false as a way of letting the rest of the script know that the player is already jumping.

Lastly, we set the “Jumping” HumanoidStateType to false, which makes the on-screen jump button disappear for touch-screen users, and also prevents any other buttons / keybinds / etc. from allowing the Character to jump.


elseif newState == Enum.HumanoidStateType.Landed then
    isJumping = false
			
    local lastInputType = UserInputService:GetLastInputType()
    if lastInputType == Enum.UserInputType.Touch then
        allowedToJump = true
        Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
    end
end

The second conditional statement checks if the Character has just landed on the ground / on an object that they can stand on. If so, we’ll update the isJumping variable to false since we know the player is no longer in the air.

Then, we’ll retrieve the most recent UserInputType that was used (such as Keyboard, Gamepad / controller, Touch, etc.) using UserInputService:GetLastInputType(). We’re checking if the last input type was Enum.UserInputType.Touch in order to determine if the jump button should be enabled again. As mentioned earlier, when the “Jumping” HumanoidStateType is false, the jump button does not appear on-screen for touch-screen users. As a result, we can immediately turn it back on to make sure that they can jump again.

Based on my testing using the Device Emulation feature in Roblox Studio, players shouldn’t be able to hold down the jump button for consecutive jumps (although they might be able to get close to doing so if they’re rocking their finger back and forth to immediately press the button when it appears again).

And to clarify why we’re not using this same idea of checking for the most recent input type for keyboard / controller / etc., that’s because this would not consider whether or not the player continued holding down the physical button on their device since the last time they jumped. It’s different for touch-screens because the digital button becomes completely invisible when the script disables the “Jumping” state for the Humanoid.


UserInputService.InputEnded:Connect(function(inputObject, gameProcessedEvent)
	
	if allowedToJump == false and not gameProcessedEvent then
		if table.find(jumpKeybinds, inputObject.KeyCode) then
			allowedToJump = true
			
			local Character = player.Character or player.CharacterAdded:Wait()
			local Humanoid = Character:WaitForChild("Humanoid")
			
			if Character and Humanoid then
				Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
			end
		end
	end
	
end)

At the very end of the script, we have a function that is run whenever the UserInputService.InputEnded event fires. This means that whenever a player releases a key on a keyboard, a button on a controller, etc., the function will run.

UserInputService.InputEnded:Connect(function(inputObject, gameProcessedEvent)
	
	if allowedToJump == false and not gameProcessedEvent then

The inputObject refers to the specific input that the user made. In this case, we’ll be referencing the InputObject.KeyCode property to figure out which key / button they pressed in order to compare it with the KeyCodes defined in the jumpKeybinds table at the start of the script.

gameProcessedEvent is a true or false value that basically says whether or not the input had anything to do with User Interface objects, such as TextBoxes or ImageButtons. We’re making sure it’s equal to false because if the player was typing in the chat, for example, they would probably be pressing the space bar a lot to add spaces between their words (and the space bar happens to be the default jump key for keyboards!) In cases like that, we don’t need to do anything because the player is not trying to make their Character jump.

Along with that, we also check if the allowedToJump variable is equal to false, which is manually updated within the OnCharacterLoad function to false after the player jumps. When it’s false, we know that the player is either in the air, or, they still have to let go of the jump key before they are allowed to jump again.

if table.find(jumpKeybinds, inputObject.KeyCode) then
    allowedToJump = true
			
    local Character = player.Character or player.CharacterAdded:Wait()
    local Humanoid = Character:WaitForChild("Humanoid")
			
    if Character and Humanoid then
        Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
    end
end

Afterwards, using table.find(), we’ll look through the jumpKeybinds table from the top of the script for the specific InputObject.KeyCode that the player released in order to fire the InputEnded event.

If it matches any of the KeyCodes, then we know that the player released the jump key / button. From there, we begin the process of allowing their Character to jump again. Well, first, we update the allowedToJump variable back to true so the rest of the script knows that the player is allowed to make their Character jump again. After that, we reference their current Character model and Humanoid, make sure both exist, and then use Humanoid:SetStateEnabled to allow the player’s Character to jump again.


And that’s everything! I did not realize how many hours it would take to create this and then to write out the entire explanation (might have taken more time to write the explanation, if I’m being honest, haha). It was a lot of fun to be able to create this, especially since it didn’t seem like an answer to this specific question for multiple input types had been thoroughly addressed anywhere on the Roblox Developer Forum yet.

If you have any questions even after all of this, feel free to let me know! Hope that this will be very useful for you and everyone else who may be looking for a solution to this question :sunglasses:

7 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.