Need tips on a movement script optimization

  • What does the code do and what are you not satisfied with?
    • The code below is a local script that is made for handling player movement. sprinting, crouching, running.
    • The problem with the code is it’s very messy and unoptimized.
  • What potential improvements have you considered?
    • I’ve considered making a module script off of it and handling it that way to make it more organized and readable.
  • How (specifically) do you want to improve the code?
    • I just want the code to be optimized, and easily be able to alter values.

tdr: I just think its very messy and if you could please give me some tips on how to improve it thx.

localscript:

local KEYBINDS = require(script.Parent.KeyBinds)
local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")
local uis = game:GetService("UserInputService")
local debris = game:GetService("Debris")
local camera = workspace.CurrentCamera
local staminaBar = player.PlayerGui:WaitForChild("Main"):WaitForChild("Stamina")

local speed = {default=16, running=22}
local fov = {default=70, running=80}
local stamStat = {depletion=0.02, regen=0.015, defaultRegen=0.015, cooldown=0.1}
local slideStat = {magnitude=25, maxForce=30000, duration=0.7}
local stamina = 1
local oldStam = stamina

local clamp, udim, floor = math.clamp, UDim2.new, math.floor
local tired, running, sliding, fovBounce, canSprint, canSlide = false, false, false, false, true, true
------------------------------------------------------------------------------------------

local animations = {
	Crouch = humanoid:LoadAnimation(script.Crouch),
	Slide = humanoid:LoadAnimation(script.Slide),
}

local function stopAnimations()
	for _, v in pairs(animations) do
		v:Stop()
	end
end

local function changeFov(value)
	coroutine.wrap(function()
		fovBounce = not fovBounce
		local oldBounce = fovBounce
		local increment = 1
		if value < camera.FieldOfView then increment=-1 end

		for i=camera.FieldOfView, value, increment do
			if fovBounce == oldBounce then
				wait(0.001)
				camera.FieldOfView = i
			end
		end
	end)()
end

local function updateStamina()
	if staminaBar:FindFirstChild("Bar") then
		if oldStam~=stamina then
			oldStam = stamina
			staminaBar.Bar:TweenSize(udim(stamina,0,1,0), "Out", "Linear", 0.1, true)
			staminaBar.Text.Text = floor(stamina*100) .. "%"
		end
		if canSprint then
			staminaBar.Bar.BackgroundColor3 = Color3.fromRGB(0, 187, 255)
		else
			staminaBar.Bar.BackgroundColor3 = Color3.fromRGB(0, 133, 181)
		end
	end
end

local function toggleRun(enabled)
	if enabled then
		if stamina>0.25 then
			running = true
			humanoid.WalkSpeed = speed.running
			changeFov(fov.running)
		end
	else
		running = false
		humanoid.WalkSpeed = speed.default
		changeFov(fov.default)
	end
end

local function manipulateStamina(add, amount)
	if add then
		stamina=clamp(stamina+amount, 0, 1)
	else
		stamina=clamp(stamina-amount, 0, 1)
		if stamina<=0 then
			stamina=0
			tired = true
			toggleRun(false)
			updateStamina()
			wait(5)
			tired = false
		end
	end
end

local function slide()
	canSlide, canSprint, sliding = false, false, true
	toggleRun(false)
	manipulateStamina(false, 0.25)
	
	humanoid.WalkSpeed = 0
	
	local force = Instance.new("BodyVelocity")
	force.MaxForce = Vector3.new(1,0,1) * slideStat.maxForce
	force.Velocity = character.Head.CFrame.LookVector * slideStat.magnitude
	force.Parent = character.HumanoidRootPart
	debris:AddItem(force, slideStat.duration)
	
	animations.Slide:Play()
	wait(slideStat.duration)
	stopAnimations()
	canSlide, canSprint, sliding = true, true, false
	humanoid.WalkSpeed = speed.default
end

local function toggleCrouch(enabled)
	if not sliding then
		stopAnimations()
		if enabled then
			if running and canSlide and not sliding and stamina>0.25 then
				slide()
			else
				canSprint = false
				toggleRun(false)
				stamStat.regen = stamStat.defaultRegen+.008
				humanoid.WalkSpeed = speed.default-6
				animations.Crouch:Play()
			end
		else
			stamStat.regen = stamStat.defaultRegen-.008
			humanoid.WalkSpeed = speed.default
			canSprint = true
			toggleRun(false)
		end
	end
end

uis.InputBegan:Connect(function(input, processed)
	if not processed then
		if input.KeyCode == KEYBINDS.sprint and canSprint then
			toggleRun(true)
		elseif input.KeyCode == KEYBINDS.crouch then
			toggleCrouch(true)
		end
	end
end)

