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)
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)
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)
-- 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.
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.