I need to improve on stamina/sprinting system

I followed a tutorial online on how to do a basic sprinting system. It works fine, but after working with it for a few months there are things that kind of annoy me.

Here’s how it works:

The client picks up any user input and detects whether or not the shift key has been pressed. If it has been pressed, it would fire a remote event to the server.

In the server, it would make adjustments to the character’s walk speed and inserts them in an array. The stamina initialization and regeneration are also handled in the server. (that was a mouthful sorry about that)

There are two things I’m not satisfied with:

  1. I’ve added sprinting animations to the client and work okay. However, the player’s sprinting animation won’t stop playing even when they returned to normal walk speed. I’ve tried checking the walk speed with no luck.

  2. Sometimes pressing the shift key won’t even speed me up, but it’ll still play my sprinting animations.

I want my system to stay mostly server sided so people won’t try to hack the values of how fast the character goes. The stamina variable is also created and set on the server as well.

I also want to move away from the base code of the tutorial because it seems like there are small flaws in it that can not be fixed.

You can try out the code on the video embedded earlier in the post. The code snippets below are mostly from the original code, but I have added a few more variables overtime for the game that is not shown below. Feedback is greatly appreciated!

(assume all variables are declared)

Server
-- [[ Configureable ]] --
local MAX_STAMINA = 100
local STAMINA_REGEN = 0.1
local SPRINT_MODIFIER = 1.6
local SPRINT_STAMINA_COST = 0.4

local sprintingPlayers = {}

Players.PlayerAdded:Connect(function(player)
	local playerStats = player:WaitForChild("PlayerStats")
	local stamina = playerStats:WaitForChild("Stamina")
	
	stamina.Value = MAX_STAMINA
	
	-- Will update the stamina gui locally
	stamina.Changed:Connect(function(property)
		UpdateStamina:FireClient(player, stamina.Value, MAX_STAMINA)
	end)
end)

-- Detects when sprint key is pressed or released
Sprint.OnServerEvent:Connect(function(player, state)
	local localCharacter = player.Character or player.CharacterAdded:wait()
	local localHumanoid = localCharacter:WaitForChild("Humanoid")
	
	-- Checks if the player can sprint 
	if state == "Began" and localHumanoid:GetState() == Enum.HumanoidStateType.RunningNoPhysics and localHumanoid.MoveDirection.Magnitude > 0 then
		sprintingPlayers[player.Name] = localHumanoid.WalkSpeed
		localHumanoid.WalkSpeed = localHumanoid.WalkSpeed * SPRINT_MODIFIER
	elseif state == "Ended" and sprintingPlayers[player.Name] then
		localHumanoid.WalkSpeed = sprintingPlayers[player.Name]
		sprintingPlayers[player.Name] = nil
	end
end)

-- Updates stamina value
RunService.Heartbeat:Connect(function()
	for _,player in pairs(Players:GetChildren()) do
		local localCharacter = player.Character or player.CharacterAdded:wait()
		local localHumanoid = localCharacter:WaitForChild("Humanoid")
		
		-- Changes stamina value when player is not sprinting
		if not sprintingPlayers[name] then
			if stamina.Value > MAX_STAMINA then
				stamina.Value = MAX_STAMINA
			elseif stamina.Value < MAX_STAMINA then
				stamina.Value += STAMINA_REGEN
			end
		else
			-- Changes stamina value when player is sprinting
			if stamina.Value >= SPRINT_STAMINA_COST then
				stamina.Value -= SPRINT_STAMINA_COST
			else
				player.Character.Humanoid.WalkSpeed = sprintingPlayers[name]
				sprintingPlayers[name] = nil
			end
		end
	end
end)
Client
-- Detects when shift key or space key is pressed
UserInputService.InputBegan:Connect(function(input,GDE)
	if GDE then return end
	
	if input.KeyCode == Enum.KeyCode.LeftShift then
		Sprint:FireServer("Began")
		if localHumanoid:GetState() == Enum.HumanoidStateType.RunningNoPhysics and localHumanoid.MoveDirection.Magnitude > 0 then
			while camera.FieldOfView <= sprintingFOV do
				camera.FieldOfView += 1
				wait()
			end
	
			runningAnimTrack:Play()
		end

	end
	
end)
		
-- Detects when shift key is released
UserInputService.InputEnded:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.LeftShift then
		Sprint:FireServer("Ended")
		
		runningAnimTrack:Stop()
		
		while camera.FieldOfView >= defaultFOV do
			camera.FieldOfView -= 2
			wait()
		end

	end	
