How make a stamina system without runservice

How make a stamina system without RunService

I have read tutorials on RunService but I still don’t understand it

This is my messy script

-- Services --
local userInputService = game:GetService('UserInputService')
local player = game:GetService('Players')

-- Player Variables --
local localPlayer = player.LocalPlayer
local character = localPlayer.Character or localPlayer.CharacterAdded:Wait()

local function sprint(working : boolean)
	
	if working == true then
		character.Humanoid.WalkSpeed = 32
	else
		character.Humanoid.WalkSpeed = 16
	end
	
end

userInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	
	if gameProcessedEvent then return end
	
	if input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.ButtonL3 then
		sprint(true)
	end
	
end)

userInputService.InputEnded:Connect(function(input, gameProcessedEvent)
	
	if gameProcessedEvent then return end
	
	if input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.ButtonL3 then
		sprint(false)
	end
	
end)

Not a scripter nor much help but I do suggest you should probably look at some public stamina systems in the toolbox

The stamina systems I use at times are much simpler and i think i saw one without runservice

RunService is really easy to understand

local RunService = game:GetService("RunService")

RunService.PreRender:Connect(function()
	print("This prints every new frame")
end)

RunService.PostSimulation:Connect(function()
	print("This prints everytime the physics simulations are finished")
end)

RunService.PreSimulation:Connect(function()
	print("This prints before the physics simulation has started")
end)

so in your case since the walkspeed is physics related, you can use either of the simulation events:

-- Services --
local userInputService = game:GetService('UserInputService')
local runService = game:GetService('RunService')
local player = game:GetService('Players')

-- Player Variables --
local localPlayer = player.LocalPlayer
local character = localPlayer.Character or localPlayer.CharacterAdded:Wait()

runService.PreSimulation:Connect(function(): ()
	
	local keypadDown = userInputService:IsKeyDown(Enum.KeyCode.LeftShift)
	local gamepadDown = userInputService:IsKeyDown(Enum.KeyCode.ButtonL3)
	
	if (keypadDown or gamepadDown) then
		character.Humanoid.WalkSpeed = 32
	else
		character.Humanoid.WalkSpeed = 16
	end
end)

also if you still dont want to use runservice, you can use InputActions and use their pressed and released functions which would be allot cleaner:

local action = nil :: InputAction -- change to your inputAction instance

action.Pressed:Connect(function(): ()
	character.Humanoid.WalkSpeed = 32
end)

action.Released:Connect(function(): ()
	character.Humanoid.WalkSpeed = 16
end)
2 Likes

tbh i would love to be taught this sometime in the future bc I’m a beginner and lookin for help

1 Like

Hello! I would highly recommend using the RunService. While it is theoretically possible to make something that works without using the RunService it’d greatly reduce the amount of spaggheti code in your script to use a RunService solution. Here is a quick example I made of something that theoretically works (Do not use this, it is a horrible design & even if you are to make a solution like this you should do it better than what I did.)

-- Services --
local userInputService = game:GetService('UserInputService')
local player = game:GetService('Players')

-- Player Variables --
local localPlayer = player.LocalPlayer
local character = localPlayer.Character or localPlayer.CharacterAdded:Wait()

local Stamina = 100
local MaxStamina = 100
local CurrentSprint = 0

local LastSprint = tick()
local RunBegin = tick()

local IsSprinting = false

local StaminaPerSecond = 100

local function calculateStaminaLeftInSeconds()
	return Stamina / StaminaPerSecond
end

local function sprint(working : boolean)

	if working == true then
		character.Humanoid.WalkSpeed = 32
	else
		character.Humanoid.WalkSpeed = 16
	end

end

userInputService.InputBegan:Connect(function(input, gameProcessedEvent)

	if gameProcessedEvent then return end

	if input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.ButtonL3 then
		Stamina = math.clamp(Stamina + (tick()-LastSprint)*StaminaPerSecond, 0, MaxStamina)
		print(Stamina)

		RunBegin = tick()

		IsSprinting = true

		sprint(true)
		CurrentSprint += 1
		local ThisSprint = CurrentSprint
		task.wait(calculateStaminaLeftInSeconds())
		if CurrentSprint == ThisSprint then
			CurrentSprint += 1
			sprint(false)
			Stamina = 0
			LastSprint = tick()
			IsSprinting = false
		end
	end

end)

userInputService.InputEnded:Connect(function(input, gameProcessedEvent)

	if gameProcessedEvent then return end

	if input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.ButtonL3 then
		if not IsSprinting then
			return
		end
		IsSprinting = false

		sprint(false)

		local StaminaUsed = (tick()-RunBegin)*StaminaPerSecond
		Stamina = math.clamp(Stamina - StaminaUsed, 0, MaxStamina)

		LastSprint = tick()
	end

end)

but this is absolutely horrible. And you should instead use a RunService solution like this

-- Services --
local userInputService = game:GetService('UserInputService')
local RunService = game:GetService('RunService')
local player = game:GetService('Players')

-- Player Variables --
local localPlayer = player.LocalPlayer
local character = localPlayer.Character or localPlayer.CharacterAdded:Wait()

local Sprinting = false
local Stamina = 100
local MaxStamina = 100
local StaminaPerSecond = 100

local function sprint(working : boolean)
	Sprinting = working
	
	if working == true then
		character.Humanoid.WalkSpeed = 32
	else
		character.Humanoid.WalkSpeed = 16
	end

