How to detect that player is on the ground when they want to jump?

Hello! Im currently working on the Advanced Run script, and there is acceleration system. You run faster as you gain more acceleration, you can currently do that by 2 ways:

  1. Run nonstop on the ground
  2. Jump while running

And first way seems to work alright with this part of script:

				if humanoid.MoveDirection.Magnitude > 0 and not braking then -- if humanoid moves and not braking
					if humanoid:GetState() == Enum.HumanoidStateType.Running then 
						accelerationPercent.Value = math.clamp(accelerationPercent.Value+1, 0, 100)
					end
				end

Although, when i tried to make the same for jump with checking humanoid’s state, for some reason it won’t add acceleration after jump:

	player.CharacterAdded:Connect(function(character)
		local hrp:Part = character:FindFirstChild("HumanoidRootPart")
		local humanoid:Humanoid = character:WaitForChild("Humanoid")

		--onJumpRequestRemote.OnServerEvent:Connect(function(plr)
		--if plr ~= player then return nil end

		humanoid:GetPropertyChangedSignal("Jump"):Connect(function() print(1)
			local humState = humanoid:GetState() 

			if humState ~= Enum.HumanoidStateType.Freefall and humanoid.MoveDirection.Magnitude > 0 and not braking then 
				if not jumpAccelerationCooldown then 
					jumpAccelerationCooldown = true
					accelerationPercent.Value = math.clamp(accelerationPercent.Value+3, 0, 100)
					task.wait(0.2)
					jumpAccelerationCooldown = false
				end
			else
				print(humState)
			end
		end)
	end)

It works like more than 50% of time, but i hope to find more accurate way so i will appreciate any help!

2 Likes

a way to check if a player is touching the ground is to check if the humanoid’s FloorMaterial is set to anything other than Enum.Material.Air. i’m not sure how i would go about fixing the script since i don’t see the full version of it, but i’d reckon replacing if humState ~= Enum.HumanoidStateType.Freefall with if humanoid.FloorMaterial ~= Enum.Material.Air

2 Likes

Hello! I’ve also discovered this when testing but thought it will be bad because it seemed for me to always show “Air”, especially when you hold the button. I tried to swap and it seems to still be above 50% a little bit but it felt like it’s now even more unreliable :frowning:

About fixing the script, other parts aren’t related to it at all

2 Likes

hm… i see i see
what if instead of…

	player.CharacterAdded:Connect(function(character)
		local hrp:Part = character:FindFirstChild("HumanoidRootPart")
		local humanoid:Humanoid = character:WaitForChild("Humanoid")

		--onJumpRequestRemote.OnServerEvent:Connect(function(plr)
		--if plr ~= player then return nil end

		humanoid:GetPropertyChangedSignal("Jump"):Connect(function() print(1)
			local humState = humanoid:GetState() 

			if humState ~= Enum.HumanoidStateType.Freefall and humanoid.MoveDirection.Magnitude > 0 and not braking then 
				if not jumpAccelerationCooldown then 
					jumpAccelerationCooldown = true
					accelerationPercent.Value = math.clamp(accelerationPercent.Value+3, 0, 100)
					task.wait(0.2)
					jumpAccelerationCooldown = false
				end
			else
				print(humState)
			end
		end)
	end)

… it was something like this:

player.CharacterAdded:Connect(function(chara)
	local hum = chara:WaitForChild("Humanoid");
	hum.StateChanged:Connect(function(oldState, newState) -- upon the humanoid's state changing
		if newState == Enum.HumanoidStateType.Landed then --  upon landing from a jump...
			if hum.MoveDirection.Magnitude > 0 and not braking then -- are they moving and not braking?
				jumpAccelerationCooldown = true;
				accelerationPercent.Value = math.clamp(accelerationPercent.Value+3, 0, 100);
				task.wait(0.2);
				jumpAccelerationCooldown = false;
			end
		end
	end)
end)
1 Like

Can’t you just raycast down 2.1 studs, and if the raycast hits something, he is on the ground?

1 Like

Alrighty, i’ll try that tomorrow cuz i need to go sleep, i don’t remember if i did with StateChanged exactly yet

1 Like

Yeah i’ve thought of that, but i didn’t tried yet. I just have a concern: does Human Root Part stays on it’s place for small avatars? Because if the size of avatar will matter that won’t be good

I’m not sure if it does. Okay, how about when a player spawns in the game, you check their position with the position of a 1 stud, find the difference, and add .5 (half the size of the part), +.25 for checking if the player is on the ground, or .25 studs near it.

You will need to save this value, and use it whenever you need to check if they are on the ground.

NOTE: You will need to check the distance to the part to the humanoid rootpart every time the character respawns, because if they change their avatar on the website, and reset their character, it will apply the new changes. That could mean the distance would differ from what it should be, and cause some problems.

1 Like

I tried it with “oldState == running and newState == jumping” and your way, but still seems to not work accurate. Even though about your way, i need it to be when the player jump, not when the player lands, i was trying to recreate how real acceleration physic works, and it supposed to increace when you’re pushing off the ground, you know? :sweat_smile:

I actually think it’s just humanoid state parameter is not exactly accurate… or it’s a little difference between client and server side…? Im doing that on server side as i don’t want those calculations to be exploited :upside_down_face:

1 Like

Hmm… changing it specifically for a player could be an idea. Maybe on spawn, when player hits the floor (i could check it when humanoid lands on spawn), i’d need to make a ray down to the floor and it’s length and like + ~0.05-0.25 of a stud for accuracy and use it? Because i didn’t quite understood what you meant with difference between something and adding… :sweat_smile:

Yeah, that’s pretty much what I said. Just check how high off the ground he is and add a bit.

2 Likes

ClientScript

humanoid:GetPropertyChangedSignal("FloorMaterial"):Connect(function()
	rns.Stepped:Wait() --needs a pause
	
	if (humanoid.FloorMaterial)==Enum.Material.CrackedLava then
		humanoid.Health=0
	end
end)

or

local ray = Ray.new(character.HumanoidRootPart.Position, Vector3.new(0, -10, 0))
local hit = workspace:FindPartOnRay(ray, character)

if hit and hit.Material == Enum.Material.CrackedLava then
	humanoid.Health = 0
end

Easy way to tell what the player is standing on, will even do air.

1 Like

Soo like yesterday i finished my script, now it looks like that:

	player.CharacterAdded:Connect(function(character)
		local hrp:Part = character:FindFirstChild("HumanoidRootPart")
		local humanoid:Humanoid = character:WaitForChild("Humanoid")
		
		local rayParams = RaycastParams.new()
		rayParams.FilterDescendantsInstances = {character}
		rayParams.FilterType = Enum.RaycastFilterType.Exclude
		rayParams.IgnoreWater = true
		
		local groundRayLength = nil
		
		task.spawn(function()
			repeat task.wait(0.01) until humanoid.FloorMaterial ~= Enum.Material.Air
			
			task.wait(0.5)
			
			local rayResult = workspace:Raycast(hrp.Position, -(hrp.CFrame.UpVector.Unit * 25), rayParams)
			groundRayLength = Vector3.new(0, -((rayResult.Position - hrp.Position).Magnitude) - 2.25, 0)
			
			print(groundRayLength)
			toggleMovementRemote:FireClient(player, true)
		
			humanoid:GetPropertyChangedSignal("Jump"):Connect(function() 
				local humState = humanoid:GetState() 
				local isOnGround = workspace:Raycast(hrp.Position, groundRayLength, rayParams)

				if (humState ~= Enum.HumanoidStateType.Freefall or isOnGround) and 
					humanoid.MoveDirection.Magnitude > 0 and not braking then

					if not jumpAccelerationCooldown then 
						jumpAccelerationCooldown = true
						accelerationPercent.Value = math.clamp(accelerationPercent.Value+3, 0, 100)
						task.wait(0.25)
						jumpAccelerationCooldown = false
					end
				end
			end)
		end)

Instead of checking when player landed (it was working not accurately) i did that in local script it disables movement, checks the distance between humanoid root and floor, adds a little bit of studs, and uses the said distance for calculation. I don’t know if i should use the freefall check with it both, if it has any difference or sense? But i have to say, it is really better than previous versions, although a miss can occur rarely if i don’t add too much. But there comes another issue that it can add speed while player isn’t on the ground, oof

1 Like

I’m not sure, but there are a few cases where if

(humState ~= Enum.HumanoidStateType.Freefall or isOnGround) and humanoid.MoveDirection.Magnitude > 0 and not braking then

may not work as intended. (If he is swimming, climbing, etc.)

I don’t think checking if he is freefalling or not will do anything, as the IsOnGround will essentially check if he is in the air or not.

What is the “braking”?

groundRayLength = Vector3.new(0, -((rayResult.Position - hrp.Position).Magnitude) - 2.25, 0)

Is it supposed to be 2.25 studs?
If -((rayResult.Position - hrp.Position).Magnitude) is the distance from player to ground, then -2.25 seems like a bit much. I would do .5 or less.

1 Like

What do you mean by “may not work as intented”? Because script fires when Jump property changes, not just everytime. Or do you think like when you’re jumping on water or jumping off ladder it would be unwanted?

Braking is when you want to drop your acceleration without having to stay still, you just hold F and keep moving.

And yeah, 2.25 is a bit big, but it seems to work more consistently, i thought it will be enough but still there are some cases when it doesn’t get floor underneath for some reason.

Yesh, if you jump on water, or jump off a ladder, you may have have increased speed when you don’t want it, or vice versa.

1 Like

Oh, well that’s fair including there’s a few other states… although i don’t have water in my game and terrain at all, it’s probably useless. I just had a thought that i cannot explain, maybe that wasn’t logical at all but my brain thought it was a great idea… :sob: Short things short i tried to fire this function by 3 different ways,. but it was the same result, it checked both of “if’s”.

So it seems it needs only one thing accurately checking if player is on the ground, and among all stuff i tried (state, floor material, raycasting), last one in list seems the best right now.

1 Like

Send a raycast to each outer edge of the feet

let chatgpt cook!!!

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

local RAYCAST_DISTANCE = 1 -- Adjust this value as needed
local RAYCAST_PARAMS = RaycastParams.new()
RAYCAST_PARAMS.FilterType = Enum.RaycastFilterType.Blacklist

local function GetCorners(part)
	local size = part.Size / 2
	local cf = part.CFrame
	return {
		cf * Vector3.new(-size.X, -size.Y, -size.Z),
		cf * Vector3.new( size.X, -size.Y, -size.Z),
		cf * Vector3.new(-size.X,  size.Y, -size.Z),
		cf * Vector3.new( size.X,  size.Y, -size.Z),
		cf * Vector3.new(-size.X, -size.Y,  size.Z),
		cf * Vector3.new( size.X, -size.Y,  size.Z),
		cf * Vector3.new(-size.X,  size.Y,  size.Z),
		cf * Vector3.new( size.X,  size.Y,  size.Z)
	}
end

local function GetOuterEdges(part)
	local corners = GetCorners(part)
	return {
		{corners[1], corners[2]}, -- Bottom front
		{corners[1], corners[3]}, -- Left front
		{corners[2], corners[4]}, -- Right front
		{corners[5], corners[6]}, -- Bottom back
		{corners[5], corners[7]}, -- Left back
		{corners[6], corners[8]}  -- Right back
	}
end

local function CreateEdgePart(startPos, endPos, parent)
	local edge = Instance.new("Part")
	edge.Anchored = false
	edge.CanCollide = false
	edge.Material = Enum.Material.Neon
	edge.Color = Color3.new(1, 0, 0) -- Red color for visibility
	edge.Size = Vector3.new(0.1, 0.1, (startPos - endPos).Magnitude)
	edge.CFrame = CFrame.new((startPos + endPos)/2, endPos)
	edge.Parent = parent

	local weld = Instance.new("WeldConstraint")
	weld.Part0 = edge
	weld.Part1 = parent
	weld.Parent = edge

	return edge
end

local function IsCornerGrounded(corner, character)
	RAYCAST_PARAMS.FilterDescendantsInstances = {character}
	local raycastResult = workspace:Raycast(corner, Vector3.new(0, -RAYCAST_DISTANCE, 0), RAYCAST_PARAMS)
	return raycastResult ~= nil
end

local function VisualizeFootEdges(character)
	local leftFoot = character:FindFirstChild("LeftFoot")
	local rightFoot = character:FindFirstChild("RightFoot")

	if leftFoot and rightFoot then
		local leftFootEdges = GetOuterEdges(leftFoot)
		local rightFootEdges = GetOuterEdges(rightFoot)

		for _, edge in ipairs(leftFootEdges) do
			CreateEdgePart(edge[1], edge[2], leftFoot)
		end

		for _, edge in ipairs(rightFootEdges) do
			CreateEdgePart(edge[1], edge[2], rightFoot)
		end
	end
end

local function CheckGrounded(character, isOnGround)
	local leftFoot = character:FindFirstChild("LeftFoot")
	local rightFoot = character:FindFirstChild("RightFoot")

	if leftFoot and rightFoot then
		local leftCorners = GetCorners(leftFoot)
		local rightCorners = GetCorners(rightFoot)
		local groundedCorners = 0

		for _, corner in ipairs(leftCorners) do
			if IsCornerGrounded(corner, character) then
				groundedCorners = groundedCorners + 1
			end
		end

		for _, corner in ipairs(rightCorners) do
			if IsCornerGrounded(corner, character) then
				groundedCorners = groundedCorners + 1
			end
		end

		local isGrounded = groundedCorners > 0
		if isGrounded then
			print('player is on ground')
		else
			print('player is in air')
		end

		return isOnGround, groundedCorners
	end

	return isOnGround, 0
end

local function OnCharacterAdded(character)
	VisualizeFootEdges(character)

	local isOnGround = false
	RunService.Heartbeat:Connect(function()
		isOnGround = CheckGrounded(character, isOnGround)
	end)
end

local player = Players.LocalPlayer
player.CharacterAdded:Connect(OnCharacterAdded)

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