Quake Engine Movement [Open Source]

-- Player Movement Script --

-- Derived from CS:S Source Code --
local runService = game:GetService("RunService")
local modules = game.ReplicatedStorage.Modules
local velocity = require(modules.Velocity):Init(true)
local inputs = velocity:GetService("InputService")

-- movement variables
local moveSpeed = 1000
local runAccel = 10
local runDeaccel = 1000
local airAccel = 7.5
local airDeaccel = 7.5
local sideStrafeAccel = 100
local sideStrafeSpeed = 100
local jumpSpeed = 16
local holdJumpToBhop = true
local gravity = 50
local friction = 5

-- vectors for movment
local playerVel = Vector3.new(0, 0, 0)

-- variable to check if player is on ground
local grounded = false

-- thing to queue jumps
local wishJump = false
local canJump = true
local jumpBuffer = 0.1

-- contains inputs for character
local command = {
	
	forwardMove = 0;
	rightMove = 0;
	
	upmove = 0;
	
	space = false;
	justPressed = false;
	
}

local enumBinds = {
	
	[1] = "One";
	[2] = "Two";
	[3] = "Three";
	
}

-- player variables
local player = game.Players.LocalPlayer
local camera = workspace.CurrentCamera
local character = player.Character
local humanoidRootPart = character.HumanoidRootPart

-- create body velocity to control the player
local bodyVel = Instance.new("BodyVelocity", humanoidRootPart)
bodyVel.MaxForce = Vector3.new(100000, 0, 100000)


-- input stuff
local function onUp(toggle)
	if toggle then command.forwardMove = -1 else if command.forwardMove ~= 1 then command.forwardMove = 0 end end
end
local function onRight(toggle)
	if toggle then command.rightMove = 1 else if command.rightMove ~= -1 then command.rightMove = 0 end end
end
local function onDown(toggle)
	if toggle then command.forwardMove = 1 else if command.forwardMove ~= -1 then command.forwardMove = 0 end end
end
local function onLeft(toggle)
	if toggle then command.rightMove = -1 else if command.rightMove ~= 1 then command.rightMove = 0 end end
end
local function onSpace(toggle)
	if toggle then command.space = true command.justPressed = true else command.space = false command.justPressed = false end
end

-- movement inputs
inputs.BindOnBegan(nil, "W", function() onUp(true) end, "Up : True")
inputs.BindOnEnded(nil, "W", function() onUp(false) end, "Up : False")
inputs.BindOnBegan(nil, "A", function() onLeft(true) end, "Left : True")
inputs.BindOnEnded(nil, "A", function() onLeft(false) end, "Left : False")
inputs.BindOnBegan(nil, "S", function() onDown(true) end, "Down : True")
inputs.BindOnEnded(nil, "S", function() onDown(false) end, "Down : False")
inputs.BindOnBegan(nil, "D", function() onRight(true) end, "Right : True")
inputs.BindOnEnded(nil, "D", function() onRight(false) end, "Right : False")
inputs.BindOnBegan(nil, "Space", function() onSpace(true) end, "Jump : True")
inputs.BindOnEnded(nil, "Space", function() onSpace(false) end, "Jump : False")


-- call every frame
local function update(dt)
	
	-- queue jump function
	queueJump()
	
	grounded = checkGround()
	
	-- update move speed to humanoid walk speed
	moveSpeed = character.Humanoid.WalkSpeed
	
	-- check if grounded to decide function
	if grounded then
		
		groundMove()
		
	else
		
		airMove()
		
	end
	
	-- move the player
	bodyVel.Velocity = playerVel * dt
	
end

local updateFunction = runService.RenderStepped:Connect(update)

character.Humanoid.Died:Connect(function()
	bodyVel:Destroy()
	updateFunction:Disconnect()
end)


function queueJump()
	
	if holdJumpToBhop then
		
		wishJump = command.space
		return
		
	end
	
	if command.space and not wishJump and command.justPressed then
		
		wishJump = true
		command.justPressed = false
		
	end
	
	if not command.space then
		
		wishJump = false
		
	end
	
end