end)

-- Updates stamina gui locally
UpdateStamina.OnClientEvent:Connect(function(stamina, maxStamina)
	staminaBar.Size = UDim2.new((stamina / maxStamina) * staminaBarScaleX, 0, staminaBarScaleY, 0) 
end)
3 Likes

Have you tried reading Platform Overview | Documentation - Roblox Creator Hub ??? Because if you had…Tough Luck…Or you could join Alvin_BLox’s discord sever for a few Pounds…They’ll help you out !

1 Like

Thank you, but I do not need those resources right now. I just want feedback on how I can improve this code so that it can run more efficiently and solve the current issues I have with it.

1 Like

I will bump because I do not know how I can improve this code any further.

If you do not have time to review my code, I would greatly appreciate any suggestions on how I can improve my code in general by myself. Thank you!

Thank you for the feedback! (I thought it was going to be another like on this post hehe)

I’ll definitely look into spawning new threads and coroutines.

I think I’ve tried to change the default running animation with the Animate script with no luck. How would the script know when to change the character’s animation to the running animation? It is kind of confusing since default characters only walk and don’t change walkspeed unless you script it.

Thank you so much! After tweaking the code for a few hours, I finally got the running animation to work! It feels so much more responsive whenever I press the shiftkey.

It also fixed an issue I had with how the running animation would override the equipped tool animation, making the tool float with the character.

I’ll just share how I did it below, because I was having an issue where the running animation would play for a split second, then switches back to the walking animation.

what I did

I had to create a new “pose” called “Walking” because the source of the problem was in another function. They used “Running” as a way to play the default walking animation, which was a little confusing at first.

function onRunning(speed)
	local defaultWalkSpeed = game:GetService("StarterPlayer").CharacterWalkSpeed
	local speedFloor = math.floor(speed)
	if speedFloor > 0.75 then
		if speedFloor > defaultWalkSpeed then
			local scale = 18.0
			playAnimation("run", 0.2, Humanoid)
			setAnimationSpeed(speed / scale)
			pose = "Running"
		else
			local scale = 12.0
			playAnimation("walk", 0.2, Humanoid)
			setAnimationSpeed(speed / scale)
			pose = "Walking" -- Here
		end
	end

Now, the source of the problem I had was in a function that controlled all the animations while the player was moving. In the function, the pose variable would be checked, and the corresponding animation would be played.

However, the “Running” pose would always play the walking animation, overwriting the running animation that played before. That’s why I had to separate those two and make sure the running animation continued to play when I held the shift key.

It worked when you tested it out because you were using an existing “climb” pose that played the climbing animation.

move function snippet
if (pose == "FreeFall" and jumpAnimTime <= 0) then
		playAnimation("fall", fallTransitionTime, Humanoid)
	elseif (pose == "Seated") then
		playAnimation("sit", 0.5, Humanoid)
		return
	elseif (pose == "Walking") then  -- "Walking" pose to play walking animation
		playAnimation("walk", 0.1, Humanoid)
	elseif (pose == "Running") then -- "Running" pose to play running animation
		playAnimation("run", 0.1, Humanoid)
	elseif (pose == "Dead" or pose == "GettingUp" or pose == "FallingDown" or pose == "Seated" or pose == "PlatformStanding") then
--		print("Wha " .. pose)
		stopAllAnimations()
		amplitude = 0.1
		frequency = 1
		setAngles = true
	end

I guess the only issue I have with this code is how if I hold the shift key and stop moving, the FOV still stays the same, but I’ll see what I can do later. Also, in other games, I think that you can hold the shift key and sprint, stop moving and then sprint again without lifting your finger to sprint again, which I wonder if I could try to do the same here to improve the flow.

I was getting a little helpless with the code I had, but your help made me realize I need to experiment more and try to dig a little deeper with what I can do! If I tried to look into the Animate script, I would have been so confused about where and what to look at, and probably give up hehe. Thank you again!

Just to throw this out there.
But I’m pretttttty sure any exploiter can already change walkspeed easily, since it’s replicated from client-to-server.
So no need to worry about that, well, yes need to worry… but you know what I mean.

1 Like

Ah, I see. If that’s the case then, I will probably just look towards typing up an anti-cheat to combat anyone trying to change their walk speed.

1 Like