end

userInputService.InputBegan:Connect(function(input, gameProcessedEvent)

	if gameProcessedEvent then return end

	if input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.ButtonL3 then
		sprint(true)
	end

end)

userInputService.InputEnded:Connect(function(input, gameProcessedEvent)

	if gameProcessedEvent then return end

	if input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.ButtonL3 then
		sprint(false)
	end

end)

RunService.Heartbeat:Connect(function(dt)
	print(Stamina)
	if Sprinting == true then
		Stamina = math.max(Stamina - StaminaPerSecond * dt, 0)
		if Stamina <= 0 then
			sprint(false)
		end
	else
		Stamina = math.min(Stamina + StaminaPerSecond * dt, MaxStamina)
	end
end)

I still don’t understand RunService, and what is it doing in the script?

My concerns is that it can mess up the script, for example, Somebody faster fps could potentially have faster regain

RunService events gives you a deltatime.

You can use deltatime so that stamina drain and regeneration rates are consistent across different framerates.

No worries! Let me explain it for you :

-- Every single frame
RunService.Heartbeat:Connect(function(dt) -- DT is the amount of time passed since the last frame
	print(Stamina) -- Just a debug statement, can be removed
	if Sprinting == true then -- If they are running
		Stamina = math.max(Stamina - StaminaPerSecond * dt, 0)
                -- Set the stamina to the biggest of the two :
                    -- The Current Stamina - StaminaPerSecond * The amount of time that has passed since the last frame
                    -- 0
		if Stamina <= 0 then -- if stamina is 0 or less
			sprint(false) -- stop spritning
		end
	else
		Stamina = math.min(Stamina + StaminaPerSecond * dt, MaxStamina)
                -- Set stamina to the smallest one of the two :
                      -- The current stamina + staminapersecond * the amount of time that has passed
                     -- the max stamina
	end
end)

That’s an excellent question, but this is exactly why we have DT (Deltatime, deltatime means the amount of time that has passed since the last frame)

Let’s say 1 second has passed between frames & the stamina per second is 100 :
100 * 1 = 100
you are given 100 stamina
Lets say 0.5 seconds has passed between frames & the stamina per second is 100 :
100 * 0.5 = 50
you are given 50 stamina

So yes, it is a real concern in some poorly written scripts that they’ll drain stamina faster for higher FPS but in this specific context we account for that using DeltaTime.

If you have any more questions feel free to ask!
Otherwise mark this as solved.

-- Services --
local userInputService = game:GetService('UserInputService')
local player = game:GetService('Players')

-- Player Variables --
local localPlayer = player.LocalPlayer
local character = localPlayer.Character or localPlayer.CharacterAdded:Wait()

local Stamina = 100
local MaxStamina = 100
local CurrentSprint = 0

local LastSprint = tick()
local RunBegin = tick()

local IsSprinting = false

local StaminaPerSecond = 100

local function calculateStaminaLeftInSeconds()
	return Stamina / StaminaPerSecond
end

local function sprint(working : boolean)

	if working == true then
		character.Humanoid.WalkSpeed = 32
	else
		character.Humanoid.WalkSpeed = 16
	end

end

userInputService.InputBegan:Connect(function(input, gameProcessedEvent)

	if gameProcessedEvent then return end

	if input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.ButtonL3 then
		Stamina = math.clamp(Stamina + (tick()-LastSprint)*StaminaPerSecond, 0, MaxStamina)
		print(Stamina)

		RunBegin = tick()

		IsSprinting = true

		sprint(true)
		CurrentSprint += 1
		local ThisSprint = CurrentSprint
		task.wait(calculateStaminaLeftInSeconds())
		if CurrentSprint == ThisSprint then
			CurrentSprint += 1
			sprint(false)
			Stamina = 0
			LastSprint = tick()
			IsSprinting = false
		end
	end

end)

userInputService.InputEnded:Connect(function(input, gameProcessedEvent)

	if gameProcessedEvent then return end

	if input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.ButtonL3 then
		if not IsSprinting then
			return
		end
		IsSprinting = false

		sprint(false)

		local StaminaUsed = (tick()-RunBegin)*StaminaPerSecond
		Stamina = math.clamp(Stamina - StaminaUsed, 0, MaxStamina)

		LastSprint = tick()
	end

end)

I generally feel like tick() is better than RunService, you’re just saying it’s bad but you’re not telling me why it’s bad

You can’t skip learning RunService. It deals with time.
It’s just a command library like any other.

The only thing that’s a bit misleading is some of the new name changes.
Heartbeat and Stepped are now, or at some point will be, PostSimulation and PreSimulation.

This isn’t that bad for misleading, due to them being just what they really always have been:
a pause after or a pause before a screen update by a given time.

There are a few commands worth really getting to know how to use, just as basic, everyday commands in RunService.

2 Likes

tick() isn’t bad for any particular reason. The solution I presented without RunService isn’t worse because it’s a slower algorithm or because tick() is bad. The reason that solution is bad is because it’s spaghetti code that’s hard to follow.

Basically : RunService is essential for organized, easier to read code in this specific context. While there may be a million ways to make a Stamina System without RunService just because a way exists doesn’t mean it should be that way.

tick() has it’s own usecases, but for readable, maintainable code runservice is just better. So it’s really just about making readable code not fast code so that your game doesn’t become a mess.