BHOP
Key RequiredUniversal

BHOP

Universal Scriptalphaneumericsalphaneumerics 8.4K views 1.2K executesUpdated Aug 6, 2025
5.0/ 5(1 votes)
10100% Positive
Lua Script•922 lines (25182 chars)
--- made by forsaken c:

workspace.Gravity = 100


local Config = {

	--[[General]]
	VISUALIZE_FEET_HB = false, -- Visualize Feet Hitbox
	VISUALIZE_COLLIDE_AND_SLIDE = false,
	STEP_OFFSET = 1.2,
	MASS = 16,
	AIR_FRICTION = 0.4,
	FRICTION = 6,
	GRAVITY = 10,
	JUMP_VELOCITY = 30,

	--[[Accel/Deccel]]
	GROUND_ACCEL = 14,
	GROUND_DECCEL = 10,
	AIR_ACCEL = 52,

	--[[General Speed]]
	AIR_SPEED = 42,
	RUN_SPEED = 32,
	WALK_SPEED = 10,
	CROUCH_SPEED = 10,

	--[[Advanced Speed]]
	AIR_MAX_SPEED = 36.5,        -- The speed at which AIR_MAX_SPEED_FRIC is applied.
	AIR_MAX_SPEED_FRIC = 3,      -- The initial friction applied at max speed
	AIR_MAX_SPEED_FRIC_DEC = .5, -- Amount multiplied to current max speed friction per 1/60sec
	MIN_SLOPE_ANGLE = 40,
	MAX_SLOPE_ANGLE = 75,

	--[[Misc (Don't worry about these)]]
	LEG_HEIGHT = 1.9+.3,
	TORSO_TO_FEET = 3.1+1.9,
	FEET_HB_SIZE = Vector3.new(1,0.1,1),
	TORSO_HB_SIZE = Vector3.new(3,1,3),
	FOOT_OFFSET_AMOUNT = 1.2
}

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

local ids = {
	"rbxassetid://7338942417",
	"rbxassetid://7338942345",
	"rbxassetid://7338942259",
	"rbxassetid://7338942180",
	"rbxassetid://7338942417",
}
local sounds = {}

for _, i in ipairs(ids) do
	local sound = Instance.new("Sound")
	sound.SoundId = i
	sound.Volume = 2
	sound.Parent = game.Workspace  -- Or another appropriate parent
	table.insert(sounds, sound)
end






export type Movement = {
	Keys: {W: number, A: number, S: number, D: number, Space: number},

	player: Player,
	character: Model,
	collider: Part,
	vispart: Part?,
	vis_coll_parts: {Part},
	config: MovementConfig,
	mover: LinearVelocity,
	up_mover: LinearVelocity,
	dt: number,
	states: MovementStates,
}

export type MovementConfig = {
	VISUALIZE_FEET_HB: boolean,
	VISUALIZE_COLLIDE_AND_SLIDE: boolean,
	LEG_HEIGHT: number,
	FEET_HB_SIZE: Vector3,
	TORSO_HB_SIZE: Vector3,

	MASS: number,
	GRAVITY: number,
	FRICTION: number,
	AIR_FRICTION: number,

	GROUND_ACCEL: number,
	GROUND_DECCEL: number,
	AIR_ACCEL: number,

	RUN_SPEED: number,
	WALK_SPEED: number,
	CROUCH_SPEED: number,
	AIR_SPEED: number,

	AIR_MAX_SPEED: number,
	AIR_MAX_SPEED_FRIC: number,
	AIR_MAX_SPEED_FRIC_DEC: number,

	JUMP_VELOCITY: number,

	STEP_OFFSET: number,
	TORSO_TO_FEET: number,

	MIN_SLOPE_ANGLE: number,
	MAX_SLOPE_ANGLE: number,
}

export type MovementStates = {
	grounded: boolean,
	air_friction: number,
	input_vec: number,
	surfing: boolean,
	jumping: boolean,
}

export type Vec3Mod = {x: number, y: number, z: number, ToVector3: (Vec3Mod) -> Vector3}
local Vec3Mod = {}
Vec3Mod.__index = Vec3Mod

function Vec3Mod.new(input: Vector3?) : Vec3Mod
	input = input or Vector3.zero
	return setmetatable({x = input.X, y = input.Y, z = input.Z}, Vec3Mod) :: Vec3Mod
end

function Vec3Mod:ToVector3()
	return Vector3.new(self.x, self.y, self.z)
end

local Movement: Movement = {}
local Physics = {} :: Movement
local Shared  : Movement= {}
local Trace : Movement= {}
local Collisions = {} :: Movement

local function visualizeRayResult(result: RaycastResult, origin: Vector3, direction: Vector3)
	local position = result and result.Position or (origin + direction)
	local distance = (origin - position).Magnitude
	local p = Instance.new("Part")
	p.Anchored = true
	p.CanCollide = false
	p.Size = Vector3.new(0.1, 0.1, distance)
	p.CFrame = CFrame.lookAt(origin, position)*CFrame.new(0, 0, -distance/2)
	p.Parent = workspace.Temp
	return p
end

local function flattenVectorAgainstWall(moveVector: Vector3, normal: Vector3)
	-- if magnitudes are 0 then just nevermind
	if moveVector.Magnitude == 0 and normal.Magnitude == 0 then
		return Vector3.zero
	end

	-- unit the normal (i its already normalized idk)
	normal = normal.Unit

	-- reflect the vector
	local reflected = moveVector - 2 * moveVector:Dot(normal) * normal
	-- add the reflection to the move vector = vector parallel to wall
	local parallel = moveVector + reflected

	-- if magnitude 0 NEVERMIND!!!
	if parallel.Magnitude == 0 then
		return Vector3.zero
	end

	-- reduce the parallel vector to make sense idk HorseNuggetsXD did all this thank u
	local cropped = parallel.Unit:Dot(moveVector.Unit) * parallel.Unit * moveVector.Magnitude
	return cropped
end

function Collisions:CollideAndSlide(wishedSpeed: Vector3)
	local mod = 1

	if wishedSpeed.Magnitude == 0 then
		return wishedSpeed
	end

	-- get input vector
	local inputVec = self.states.input_vec
	local newSpeed = wishedSpeed
	local hrp = self.player.Character.HumanoidRootPart

	-- raycast var
	local params = Shared.GetMovementParams(self)

	-- wished speed modifier
	wishedSpeed *= 2

	-- direction amount var
	local dirAmnt = 1.375 * (mod or 1)
	local mainDirAmnt = 1.55 * (mod or 1)

	-- stick var
	local isSticking = false
	local normals = {}
	local stickingDirections = {}
	local ldd = {dir = false, dist = false} -- lowest distance direction
	local partsAlreadyHit = {}

	-- destroy sticking visualizations
	if Config.VISUALIZE_COLLIDE_AND_SLIDE then
		for _, v in pairs(self.vis_coll_parts) do
			v:Destroy()
		end
	end

	local lookVecs = {
		Vector3.new(0, (-Config.TORSO_TO_FEET) + Config.STEP_OFFSET, 0),
		Vector3.new(0, 0, 0),
		Vector3.new(0, 2, 0)
	}

	for _, v in pairs(lookVecs) do
		local values = {}
		local hval = {}
		local currForDir
		local currSideDir
		local rayPos

		if typeof(v) == "Vector3" then
			rayPos = hrp.Position + v
		else
			rayPos = Vector3.new(hrp.Position.X, hrp.Parent[v].CFrame.Position, hrp.Position.Z)
		end

		-- right, front, back
		if inputVec.X > 0 then
			currForDir = hrp.CFrame.RightVector
			table.insert(values, currForDir)
			table.insert(hval, hrp.CFrame.LookVector * dirAmnt)
			table.insert(hval, -hrp.CFrame.LookVector * dirAmnt)
			-- left, front, back
		elseif inputVec.X < 0 then
			currForDir = -hrp.CFrame.RightVector
			table.insert(values, currForDir)
			table.insert(hval, hrp.CFrame.LookVector * dirAmnt)
			table.insert(hval, -hrp.CFrame.LookVector * dirAmnt)
		end

		-- back, left, right
		if inputVec.Z > 0 then
			currSideDir = -hrp.CFrame.LookVector
			table.insert(values, currSideDir)
			table.insert(hval, hrp.CFrame.RightVector * dirAmnt)
			table.insert(hval, -hrp.CFrame.RightVector * dirAmnt)
			-- front, left, right
		elseif inputVec.Z < 0 then
			currSideDir = hrp.CFrame.LookVector
			table.insert(values, currSideDir)
			table.insert(hval, hrp.CFrame.RightVector * dirAmnt)
			table.insert(hval, -hrp.CFrame.RightVector * dirAmnt)
		end


		if inputVec.Z == 0 and inputVec.X == 0 then
			values[1] = wishedSpeed.Unit
			table.insert(hval, CFrame.new(wishedSpeed.Unit).RightVector * dirAmnt)
			table.insert(hval, -CFrame.new(wishedSpeed.Unit).RightVector * dirAmnt)
		else
			table.insert(values, wishedSpeed.Unit * dirAmnt)
		end

		-- middle directions
		if currForDir and currSideDir then
			for a, b in pairs(values) do
				values[a] = b * mainDirAmnt
			end
			table.insert(values, (currForDir+currSideDir) * mainDirAmnt)
		else
			values[1] *= mainDirAmnt
			table.insert(values, (values[1] + hval[1]).Unit * mainDirAmnt)
			table.insert(values, (values[1] + hval[2]).Unit * mainDirAmnt)
			table.insert(values, hval[1])
			table.insert(values, hval[2])
		end

		for _, b in pairs(values) do
			if not b then continue end

			-- visualize ray using pos and direction
			if Config.VISUALIZE_COLLIDE_AND_SLIDE then
				table.insert(self.vis_coll_parts, visualizeRayResult(false, rayPos, b))
			end

			local result = workspace:Raycast(rayPos, b, params)
			if not result then continue end

			if (not ldd.dir or not ldd.dist) or (ldd.dist and ldd.dist < result.Distance) then
				ldd.dir = b
				ldd.dist = result.Distance
			end

			-- don't collide with the same part twice
			if table.find(partsAlreadyHit, result.Instance) then continue end
			table.insert(partsAlreadyHit, result.Instance)

			-- get the movement direction compared to the wall
			local _v =  newSpeed.Unit * result.Normal

			-- find active coordinate of comparison
			for _, c in pairs({_v.X, _v.Y, _v.Z}) do
				if math.abs(c) > 0 then
					_v = c
					break
				end
			end

			-- if we are moving AWAY from the normal, (positive)
			-- then do not flatten the vector.

			-- it's not necessary.
			-- you will stick.
			-- stick.

			if type(_v) == "number" and _v > 0 then
				continue
			end

			if not isSticking then isSticking = true end
			newSpeed = flattenVectorAgainstWall(newSpeed, result.Normal)
			newSpeed -= result.Instance.Velocity

			self.mover.PlaneVelocity = Vector2.new(newSpeed.X, newSpeed.Z)
			--self.collider.Velocity = Vector3.new(newSpeed.X, (self.collider.Velocity.Y), newSpeed.Z) -- anti sticking has to be applied on collider velocity as well (resolves head & in air collision)
		end
	end

	return newSpeed, isSticking and normals, isSticking and stickingDirections, isSticking and ldd.dir
end


export type Trace = {
	startPos: Vector3,
	endPos: Vector3,


	fraction: number,
	startSolid: boolean,
	hitPart: Instance,
	hitPoint: Vector3,
	planeNormal: Vector3,
	distance: number
}

local function HandleVisualization(self, cf, size)
	if not Config.VISUALIZE_FEET_HB then
		return
	end
	if not self.vispart then
		self.vispart = Instance.new("Part", self.character)
		self.vispart.CanCollide = false
		self.vispart.Anchored = true
	end

	self.vispart.CFrame = cf
	self.vispart.Size = size
end

function Shared.GetAngle(normal)
	return math.deg(math.acos(normal:Dot(Vector3.yAxis)))
end

function Shared:IsGrounded(dir): (boolean, boolean?, RaycastResult?, number?)
	local params = Shared.GetMovementParams(self)
	local cf = CFrame.new(self.collider.Position) - Vector3.new(0, self.collider.Size.Y/2, 0)
	local size = Config.FEET_HB_SIZE
	dir = dir or Vector3.new(0,-1 * self.collider.Size.Y-Config.FOOT_OFFSET_AMOUNT,0)

	local result = workspace:Blockcast(cf, size, dir, params)

	HandleVisualization(self, cf, size)

	if not result then
		return false
	end

	local steepness = Shared.GetAngle(result.Normal)
	local isSurfing = steepness >= Config.MIN_SLOPE_ANGLE and steepness <= Config.MAX_SLOPE_ANGLE
	if isSurfing then
		return false, true, result, steepness
	end

	return true, false, result
end

function Shared:RotateCharacter()
	local collider = self.collider
	local camera = workspace.CurrentCamera
	local rotationLook = collider.Position + camera.CoordinateFrame.lookVector
	collider.CFrame = CFrame.new(collider.Position, Vector3.new(rotationLook.x, collider.Position.y, rotationLook.z))
	collider.RotVelocity = Vector3.new()
end

function Shared:GetMovementDirection(groundNormal)
	local forward = self.Keys.W + -self.Keys.S
	local side = self.Keys.A + -self.Keys.D
	groundNormal = groundNormal or Vector3.new(0,1,0)

	if forward == 0 and side == 0 then
		self.states.input_vec = Vector3.zero
		return Vector3.zero
	else
		self.states.input_vec = Vector3.new(-side, 0, -forward).Unit
		local forwardMove = groundNormal:Cross(self.collider.CFrame.RightVector)
		local sideMove = groundNormal:Cross(forwardMove)
		return (forwardMove * forward + sideMove * side).Unit
	end
end

function Shared:GetMovementParams()
	local params = RaycastParams.new()
	params.FilterDescendantsInstances = {self.character, workspace.CurrentCamera}
	params.FilterType = Enum.RaycastFilterType.Exclude
	params.RespectCanCollide = false
	params.CollisionGroup = "PlayerMovement"
	return params
end

function Shared:VectorMa(start: Vector3, scale: number, direction: Vector3)
	return Vector3.new(
		start.X + direction.X * scale,
		start.Y + direction.Y * scale,
		start.Z + direction.Z * scale
	)
end


local Trace: Movement = {}

local numBumps = 1
local maxClipPlanes = 5
local _planes = {}

local numToCoord = {"X", "Y", "Z"}

local function ClipVelocity(self, input, normal, output, overbounce)
	local newVelMod = Vec3Mod.new(output)
	Trace.ClipVelocity(self, input, normal, newVelMod, overbounce)
	return newVelMod:ToVector3()
end

function Trace:TraceBox(start: Vector3, destination: Vector3)
	local contactOffset = 0.1 -- idk what this is

	local longSide = math.sqrt(contactOffset * contactOffset + contactOffset * contactOffset)
	local direction = (destination - start).Unit
	local maxDistance = (start - destination).Magnitude + longSide

	local result: Trace = {
		startPos = start,
		endPos = destination
	}

	local _, _, hit = Shared.IsGrounded(self, direction*maxDistance)
	if hit then
		result.fraction = hit.Distance / maxDistance
		result.hitPart = hit.Instance
		result.hitPoint = hit.Position
		result.planeNormal = hit.Normal
		result.distance = hit.Distance
	else
		result.fraction = 1
	end

	return result
end

function Trace:ClipVelocity(input: Vector3, normal: Vector3, output: Types.Vec3Mod, overbounce: number)
	local angle = normal.Y
	local blocked = 0x00     -- // Assume unblocked.

	if angle > 0 then -- // If the plane that is blocking us has a positive z component, then assume it's a floor.
		blocked = blocked or 0x01
	end

	if angle == 0 then -- // If the plane has no Z, it is vertical (wall/step)
		blocked = blocked or 0x02
	end

	-- // Determine how far along plane to slide based on incoming direction.
	local backoff = input:Dot(normal) * overbounce

	--// iterate once to make sure we aren't still moving through the plane
	for i = 1, 3, 1 do
		local ci = numToCoord[i]
		local change = normal[ci] * backoff
		output[ci] = input[ci] - change
	end

	local adjust = output:ToVector3():Dot(normal)
	if adjust < 0 then
		output = output:ToVector3()-(normal * adjust)
	end

	-- Return blocking flags.
	return blocked
end

function Trace:Reflect(velocity: Vector3, origin: Vector3)

	self = self :: Movement

	local d
	local newVelocity = Vector3.zero
	local blocked = 0
	local numplanes = 1
	local originalVelocity = velocity
	local primalVelocity = velocity

	local allFraction = 0
	local timeLeft = self.dt

	for bumpcount = 1, numBumps, 1 do

		if velocity.Magnitude == 0 then
			break
		end

		-- Assume we can move all the way from the current origin to the
		--  end point.
		local endp = Shared.VectorMa(self, origin, timeLeft, velocity)
		local trace = Trace.TraceBox(self, origin, endp)
		allFraction += trace.fraction

		if trace.fraction > 0 then
			-- actually covered some distance
			originalVelocity = velocity
			numplanes = 0
		end

		-- If we covered the entire distance, we are done
		--  and can return.
		if trace.fraction == 1 then
			break
		end -- moved the entire distance

		-- If the plane we hit has a high z component in the normal, then
		--  it's probably a floor
		if trace.planeNormal.Y > Config.MAX_SLOPE_ANGLE then
			blocked = blocked or 1
		end

		-- If the plane has a zero z component in the normal, then it's a
		--  step or wall
		if trace.planeNormal.Y == 0 then
			blocked = blocked or 2 -- step/wall
		end

		-- Reduce amount of m_flFrameTime left by total time left * fraction
		--  that we covered.
		timeLeft -= timeLeft * trace.fraction


		-- Did we run out of planes to clip against?
		if numplanes >= maxClipPlanes then
			-- this shouldn't really happen
			--  Stop our movement if so.
			velocity = Vector3.zero
			break
		end

		-- Set up next clipping plane
		_planes[numplanes] = trace.planeNormal
		numplanes+=1

		-- modify original_velocity so it parallels all of the clip planes
		--

		-- reflect player velocity
		-- Only give this a try for first impact plane because you can get yourself stuck in an acute corner by jumping in place
		--  and pressing forward and nobody was really using this bounce/reflection feature anyway...
		if numplanes == 2 then
			if _planes[1].Y > Config.MAX_SLOPE_ANGLE then
				return blocked
			else
				newVelocity = ClipVelocity(self, originalVelocity, _planes[1], newVelocity, 1)
			end
			velocity = newVelocity
			originalVelocity = newVelocity
		else
			local _i = 0
			for i = 1, numplanes-1, 1 do
				_i = i

				newVelocity = ClipVelocity(self, originalVelocity, _planes[i], newVelocity, 1)

				local _j = 0
				for j = 1, numplanes-1, 1 do
					_j = j
					if j ~= i and velocity:Dot(_planes[j]) < 0 then
						break
					end
				end

				if _j == numplanes then
					break
				end
			end

			if _i ~= numplanes then

			else
				if numplanes ~= 3 then
					velocity = Vector3.zero
					break
				end

				local dir = _planes[1]:Cross(_planes[2]).Unit
				d = dir:Dot(velocity)
				velocity = dir * d
			end
		end

		d = velocity:Dot(primalVelocity)
		if d <= 0 then
			velocity = Vector3.zero
			break
		end

	end

	if allFraction == 0 then
		velocity = Vector3.zero
	end

	return velocity, blocked
end


local function ApplyMoverVelocity(self: Movement, velocity: Vector3): Vector2
	local vel = Vector2.new(velocity.X, velocity.Z)
	self.mover.PlaneVelocity = vel
	return vel
end

function Physics:ApplyGroundVelocity(groundNormal: Vector3)
	local wishDir = Shared.GetMovementDirection(self, groundNormal)
	local wishSpeed = wishDir.Magnitude * Config.RUN_SPEED

	-- normal friction
	if self.states.air_friction <= 0 then
		Physics.ApplyFriction(self)
	else

		-- friction that is applied due to the player reaching max speed while bhopping
		local sub = Config.AIR_MAX_SPEED_FRIC_DEC * self.dt * 60
		local curr = self.states.air_friction
		local fric = curr - sub
		if fric < 0 then
			fric = curr + fric
		end

		Physics.ApplyFriction(self, math.max(1, fric/Config.FRICTION))
		self.states.air_friction = math.max(0, curr - sub)
	end

	Physics.ApplyGroundAcceleration(self, wishDir, wishSpeed)
	Collisions.CollideAndSlide(self, Vector3.new(self.mover.PlaneVelocity.X, 0, self.mover.PlaneVelocity.Y))

	-- calculate & apply slope movement
	if Shared.GetAngle(groundNormal) < 5 then
		return
	end

	local curVel: Vector3 = Vector3.new(self.mover.PlaneVelocity.X, self.collider.Velocity.Y, self.mover.PlaneVelocity.Y)
	local forVel: Vector3 = groundNormal:Cross(CFrame.Angles(0,math.rad(90),0).LookVector * curVel)
	local yVel: number = 0

	if forVel.Magnitude > 0 then
		yVel = forVel.Unit.Y * curVel.Magnitude
	end

	self.collider.Velocity = Vector3.new(curVel.X, yVel, curVel.Z)
end

function Physics:ApplyGroundAcceleration(wishDir: Vector3, wishSpeed: number)
	local addSpeed
	local accelerationSpeed
	local currentSpeed
	local currentVelocity = Vector3.new(self.mover.PlaneVelocity.X, 0, self.mover.PlaneVelocity.Y)
	local newVelocity = currentVelocity

	-- get current/add speed
	currentSpeed = currentVelocity:Dot(wishDir)
	addSpeed = wishSpeed - currentSpeed

	-- if we're not adding speed, dont do anything
	if addSpeed <= 0 then return end

	-- get accelSpeed, cap at addSpeed
	accelerationSpeed = math.min(Config.GROUND_ACCEL * self.dt * wishSpeed, addSpeed)

	-- you can't change the properties of a Vector3, so we do x, y, z
	newVelocity += (accelerationSpeed * wishDir)
	newVelocity = Vector3.new(newVelocity.X,  0, newVelocity.Z)

	-- clamp magnitude (max speed)
	if newVelocity.Magnitude > (Config.RUN_SPEED) then
		newVelocity = newVelocity.Unit * math.min(newVelocity.Magnitude, (Config.RUN_SPEED))
	end

	-- apply acceleration
	ApplyMoverVelocity(self, newVelocity)
end

function Physics:ApplyAirVelocity(normal)
	normal = Vector3.new(0,1,0)
	local vel = Vector3.new(self.mover.PlaneVelocity.X, 0, self.mover.PlaneVelocity.Y)
	local wishDir = Shared.GetMovementDirection(self, normal)
	local wishSpeed = wishDir.Magnitude * Config.AIR_SPEED
	local currSpeed = vel.Magnitude

	-- initiate extra friction for max speed
	if currSpeed > Config.AIR_MAX_SPEED then
		self.states.air_friction = Config.AIR_MAX_SPEED_FRIC
	end

	-- apply extra friction if necessary
	if self.states.air_friction > 0 and not self.states.surfing then
		Physics.ApplyFriction(self, 0.01 * self.states.air_friction, false)
	end

	Physics.ApplyAirAcceleration(self, wishDir, wishSpeed)

	local refVel = Vector3.new(self.mover.PlaneVelocity.X, self.collider.Velocity.Y, self.mover.PlaneVelocity.Y)
	ApplyMoverVelocity(self, Trace.Reflect(self, refVel, self.collider.Position))
end

function Physics:ApplyAirAcceleration(wishDir: Vector3, wishSpeed: number)
	local currentSpeed
	local addSpeed
	local accelerationSpeed
	local currentVelocity = Vector3.new(self.mover.PlaneVelocity.X, 0, self.mover.PlaneVelocity.Y)

	-- get current/add speed
	currentSpeed = currentVelocity:Dot(wishDir)
	addSpeed = wishSpeed - currentSpeed

	-- if we're not adding speed, dont do anything
	if addSpeed <= 0 then return end

	-- get accelSpeed, cap at addSpeed
	accelerationSpeed = math.min(Config.AIR_ACCEL * self.dt * wishSpeed, addSpeed)

	-- get new velocity
	local newVelocity = currentVelocity + accelerationSpeed * wishDir

	-- apply acceleration
	ApplyMoverVelocity(self, newVelocity)
end

function Physics:ApplyFriction(modifier: number?, inAir: boolean?)
	local vel
	if inAir then
		vel = self.collider.Velocity
	else
		vel = Vector3.new(self.mover.PlaneVelocity.X, 0, self.mover.PlaneVelocity.Y)
	end

	local speed = vel.Magnitude
	modifier = modifier or 1

	local drop = 0
	local fric = inAir and Config.AIR_FRICTION or Config.FRICTION
	local decel = Config.GROUND_DECCEL
	local newSpeed
	local control

	-- if we're not moving, don't apply friction
	if speed <= 0 then
		return vel
	end

	-- ???
	control = speed < decel and decel or speed
	drop = control * fric * self.dt * modifier
	if type(drop) ~= "number" then drop = drop.Magnitude end

	-- ????????????
	newSpeed = math.max(speed - drop, 0)
	if speed > 0 and newSpeed > 0 then
		newSpeed /= speed
	end

	vel *= newSpeed
	ApplyMoverVelocity(self, vel)

	if inAir then
		local nv = self.mover.VectorVelocity
		--self.collider.Velocity = Vector3.new(nv.X, self.collider.Velocity.Y * newSpeed, nv.Z)
	end
end

local function Init()
	Movement.Keys = {W = 0, S = 0, D = 0, A = 0, Space = 0}
	Movement.player = game.Players.LocalPlayer
	Movement.character = Movement.player.Character or Movement.player.CharacterAdded:Wait()
	Movement.collider = Movement.character:WaitForChild("HumanoidRootPart")
	Movement.vispart = false
	Movement.vis_coll_parts = {}
	Movement.config = Config

	local mover = Instance.new("LinearVelocity", Movement.collider)
	local a0 = Instance.new("Attachment", Movement.collider)
	a0.Name = "MovementAttachment"
	mover.Attachment0 = a0
	mover.MaxForce = 10000000
	mover.VelocityConstraintMode = Enum.VelocityConstraintMode.Plane
	mover.PrimaryTangentAxis = Vector3.new(1,0,0)
	mover.SecondaryTangentAxis = Vector3.new(0,0,1)
	Movement.mover = mover

	-- Will figure this out soon.
    --[[local upMover = Instance.new("LinearVelocity", Movement.collider)
    upMover.Attachment0 = Movement.collider:WaitForChild("MovementAttachment")
    upMover.ForceLimitMode = Enum.ForceLimitMode.PerAxis
    upMover.ForceLimitsEnabled = true
    upMover.MaxAxesForce = Vector3.new(0, 100000, 0)
    upMover.VelocityConstraintMode = Enum.VelocityConstraintMode.Vector
    Movement.up_mover = upMover]]

	Movement.states = {grounded = false, air_friction = 0, input_vec = Vector3.zero, surfing = false}
end

--

local function Gravity()
	local mod = Movement.config.GRAVITY * Movement.dt
	Movement.collider.AssemblyLinearVelocity = Vector3.new(
		Movement.collider.AssemblyLinearVelocity.X,
		Movement.collider.AssemblyLinearVelocity.Y - mod,
		Movement.collider.AssemblyLinearVelocity.Z
	)
end

local function Air()
	Physics.ApplyAirVelocity(Movement)
	local vel = Vector3.new(Movement.mover.PlaneVelocity.X, Movement.collider.AssemblyLinearVelocity.Y, Movement.mover.PlaneVelocity.Y)
	vel = Trace.Reflect(Movement, vel, Movement.collider.CFrame.Position)
	Movement.mover.PlaneVelocity = Vector2.new(vel.X, vel.Z)
	Movement.collider.AssemblyLinearVelocity = vel
end

local function Ground(groundNormal: Vector3)
	Physics.ApplyGroundVelocity(Movement, groundNormal)
end

local function Jump()
	sounds[math.random(1, #sounds)]:Play()
	Movement.temp_jump_last = tick()
	Movement.states.jumping = true
	Movement.collider.AssemblyLinearVelocity = Vector3.new(
		Movement.collider.AssemblyLinearVelocity.X,
		Movement.config.JUMP_VELOCITY,
		Movement.collider.AssemblyLinearVelocity.Z
	)
end

local function ProcessMovement()
	local isGrounded, isSurfing, result = Shared.IsGrounded(Movement)
	Movement.states.grounded = isGrounded or false
	Movement.states.surfing = isSurfing or false

	if Movement.collider.AssemblyLinearVelocity.Y < 0 then
		Movement.states.jumping = false

	end

	Shared.RotateCharacter(Movement)

	if Movement.states.jumping or not Movement.states.grounded then
		local groundNormal = false
		if isSurfing then
			groundNormal = result.Normal
			if Movement.collider.Velocity.Y > 0 then
				--Physics.ApplyFriction(Movement, false, true)
			end
		end

		Air(groundNormal)
		Gravity()
	elseif Movement.Keys.Space > 0 then
		Jump()
		Air()
	else
		Ground(result.Normal)
	end
end

--

local function InputBegan(input: InputObject, gp: boolean)
	if input.KeyCode and Movement.Keys[input.KeyCode.Name] then
		Movement.Keys[input.KeyCode.Name] = 1
	end
end

local function InputEnded(input: InputObject, gp: boolean)
	if input.KeyCode and Movement.Keys[input.KeyCode.Name] then
		Movement.Keys[input.KeyCode.Name] = 0
	end
end

local function Update(dt)
	Movement.dt = dt
	ProcessMovement()
end

Init()
UserInputService.InputBegan:Connect(InputBegan)
UserInputService.InputEnded:Connect(InputEnded)
RunService.RenderStepped:Connect(Update)

Features & Information

its basically bhopping like half life 1

Community Comments (2)

kingraxton888kingraxton888Aug 6, 2025

2 seconds ago

mrcreeperyt91mrcreeperyt91Sep 13, 2026

not working

More Scripts for Universal Scripts