uis.InputEnded:Connect(function(input, processed)
	if not processed then
		if input.KeyCode == KEYBINDS.sprint and canSprint then
			toggleRun(false)
		elseif input.KeyCode == KEYBINDS.crouch then
			toggleCrouch(false)
		end
	end
end)

game.ReplicatedStorage.Events.EditStamina.OnClientEvent:Connect(function(add, amount)
	manipulateStamina(add, amount)
end)

while wait(stamStat.cooldown) do
	if running then
		manipulateStamina(false, stamStat.depletion)
	elseif stamina<1 and not tired then
		manipulateStamina(true, stamStat.regen)
	end
	updateStamina()
end
1 Like

Edit: Little correction someone made in a different post, wait() has a capped time internally equivalent to 0.03 seconds (I’m honestly not sure why)

There are a few things I notice right away. One, you’re making a lot of local variables like clamp, udim, and floor when you don’t need to. Luau does optimizations around accessing static functions like those, and it isn’t great for readability.

A somewhat minor thing I notice is that in your stopAnimations loop, you have the variable name as v, but, it’d make more sense if you named it, e.g. as animation so that reading it will make a little more sense.

For your changeFov code, I have a few different things I’d suggest. With the release of the new task library, coroutine.wrap spawning isn’t as good since it eats errors. I’d recommend using task.spawn there now.

Additionally, you make a wait call there and you specify a time. wait(0.001) is actually exactly the same as wait(), because the minimum wait time is locked to exactly two frames 0.03, or about 1/30th of a second depending on your FPS, so smaller times than that will still always wait that long. It doesn’t make it any faster to specify a number, because wait() always waits for the minimum possible amount of time regardless of FPS.

Even then, instead you should use the newer task.wait rather than wait, since it does not have throttling issues and uses the Heartbeat event. task.wait's minimum time is always one frame not two so it’s actually faster, and, wait has had an ancient issue that has been known about for ages, and that’s that it relies on the thread scheduler. A regular wait call might take much longer than it should because of throttling, and making a lot of wait calls too quickly can cause even more aggressive throttling resulting in game-wide slowdown. This results in games that excessively use wait to feel significantly slower, because well, they are actually getting slower.

task.wait resolves this in the same way that developers have been for ages now, and that’s to use what’s basically equivalent to RunService.Heartbeat:Wait(). task.wait() will never wait for more than one frame, and will never throttle. task.wait(1) will always wait at most 1 second (and up to a single frame). It’ll always be within that single frame of the target time, meaning code relying on task.wait doesn’t get even slower when the game is already running at a lower than normal performance.

I would actually recommend replacing the whole changeFov section to use TweenService and tween the field of view with the Linear easing style (which is the same as what you’re doing). Your loops doesn’t actually take into account delta time (aka how long wait is taking, aka how much time is passing between when you’re updating the FOV), so it could take 10 seconds, or 5 seconds or 1 second to change, all depending on how fast wait is at any given time, and depending on the player’s FPS, the FieldOfView property will change at different speeds. Using TweenService would be a much more compact way to do it that wouldn’t rely on what the player’s FPS is being consistent.

I see more places that wait is used also, and again would recommend now using task.wait for that throttling issue that wait has always had.

In your last loop which updates stamina, you run into the same delta time issue, and again are using wait. I would recommend using task.wait() inside the loop rather than in the condition since it’s a bit better practice to have it be in the loop body (with no timing value passed, I will explain why).

To fix that delta time issue with stamina, and still take into account that cooldown stat, you can use the number returned from task.wait() (I’ll call it delta) which tells you how much time the task.wait call took to calculate how much to change the stamina by. Since you want it to go down by the depletion stat every cooldown seconds, you can just calculate it as the depletion amount per second (depletion / cooldown) and multiply by the delta. Same with the regen, the amount per second you want it to regenerate by is just regen / cooldown, and then you can multiply by the delta to decide how much to change it by each frame.

The reason why this works is pretty simple… By calculating how much it should change per second you can figure out for any amount of time how much it should change. delta is just how much time has passed, so every second that delta counts up for you want it to change by that value you calculate, and the way you do that is just to multiply.

That will result in the stamina being updated perfectly, so that no matter what your FPS is or how much its changing, if you can get 5, or 60 FPS, your stamina will always change by the same amount each second.

Hopefully you’ll find some of what I said helpful :smile:

2 Likes

Wow thanks. Never knew task existed. This helped a lot.

1 Like

Saw this on my following feed, even though I wasn’t looking for an answer like this, it has actually answered some questions I’ve had running through my head recently about task.wait(), so yet again, thanks for allowing me inside of your scripting brain mate.

I’ll leave you a DM on discord about some other questions in a while, cya around homes!

:slight_smile:

2 Likes