Anyone knows how to make vanilla CS:GO bhopping in roblox studio? Not auto hop btw, if anyone knows tell me.
You’d have to replicate the source engine movement to do that. You could try looking at the actual source code and attempt to replicate it in Roblox.
but its better to not replicate it, because its bad in some aspects, and also as we know bhopping is a bug, it’s not supposed to work.
What i am asking it to make a bunnyhopping like it’s not even a bug on LUA.
I don’t think it’ll be a bug if it’s intended, but just try to remake something like this and see if it’s fine, not really sure what you mean by the “bad in some aspects”, also you need to use some camera functions with a movement vector (3 axes of -1, 0 or 1 depending on what keys are pressed) to get wishdir
I am doing this right now, and you are right. I do get wish dir, but also I implemented surfing (slope handling).
So, this is how I made bhopping:
local state = {
vel = Vector3.new(),
onGround = true,
groundNormal = Vector3.new(0,1,0),
inWater = false,
onLadder = false,
crouching = false,
jumpQueued = false,
jumpHeld = false,
canJump = true,
surf = false,
crouchSliding = false,
crouchSlideTimer = 0,
fallVel = 0,
landed = false,
lastLandTime = 0,
prevCrouch = false,
prevMoveDir = Vector3.new(), -- Added for counter-strafe
-- For counter-strafe tap logic:
axisHoldStart = {X = 0, Z = 0}, -- When did we start holding a direction on each axis
axisLastDir = {X = 0, Z = 0}, -- Last nonzero direction on each axis
axisTapTime = {X = 0, Z = 0}, -- How long the opposite was held before release
wasOnGround = true, -- Track previous onGround state for bunnyhop/counter-strafe logic
}
local input = {
moveDir = Vector3.new(),
jump = false,
crouch = false,
}
-- Input handling
local moveVector = Vector3.new()
local jumpPressed = false
local jumpPressedLast = false -- Track previous jump key state
local jumpConsumed = false -- Prevents holding space for auto-hop
local crouchPressed = false
is the variables we need.
then make jumpressed true when player uses spacebar and crouchpressed true when left control is pressed (via UserInputService.inputbegan).
Use the same but now with input ended. jumpPressed = false, jumpConsumed = false, crouchPressed = false.
this is how you get move direction:
local function getMoveDirection()
local dir = Vector3.new()
if UserInputService:IsKeyDown(Enum.KeyCode.W) then dir = dir + Vector3.new(0,0,-1) end
if UserInputService:IsKeyDown(Enum.KeyCode.S) then dir = dir + Vector3.new(0,0,1) end
if UserInputService:IsKeyDown(Enum.KeyCode.A) then dir = dir + Vector3.new(-1,0,0) end
if UserInputService:IsKeyDown(Enum.KeyCode.D) then dir = dir + Vector3.new(1,0,0) end
-- Camera relative
local cam = workspace.CurrentCamera
if cam then
local cf = cam.CFrame
local move = (cf.LookVector * -dir.Z + cf.RightVector * dir.X)
move = Vector3.new(move.X, 0, move.Z)
if move.Magnitude > 0 then
return move.Unit
end
end
return Vector3.new()
end
then make variables:
-- Main movement loop
local lastGrounded = true
local lastY = rootPart.Position.Y
make runservice.renderstepped and update the input, Only allow jump on the frame the key is pressed (no hold-to-bhop), Block further jumps until key is released, Store previous move direction for counter-strafe.
Make counter-strafe tap logic (Track axis hold times and tap durations).
local now = tick()
for _, axis in {"X", "Z"} do
local prev = state.axisLastDir[axis] or 0
local curr = input.moveDir[axis]
-- If we just started holding a direction (from 0 to nonzero)
if math.abs(prev) < 0.01 and math.abs(curr) > 0.5 then
state.axisHoldStart[axis] = now
end
-- If we just released a direction (from nonzero to 0)
if math.abs(prev) > 0.5 and math.abs(curr) < 0.01 then
state.axisTapTime[axis] = now - (state.axisHoldStart[axis] or now)
end
state.axisLastDir[axis] = curr
end
now check if on the ground with raycast.
make Water/Ladder detection
create Fall velocity
state.fallVel = rootPart.Velocity.Y
make landing detection, track previous onGround state for bunnyhop/counter-strafe logic.
Make crouch slide logic.
state.prevCrouch = state.crouching
state.crouching = input.crouch
SourceMovement:TryStartCrouchSlide(state, input)
apply movement
state.vel = SourceMovement:Move(state, input, dt)
Apply velocity to character and make crouching.
Clamp speed for sanity
Module Script:
-- SourceMovement ModuleScript
-- Implements Source-engine-like movement physics for Roblox characters, with bhop, surfing, and crouch sliding
local SourceMovement = {}
-- Tweakable parameters
local WALK_SPEED = 16
local CROUCH_SPEED = 8
local JUMP_POWER = 50
local CROUCH_JUMP_BOOST = 1.2
local AIR_ACCEL = 34 -- Increased for more bhop speed gain
local GROUND_ACCEL = 64
local FRICTION = 8
local AIR_FRICTION = 0.5
local SLOPE_SLIDE_ANGLE = 40
local SLOPE_SLOW_ANGLE = 20
local SURF_MIN_ANGLE = 10
local SURF_MAX_ANGLE = 80
local SURF_FRICTION = 0.01
local SURF_AIR_ACCEL = 60
local SURF_MAX_SPEED = 60
local LANDING_PENALTY_VEL = 60
local LANDING_PENALTY_TIME = 0.4
local WATER_SPEED = 8
local LADDER_SPEED = 10
local MAX_AIR_SPEED = 32 -- Increased for more bhop speed gain
local CROUCH_SLIDE_SPEED = 30
local CROUCH_SLIDE_TIME = 0.5
local CROUCH_SLIDE_FRICTION = 1
local COUNTERSTRAFE_TAP_THRESHOLD = 0.15 -- seconds, must tap opposite key within this time to trigger instant stop
local function getSlopeAngle(normal)
if normal.Y == 0 then return 90 end
return math.deg(math.acos(normal.Y))
end
function SourceMovement:IsSurfing(groundNormal, onGround, onLadder, inWater)
if not onGround or onLadder or inWater then return false end
local angle = getSlopeAngle(groundNormal)
return angle > SURF_MIN_ANGLE and angle < SURF_MAX_ANGLE
end
function SourceMovement:ApplyFriction(vel, dt, onGround, frictionOverride)
local friction = frictionOverride or (onGround and FRICTION or AIR_FRICTION)
local speed = vel.Magnitude
if speed ~= 0 then
local drop = speed * friction * dt
local newSpeed = math.max(speed - drop, 0)
return vel.Unit * newSpeed
end
return vel
end
function SourceMovement:Accelerate(vel, wishDir, wishSpeed, accel, dt, maxSpeed)
local currentSpeed = vel:Dot(wishDir)
local addSpeed = wishSpeed - currentSpeed
if addSpeed <= 0 then return vel end
local accelSpeed = accel * dt * wishSpeed
if accelSpeed > addSpeed then accelSpeed = addSpeed end
local newVel = vel + wishDir * accelSpeed
if maxSpeed and newVel.Magnitude > maxSpeed then
newVel = newVel.Unit * maxSpeed
end
return newVel
end
function SourceMovement:HandleSlope(vel, groundNormal)
local angle = getSlopeAngle(groundNormal)
if angle > SLOPE_SLIDE_ANGLE then
-- Slide down steep slopes
local slideDir = Vector3.new(groundNormal.X, 0, groundNormal.Z)
if slideDir.Magnitude > 0 then
slideDir = slideDir.Unit
return vel + slideDir * (angle - SLOPE_SLIDE_ANGLE)
end
elseif angle > SLOPE_SLOW_ANGLE then
-- Slow uphill
return vel * 0.7
end
return vel
end
function SourceMovement:HandleLandingPenalty(vel, fallVel, landed, lastLandTime, now)
if landed and fallVel < -LANDING_PENALTY_VEL and (now - lastLandTime) > 0.1 then
return vel * 0.5, now
end
return vel, lastLandTime
end
-- Counter-strafe helper
local function counterStrafeVelocity(vel, moveDir, prevMoveDir, axisTapTime, canCounterStrafe)
-- Only apply if not holding both directions (e.g. not holding A and D at the same time)
-- X axis: A (-1) and D (+1)
-- Z axis: W (-1) and S (+1)
local newVel = vel
if not canCounterStrafe then
return newVel
end
if prevMoveDir and moveDir then
-- Counter-strafe left/right (A/D)
if prevMoveDir.X > 0.5 and moveDir.X < -0.5 and math.abs(moveDir.X) > 0.5 and math.abs(prevMoveDir.X) > 0.5 then
-- D to A
newVel = Vector3.new(0, newVel.Y, newVel.Z)
elseif prevMoveDir.X < -0.5 and moveDir.X > 0.5 and math.abs(moveDir.X) > 0.5 and math.abs(prevMoveDir.X) > 0.5 then
-- A to D
newVel = Vector3.new(0, newVel.Y, newVel.Z)
end
-- Counter-strafe forward/back (W/S)
if prevMoveDir.Z > 0.5 and moveDir.Z < -0.5 and math.abs(moveDir.Z) > 0.5 and math.abs(prevMoveDir.Z) > 0.5 then
-- S to W
newVel = Vector3.new(newVel.X, newVel.Y, 0)
elseif prevMoveDir.Z < -0.5 and moveDir.Z > 0.5 and math.abs(moveDir.Z) > 0.5 and math.abs(prevMoveDir.Z) > 0.5 then
-- W to S
newVel = Vector3.new(newVel.X, newVel.Y, 0)
end
-- NEW: If you release the opposite key after counter-strafe (i.e. go from input to zero), stop instantly
-- Only if the opposite key was tapped (held for less than threshold)
-- X axis
if math.abs(prevMoveDir.X) > 0.5 and math.abs(moveDir.X) <= 0.01 then
if axisTapTime and axisTapTime.X and axisTapTime.X > 0 and axisTapTime.X <= COUNTERSTRAFE_TAP_THRESHOLD then
newVel = Vector3.new(0, newVel.Y, newVel.Z)
end
end
-- Z axis
if math.abs(prevMoveDir.Z) > 0.5 and math.abs(moveDir.Z) <= 0.01 then
if axisTapTime and axisTapTime.Z and axisTapTime.Z > 0 and axisTapTime.Z <= COUNTERSTRAFE_TAP_THRESHOLD then
newVel = Vector3.new(newVel.X, newVel.Y, 0)
end
end
end
return newVel
end
function SourceMovement:Move(state, input, dt)
-- state: {vel, onGround, groundNormal, inWater, onLadder, crouching, jumpQueued, jumpHeld, canJump, surf, crouchSliding, crouchSlideTimer, fallVel, landed, lastLandTime, prevMoveDir, axisTapTime, wasOnGround}
-- input: {moveDir, jump, crouch}
local vel = state.vel
local onGround = state.onGround
local groundNormal = state.groundNormal
local inWater = state.inWater
local onLadder = state.onLadder
local crouching = input.crouch
local jump = input.jump
local moveDir = input.moveDir
local dt = dt
local now = tick()
local surf = self:IsSurfing(groundNormal, onGround, onLadder, inWater)
state.surf = surf
-- Counter-strafe logic (only on ground and not bunnyhopping)
local canCounterStrafe = false
if onGround and state.wasOnGround then
canCounterStrafe = true
end
if canCounterStrafe then
vel = counterStrafeVelocity(vel, moveDir, state.prevMoveDir, state.axisTapTime, canCounterStrafe)
-- Reset axisTapTime after using it, so it only applies once per tap
if state.axisTapTime then
if math.abs(state.prevMoveDir and state.prevMoveDir.X or 0) > 0.5 and math.abs(moveDir.X) <= 0.01 then
state.axisTapTime.X = 0
end
if math.abs(state.prevMoveDir and state.prevMoveDir.Z or 0) > 0.5 and math.abs(moveDir.Z) <= 0.01 then
state.axisTapTime.Z = 0
end
end
end
-- Water/Ladder
if inWater then
vel = moveDir * WATER_SPEED
if jump then vel = vel + Vector3.new(0, JUMP_POWER * 0.5, 0) end
return vel
elseif onLadder then
vel = moveDir * LADDER_SPEED
if jump then vel = vel + Vector3.new(0, LADDER_SPEED, 0) end
return vel
end
-- Crouch sliding
if state.crouchSliding then
vel = self:ApplyFriction(vel, dt, true, CROUCH_SLIDE_FRICTION)
vel = self:Accelerate(vel, moveDir, CROUCH_SLIDE_SPEED, GROUND_ACCEL, dt, CROUCH_SLIDE_SPEED)
state.crouchSlideTimer = state.crouchSlideTimer - dt
if state.crouchSlideTimer <= 0 or not crouching then
state.crouchSliding = false
end
return vel
end
-- Surfing
if surf then
-- Frictionless, high air acceleration, can't jump
vel = self:ApplyFriction(vel, dt, true, SURF_FRICTION)
if moveDir.Magnitude > 0 then
vel = self:Accelerate(vel, moveDir.Unit, SURF_MAX_SPEED, SURF_AIR_ACCEL, dt, SURF_MAX_SPEED)
end
-- Project velocity onto surf plane
local surfNormal = groundNormal
vel = vel - surfNormal * vel:Dot(surfNormal)
return vel
end
-- Friction
vel = self:ApplyFriction(vel, dt, onGround)
-- Slope
if onGround then
vel = self:HandleSlope(vel, groundNormal)
end
-- Acceleration
local wishSpeed = crouching and CROUCH_SPEED or WALK_SPEED
local accel = onGround and GROUND_ACCEL or AIR_ACCEL
if not onGround then wishSpeed = math.min(wishSpeed, MAX_AIR_SPEED) end
if moveDir.Magnitude > 0 then
vel = self:Accelerate(vel, moveDir.Unit, wishSpeed, accel, dt, (onGround and nil or MAX_AIR_SPEED))
end
-- Jump (no auto-hop: must release and repress jump)
if jump and onGround and state.canJump then
vel = Vector3.new(vel.X, JUMP_POWER, vel.Z)
if crouching then
vel = vel * CROUCH_JUMP_BOOST
end
state.canJump = false
elseif not jump and onGround then
state.canJump = true
end
-- Air control (strafe jumping)
if not onGround and moveDir.Magnitude > 0 then
local airControl = 0.4 -- Slightly increased for more air control
local proj = vel:Dot(moveDir.Unit)
if proj < wishSpeed then
vel = vel + moveDir.Unit * AIR_ACCEL * dt * airControl
end
end
-- Inertia (momentum preserved)
-- Already handled by not resetting velocity
-- Landing penalty
vel, state.lastLandTime = self:HandleLandingPenalty(vel, state.fallVel, state.landed, state.lastLandTime, now)
return vel
end
function SourceMovement:TryStartCrouchSlide(state, input)
-- Start crouch slide if moving fast, on ground, and just pressed crouch
if state.onGround and not state.crouchSliding and input.crouch and not state.prevCrouch and state.vel.Magnitude > WALK_SPEED * 1.2 then
state.crouchSliding = true
state.crouchSlideTimer = CROUCH_SLIDE_TIME
end
end
return SourceMovement
Modify if you don’t like something, it’s not perfect and I only made it in like one hour.
also it’s still working like an auto hop, so you still have to modify it
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.