New Bunny Hoppy Movement Code

In the AirMove() function change this block of code from this:

local wishSpeed = wishDir.Magnitude

wishSpeed *= moveSpeed

if wishDir ~= Vector3.new(0, 0, 0) then
	wishDir = wishDir.Unit
end

To this:

if wishDir ~= Vector3.new(0, 0, 0) then
	wishDir = wishDir.Unit
end

local wishSpeed = moveSpeed

We can just set wishSpeed to moveSpeed now because wishDir.Magnitude will always be 1 when the wishDir is a unit vector.

1 Like

Hi,

Can the model be updated with the latest code?

Thanks

One last question,

Is the default values accurate to CS:GO? Just wondering, if not I’ll play with the values. I asked this since in order to gain a slight bit more of speed you just have to hold either A or D and look the direction and you gain around 1-2 units of speed.

1 Like

No I just messed with the values until it was to my liking. I’ll be honest I’m not very good with vector math so I don’t even really know what changing each value does lol.

However I would not set sideStrafeSpeed greater than 1 otherwise you can easily gain speed super fast.

1 Like

The animation is surprisingly great, reminds me of CS GO, good jobšŸ‘

I noticed something while playing with this. It bugged me quite a bit, even though it was such a small thing that it took me a while to realize it was happening.

here’s the problem:

here’s a fixed version:

and here’s a side-by-side of the two:

for anyone wondering how to fix this here is the solution I used in the video above:

	local camY, camX, camZ = camera.CFrame:ToOrientation()
	local cameraCFrame = CFrame.new(camera.CFrame.p) * CFrame.Angles(0, camX, 0)
	local inputVel = Vector3.new(sideMove, 0, forwardMove)

	local wishDir = cameraCFrame:VectorToWorldSpace(inputVel)
	wishDir = Vector3.new(wishDir.X, 0, wishDir.Z)


you just apply this in the groundMove function and the airMove function and it should fix that problem.

Here’s the full default code but with the fix:

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

local player = game:GetService("Players").LocalPlayer
local camera = workspace.CurrentCamera

local character
local humanoidRootPart
local humanoid
local groundCheck

player.CharacterAdded:Connect(function(newCharacter)

	character = newCharacter

	humanoidRootPart = character:WaitForChild("HumanoidRootPart")
	humanoid = character:WaitForChild("Humanoid")

	groundCheck = script.GroundCheck:Clone()
	groundCheck.Weld.Part0 = humanoidRootPart
	groundCheck.Parent = character
	
end)

local Cmd = {

	upMove = false;
	downMove = false;

	leftMove = false;
	rightMove = false;

	lastUp = false;
	lastLeft = false;

	jump = false;
	justPressed = false;

	crouch = false;
	cjustPressed = false;

}