function checkGround()
	
	local ray = Ray.new(humanoidRootPart.Position, Vector3.new(0, -3.25, 0))
	local hit, hitPos = workspace:FindPartOnRayWithIgnoreList(ray, {character})
	
	if hit then
		
		return true
		
	end
	
	return false
	
end


function airMove()
	
	if command.forwardMove ~= 0 then
		
		applyFriction(1)
		
	end
	
	local accel
	local cameraCFrame = camera.CFrame
	local inputVel = Vector3.new(command.rightMove, 0, command.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 command.forwardMove == 0 and command.rightMove ~= 0 then
		
		if wishSpeed > sideStrafeSpeed then
			
			wishSpeed = sideStrafeSpeed
			
		end
		
		accel = sideStrafeAccel
		
	end
	
	accelerate(wishDir, wishSpeed, accel)
	
	humanoidRootPart.Velocity = Vector3.new(humanoidRootPart.Velocity.X, humanoidRootPart.Velocity.Y - gravity * runService.Heartbeat:Wait(), humanoidRootPart.Velocity.Z)
	
end


function groundMove()
	
	if not wishJump and canJump then
		
		applyFriction(1)
		
	else
		
		applyFriction(0)
		
	end
	
	local cameraCFrame = camera.CFrame
	local inputVel = Vector3.new(command.rightMove, 0, command.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
		
		jumpSpeed = character.Humanoid.JumpPower
		humanoidRootPart.Velocity = Vector3.new(humanoidRootPart.Velocity.X, jumpSpeed, humanoidRootPart.Velocity.Z)
		wishJump = false
		canJump = false
		
		local co = coroutine.create(function()
			
			local begin = tick()
			
			while tick() - begin < jumpBuffer do
				
				wait()
				
			end
			
			canJump = true
			
		end)
		
		coroutine.resume(co)
		
	end
	
end


function applyFriction(t)
	
	local vec = Vector3.new(playerVel.X, 0, playerVel.Z)
	local speed = vec.Magnitude
	local drop = 0
	local control = 0
	
	control = speed < runDeaccel and runDeaccel or speed
	drop = control * friction * 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

Things to Note:

You can test out the game I use this for here:

33 Likes

So basically, I can add Bunny Hopping?

3 Likes

Feels a little awkward when bhopping to me, but a really good proof of concept!

5 Likes

How do you set player’s model to no friction?

1 Like

Why is it so slow? I’m confused

1 Like

because its the humanoids walkspeed
should be around 1000, most likely like 16 for you

1 Like

just add a script in serverscriptservice like this

game.Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		for index, value in ipairs(character:GetDescendants()) do
			if value:IsA("BasePart") then
				value.CustomPhysicalProperties = PhysicalProperties.new(0.7, 0, 0.5, 1, 1)
			end
		end
	end)
end)

Thanks! It works really well now

1 Like

So you can bunny hop like in CS, but is there a way to configure it so you can strafe jump like in actual quake instead? In quake you have little to no air control at all (can’t change directions in air) and you’re supposed to continue to hold w while strafe jumping as opposed to b hopping where the w key is left alone.

1 Like

Yes what you could do is remove the applyFriciton if statement in the airMove() function.

Hey thanks for the reply. I messed around with it a bit and tried what you said and messed around with it a bit more just now, but to be honest I just couldn’t get it to feel like quake. That said it WAS strafe jumping like quake, but I just couldn’t make it feel how I wanted. The walkspeed felt too slow compared to how fast I can get strafe jumping and I couldn’t figure out how to change one without changing the other and while strafe jumping you reach insane speeds in only a few jumps. I’ll probably keep messing with it though I still think it’s really cool.

1 Like

Yeah the values are a bit finnicky and I have tested around with it and got to them to what I want, mine is supposed to be like counter strike source except bhopping on steroids because I’m aiming for a fast paced defusal game.

Something weird with the movemnt. but i just put this

local PlayerScripts = player:WaitForChild("PlayerScripts")
local ControlModule = require(PlayerScripts:WaitForChild("PlayerModule"):WaitForChild("ControlModule"))
ControlModule:Disable()

where do i add the module script? (the velocity one)

can you share a video or gif of what it does?

1 Like

You want to put that in a folder named Modules located in ReplicatedStorage

1 Like