local function onUp(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.upMove = true
		Cmd.lastUp = true

	elseif inputState == Enum.UserInputState.End then

		Cmd.upMove = false

	end

end

local function onLeft(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.leftMove = true
		Cmd.lastLeft = true

	elseif inputState == Enum.UserInputState.End then

		Cmd.leftMove = false

	end

end

local function onDown(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.downMove = true
		Cmd.lastUp = false

	elseif inputState == Enum.UserInputState.End then

		Cmd.downMove = false

	end

end

local function onRight(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.rightMove = true
		Cmd.lastLeft = false

	elseif inputState == Enum.UserInputState.End then

		Cmd.rightMove = false

	end

end

local function onJump(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.jump = true
		Cmd.justPressed = true

	elseif inputState == Enum.UserInputState.End then

		Cmd.jump = false

	end

end

local function onCrouch(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.crouch = true
		Cmd.cjustPressed = true

	elseif inputState == Enum.UserInputState.End then

		Cmd.crouch = false

	end

end

ContextActionService:BindAction("Up", onUp, false, "w")
ContextActionService:BindAction("Left", onLeft, false, "a")
ContextActionService:BindAction("Down", onDown, false, "s")
ContextActionService:BindAction("Right", onRight, false, "d")
ContextActionService:BindAction("Jump", onJump, false, Enum.KeyCode.Space)
ContextActionService:BindAction("Crouch", onCrouch, false, "c")


local moveSpeed = 30
local runAccel = 5
local runDeaccel = 5
local airAccel = 2.5
local airDeaccel = 2.5
local sideStrafeAccel = 100
local sideStrafeSpeed = 1
local friction = 8
local airFriction = 3
local maxSpeed = 30

local playerVel = Vector3.new(0, 0, 0)
local forwardMove = 0
local sideMove = 0

local grounded = false

local wishJump = false
local canJump = true
local jumpBuffer = 0.1
local holdJumpToBhop = true


local function Update(deltaTime)	

	if not humanoidRootPart then return end
	
	if Cmd.leftMove and Cmd.rightMove then

		if Cmd.lastLeft then sideMove = -1 else sideMove = 1 end

	else

		if Cmd.leftMove then sideMove = -1 elseif Cmd.rightMove then sideMove = 1 else sideMove = 0 end

	end

	if Cmd.upMove and Cmd.downMove then

		if Cmd.lastUp then forwardMove = -1 else forwardMove = 1 end

	else

		if Cmd.upMove then forwardMove = -1 elseif Cmd.downMove then forwardMove = 1 else forwardMove = 0 end

	end

	QueueJump()
	grounded = CheckGround()

	if grounded then

		GroundMove()

	else

		AirMove()

	end

	local newDir = Vector3.new()
	if playerVel ~= Vector3.new(0, 0, 0) then newDir = playerVel.Unit end

	local newSpeed = playerVel.Magnitude
	newSpeed = math.clamp(newSpeed, 0, maxSpeed)

	humanoid.WalkSpeed = newSpeed
	humanoid:Move(newDir, false)
end

RunService.RenderStepped:Connect(Update)


function QueueJump()

	if holdJumpToBhop then

		wishJump = Cmd.jump
		return

	end

	if Cmd.jump and not wishJump and Cmd.justPressed then

		wishJump = true
		Cmd.justPressed = false

	end

	if not Cmd.jump then

		wishJump = false

	end

end


function CheckGround()

	local connection = groundCheck.Touched:Connect(function()end)

	for index, value in ipairs(groundCheck:GetTouchingParts()) do

		if value:IsA("BasePart") and value.Parent ~= character and value.CanCollide then

			connection:Disconnect()
			return true

		end

	end

	if humanoid:GetState() == Enum.HumanoidStateType.Climbing then

		return true

	end

	connection:Disconnect()
	return false

end


function AirMove()

	if forwardMove ~= 0 then

		ApplyFriction(1, true)

	end

	local accel
	
	local camY, camX, camZ = camera.CFrame:ToOrientation()
	local cameraCFrame = CFrame.new(camera.CFrame.p) * CFrame.Angles(0, camX, 0)
	local inputVel = Vector3.new(sideMove, 0, forwardMove)

	local wishDir = cameraCFrame:VectorToWorldSpace(inputVel)
	wishDir = Vector3.new(wishDir.X, 0, wishDir.Z)
	local wishSpeed = wishDir.Magnitude

	wishSpeed *= moveSpeed

	if wishDir ~= Vector3.new(0, 0, 0) then

		wishDir = wishDir.Unit

	end

	if playerVel:Dot(wishDir) < 0 then

		accel = airDeaccel

	else

		accel = airAccel

	end

	if forwardMove == 0 and sideMove ~= 0 then

		if wishSpeed > sideStrafeSpeed then

			wishSpeed = sideStrafeSpeed

		end

		accel = sideStrafeAccel

	end

	Accelerate(wishDir, wishSpeed, accel)

end


function GroundMove()

	if not wishJump and canJump then

		ApplyFriction(1, false)

	else

		ApplyFriction(0, false)

	end

	local camY, camX, camZ = camera.CFrame:ToOrientation()
	local cameraCFrame = CFrame.new(camera.CFrame.p) * CFrame.Angles(0, camX, 0)
	local inputVel = Vector3.new(sideMove, 0, forwardMove)

	local wishDir = cameraCFrame:VectorToWorldSpace(inputVel)
	wishDir = Vector3.new(wishDir.X, 0, wishDir.Z)

	if wishDir ~= Vector3.new(0, 0, 0) then

		wishDir = wishDir.Unit

	end

	local wishSpeed = wishDir.Magnitude

	wishSpeed *= moveSpeed

	Accelerate(wishDir, wishSpeed, runAccel)

	playerVel = Vector3.new(playerVel.X, 0, playerVel.Z)

	if wishJump and canJump then

		humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
		wishJump = false
		canJump = false

		local co = coroutine.create(function()

			local begin = tick()

			while tick() - begin < jumpBuffer do

				RunService.Heartbeat:Wait()

			end

			canJump = true

		end)

		coroutine.resume(co)

	end

end


function ApplyFriction(t, inAir)

	local vec = Vector3.new(playerVel.X, 0, playerVel.Z)
	local speed = vec.Magnitude
	local drop = 0
	local control = 0
	local newFriction = inAir and airFriction or friction

	control = speed < runDeaccel and runDeaccel or speed
	drop = control * newFriction * RunService.Heartbeat:Wait() * t


	local newspeed = speed - drop

	if newspeed < 0 then

		newspeed = 0

	end

	if speed > 0 then

		newspeed /= speed

	end

	local x = playerVel.X * newspeed
	local z = playerVel.Z * newspeed
	playerVel = Vector3.new(x, 0, z)

end


function Accelerate(wishDir, wishSpeed, accel)

	local currentspeed = playerVel:Dot(wishDir)
	local addSpeed = wishSpeed - currentspeed

	if addSpeed <= 0 then return end

	local accelSpeed = accel * RunService.Heartbeat:Wait() * wishSpeed

	if accelSpeed > addSpeed then

		accelSpeed = addSpeed

	end

	local x = playerVel.X + accelSpeed * wishDir.X
	local z = playerVel.Z + accelSpeed * wishDir.Z
	playerVel = Vector3.new(x, 0, z)

end

It was just a small problem but it really urked me. Hope that helped anyone else who was struggling to fix this problem :smile:

5 Likes

Is there any way to make the movement code work with external forces?
Like, for example: If you’re rocket jumping then you don’t keep any of the acceleration from the blast and slow down mid-air, is there a way to be able to boost off the rocket jump?

I added this for game controller support. i could not figure out mobile support though. I just use default humanoid movement for mobile in my game

local function onMoveThumbstick(_, _, inputObject)
	local x = math.round(inputObject.Position.X)
	local y = math.round(inputObject.Position.Y)
	
	-- no left or right
	if x == 0 then
		onLeft("actionname", Enum.UserInputState.End)
		onRight("actionname", Enum.UserInputState.End)
	else
		-- right
		if x == 1 then
			onRight("actionname", Enum.UserInputState.Begin)
			onLeft("actionname", Enum.UserInputState.End)
		end
		--left
		if x == -1 then
			onLeft("actionname", Enum.UserInputState.Begin)
			onRight("actionname", Enum.UserInputState.End)
		end
	end

	-- no up or down
	if y == 0 then
		onUp("actionname", Enum.UserInputState.End)
		onDown("actionname", Enum.UserInputState.End)
	else
		--up
		if y == 1 then
			onUp("actionname", Enum.UserInputState.Begin)
			onDown("actionname", Enum.UserInputState.End)
		end
		--down
		if y == -1 then
			onDown("actionname", Enum.UserInputState.Begin)
			onUp("actionname", Enum.UserInputState.End)
		end
	end
end
ContextActionService:BindAction("MoveThumbstick", onMoveThumbstick, false, Enum.KeyCode.Thumbstick1)

2 Likes

@SnarlyZoo Thank you for posting this movement script, i love it in my game

1 Like

Yo thats sick, if you wanted to use the default PlayerModule controls you probably could so that way you could have all the different default Roblox input stuff. I always like making my own input code instead of using Roblox’s but I am pretty sure there is some sort of method called GetMoveVector() or something either in PlayerModule or PlayerModule:GetControls().

I don’t like how you can easily be fast by simply holding D and W, how do I fix that?

In the function AirMove() always apply friction. So change it from this:

if forwardMove ~= 0 then
	ApplyFriction(1, true)
end

to this:

ApplyFriction(1, true)

This keeps the bhopping functionality right?

Cause, when I played around with this, I couldn’t find any solutions to even fix this.

Btw I found a really helpful video on this on youtube

2 Likes

Yeah I would honestly follow his tutorial than use my code.

I made this script a while ago, and there’s a lot of things I would change, so I might remake it sometime.

1 Like

the crouch and sprint options are missing, are they supposed to be? i’m very confused (i don’t script)

In the video its just bhoping without crouch jumps

i’m trying to add sprinting and crouching to this script but crouching is very bugged? help would be appreciated

local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local player = game:GetService("Players").LocalPlayer
local camera = workspace.CurrentCamera

local character
local humanoidRootPart
local humanoid
local groundCheck

player.CharacterAdded:Connect(function(newCharacter)

	character = newCharacter

	humanoidRootPart = character:WaitForChild("HumanoidRootPart")
	humanoid = character:WaitForChild("Humanoid")

	groundCheck = script.GroundCheck:Clone()
	groundCheck.Weld.Part0 = humanoidRootPart
	groundCheck.Parent = character

end)

local Cmd = {

	upMove = false;
	downMove = false;

	leftMove = false;
	rightMove = false;

	lastUp = false;
	lastLeft = false;

	jump = false;
	justPressed = false;

	crouch = false;
	cjustPressed = false;

}

local function onUp(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.upMove = true
		Cmd.lastUp = true

	elseif inputState == Enum.UserInputState.End then

		Cmd.upMove = false

	end

end

local function onLeft(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.leftMove = true
		Cmd.lastLeft = true

	elseif inputState == Enum.UserInputState.End then

		Cmd.leftMove = false

	end

end

local function onDown(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.downMove = true
		Cmd.lastUp = false

	elseif inputState == Enum.UserInputState.End then

		Cmd.downMove = false

	end

end

local function onRight(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.rightMove = true
		Cmd.lastLeft = false

	elseif inputState == Enum.UserInputState.End then

		Cmd.rightMove = false

	end

end

local function onJump(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.jump = true
		Cmd.justPressed = true

	elseif inputState == Enum.UserInputState.End then

		Cmd.jump = false

	end

end

local moveSpeed = 16
local runAccel = 8
local runDeaccel = 8
local airAccel = 2.5
local airDeaccel = 2.5
local sideStrafeAccel = 100
local sideStrafeSpeed = 1
local friction = 8
local airFriction = 3
local maxSpeed = 30

local playerVel = Vector3.new(0, 0, 0)
local forwardMove = 0
local sideMove = 0

local grounded = false

local wishJump = false
local canJump = true
local jumpBuffer = 0.1
local holdJumpToBhop = false

local function onCrouch(actionName, inputState)

	if inputState == Enum.UserInputState.Begin then

		Cmd.crouch = true
		Cmd.cjustPressed = true
	elseif inputState == Enum.UserInputState.End then
		Cmd.crouch = false
	end

end

ContextActionService:BindAction("Up", onUp, false, "w")
ContextActionService:BindAction("Left", onLeft, false, "a")
ContextActionService:BindAction("Down", onDown, false, "s")
ContextActionService:BindAction("Right", onRight, false, "d")
ContextActionService:BindAction("Jump", onJump, false, Enum.KeyCode.Space)
ContextActionService:BindAction("Crouch", onCrouch, false, "c")

local Crouch = false

local function onInputBeganCrouch(input, gameProcessed)
	if not gameProcessed then
		if input.KeyCode == Enum.KeyCode.LeftControl then
			if moveSpeed == 16 then
				moveSpeed = 8
				maxSpeed = 10

				player.Character:FindFirstChild("Humanoid").HipHeight = -1
				Crouch = true
			end
		end
	end
end

local function onInputEndedCrouch(input, gameProcessed)
	if not gameProcessed then
		if input.KeyCode == Enum.KeyCode.LeftControl then
			if moveSpeed == 8 then
				moveSpeed = 16
				maxSpeed = 30
				
				player.Character:FindFirstChild("Humanoid").HipHeight = 0
				Crouch = false
			end
		end
	end
end

local function onInputBeganSprint(input, gameProcessed)
	if not gameProcessed then
		if input.KeyCode == Enum.KeyCode.LeftShift then
			if moveSpeed == 16 then
				if Crouch == false then
					moveSpeed = 25
					maxSpeed = 50
				end
			end
		end
	end
end

local function onInputEndedSprint(input, gameProcessed)
	if not gameProcessed then
		if input.KeyCode == Enum.KeyCode.LeftShift then
			if moveSpeed == 25 then
				if Crouch == false then
					moveSpeed = 16
					maxSpeed = 50
				end
			end
		end
	end
end

local function Update(deltaTime)
	
	if not humanoidRootPart then return end
	
	if Cmd.leftMove and Cmd.rightMove then

		if Cmd.lastLeft then sideMove = -1 else sideMove = 1 end

	else

		if Cmd.leftMove then sideMove = -1 elseif Cmd.rightMove then sideMove = 1 else sideMove = 0 end

	end

	if Cmd.upMove and Cmd.downMove then

		if Cmd.lastUp then forwardMove = -1 else forwardMove = 1 end

	else

		if Cmd.upMove then forwardMove = -1 elseif Cmd.downMove then forwardMove = 1 else forwardMove = 0 end

	end

	QueueJump()
	grounded = CheckGround()

	if grounded then

		GroundMove()

	else

		AirMove()

	end

	local newDir = Vector3.new()
	if playerVel ~= Vector3.new(0, 0, 0) then newDir = playerVel.Unit end

	local newSpeed = playerVel.Magnitude
	newSpeed = math.clamp(newSpeed, 0, maxSpeed)

	humanoid.WalkSpeed = newSpeed
	humanoid:Move(newDir, false)
	
	UserInputService.InputBegan:Connect(onInputBeganCrouch)
	UserInputService.InputEnded:Connect(onInputEndedCrouch)
	
	UserInputService.InputBegan:Connect(onInputBeganSprint)
	UserInputService.InputEnded:Connect(onInputEndedSprint)
	
	script.Speed.Value = moveSpeed
end

RunService.RenderStepped:Connect(Update)

function QueueJump()

	if holdJumpToBhop then

		wishJump = Cmd.jump
		return

	end

	if Cmd.jump and not wishJump and Cmd.justPressed then

		wishJump = true
		Cmd.justPressed = false

	end

	if not Cmd.jump then

		wishJump = false

	end

end


function CheckGround()

	local connection = groundCheck.Touched:Connect(function()end)

	for index, value in ipairs(groundCheck:GetTouchingParts()) do

		if value:IsA("BasePart") and value.Parent ~= character and value.CanCollide then

			connection:Disconnect()
			return true

		end

	end

	if humanoid:GetState() == Enum.HumanoidStateType.Climbing then

		return true

	end

	connection:Disconnect()
	return false

end


function AirMove()

	if forwardMove ~= 0 then

		ApplyFriction(1, true)

	end

	local accel
	local cameraCFrame = camera.CFrame
	local inputVel = Vector3.new(sideMove, 0, forwardMove)

	local wishDir = cameraCFrame:VectorToWorldSpace(inputVel)
	wishDir = Vector3.new(wishDir.X, 0, wishDir.Z)
	local wishSpeed = wishDir.Magnitude

	wishSpeed *= moveSpeed

	if wishDir ~= Vector3.new(0, 0, 0) then

		wishDir = wishDir.Unit

	end

	if playerVel:Dot(wishDir) < 0 then

		accel = airDeaccel

	else

		accel = airAccel

	end

	if forwardMove == 0 and sideMove ~= 0 then

		if wishSpeed > sideStrafeSpeed then

			wishSpeed = sideStrafeSpeed

		end

		accel = sideStrafeAccel

	end

	Accelerate(wishDir, wishSpeed, accel)

end


function GroundMove()

	if not wishJump and canJump then

		ApplyFriction(1, false)

	else

		ApplyFriction(0, false)

	end

	local cameraCFrame = camera.CFrame
	local inputVel = Vector3.new(sideMove, 0, forwardMove)

	local wishDir = cameraCFrame:VectorToWorldSpace(inputVel)
	wishDir = Vector3.new(wishDir.X, 0, wishDir.Z)

	if wishDir ~= Vector3.new(0, 0, 0) then

		wishDir = wishDir.Unit

	end

	local wishSpeed = wishDir.Magnitude

	wishSpeed *= moveSpeed

	Accelerate(wishDir, wishSpeed, runAccel)

	playerVel = Vector3.new(playerVel.X, 0, playerVel.Z)

	if wishJump and canJump then

		humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
		wishJump = false
		canJump = false

		local co = coroutine.create(function()

			local begin = tick()

			while tick() - begin < jumpBuffer do

				RunService.Heartbeat:Wait()

			end

			canJump = true

		end)

		coroutine.resume(co)

	end

end


function ApplyFriction(t, inAir)

	local vec = Vector3.new(playerVel.X, 0, playerVel.Z)
	local speed = vec.Magnitude
	local drop = 0
	local control = 0
	local newFriction = inAir and airFriction or friction

	control = speed < runDeaccel and runDeaccel or speed
	drop = control * newFriction * RunService.Heartbeat:Wait() * t


	local newspeed = speed - drop

	if newspeed < 0 then

		newspeed = 0

	end

	if speed > 0 then

		newspeed /= speed

	end

	local x = playerVel.X * newspeed
	local z = playerVel.Z * newspeed
	playerVel = Vector3.new(x, 0, z)

end


function Accelerate(wishDir, wishSpeed, accel)

	local currentspeed = playerVel:Dot(wishDir)
	local addSpeed = wishSpeed - currentspeed

	if addSpeed <= 0 then return end

	local accelSpeed = accel * RunService.Heartbeat:Wait() * wishSpeed

	if accelSpeed > addSpeed then

		accelSpeed = addSpeed

	end

	local x = playerVel.X + accelSpeed * wishDir.X
	local z = playerVel.Z + accelSpeed * wishDir.Z
	playerVel = Vector3.new(x, 0, z)

end
1 Like

ik this is a pretty old post but its the only one that looks promising… the camera isnt moving and in its ā€œdefaultā€ position???

I have the exact same error, however, when I play in Studio it doesn’t affect anything, yet my Character cannot move when playtesting in an actual roblox game.