Stamina system not working

Could use a bit of help here, I’ve been at it for around 6 ish hours now.
Essentially, I’m making a sprinting system that uses stamina. After 2 seconds of not sprinting, your stamina regenerates.
Here’s the video showing what’s happening: untitled horror game framework testing - Roblox Studio 2023-03-04 13-58-02 . As you can see, its really unpredictable, random and really inconsistent.
Here’s my code (it looks long but its mostly because there are empty lines in between for readability):

-- services
local TS = game:GetService("TweenService")
local UIS = game:GetService("UserInputService")
local RS = game:GetService("RunService")

local player = game:GetService("Players").LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local hum = char:WaitForChild("Humanoid")

-- UI

local plrgui = player:WaitForChild("PlayerGui")
local stamsys = plrgui:WaitForChild("StaminaSystem")
local cover = stamsys:WaitForChild("Cover")

-- camera

local cam = game:GetService("Workspace").CurrentCamera

-- stamina system + walkspeed settings

local stamina = 200
local maxstamina = 200
local stamcost = 3
local stamregen = 2

local sprinting = Instance.new("BoolValue")
sprinting.Parent = player
sprinting.Name = "sprinting"
local lastSprintTime = tick()

stamina = math.clamp(stamina, 0, maxstamina)

local walkspeed = 16
local sprintspeed = 24

hum.WalkSpeed = walkspeed

-- update stamina bar
local function updatestamina()
	stamina = math.clamp(stamina, 0, maxstamina)
	cover:TweenSize(
		UDim2.new((stamina/maxstamina) * 0.258, 0, 0.048, 0),
		Enum.EasingDirection.InOut,
		Enum.EasingStyle.Linear,
		0
	)
end


-- deplete stamina when shift is held down
local function sprint()
	sprinting.Value = true
	hum.WalkSpeed = sprintspeed
	lastSprintTime = tick()
	while sprinting.Value and stamina > 0 do
		wait()
		if UIS:IsKeyDown(Enum.KeyCode.LeftShift) then
			stamina = stamina - stamcost
			updatestamina()
		else
			sprinting.Value = false
		end
	end
	sprinting.Value = false
end


local function regenerate()
	task.wait(2)
	if not sprinting.Value and stamina < maxstamina and tick() - lastSprintTime > 2 then
		for i = stamina, maxstamina, stamregen do
			stamina += stamregen
			updatestamina()
			task.wait(0.1)
		end
	end
	task.wait()
end

-- when shift is pressed
UIS.InputBegan:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.LeftShift then
		if stamina > 0 then
			sprint()
		end
	end
end)

-- when shift is released
UIS.InputEnded:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.LeftShift then
		hum.WalkSpeed = walkspeed
	end
end)

-- update stamina every frame
RS.RenderStepped:Connect(function()
	if sprinting.Value then
		if stamina == 0 then
			sprinting.Value = false
			hum.WalkSpeed = walkspeed
		end
	else
		if stamina < maxstamina then
			if stamina == 0 then
				sprinting.Value = false
				hum.WalkSpeed = walkspeed
			end
			stamregen = 3
			print(stamregen)
			regenerate()
		else
			print(stamregen)
			stamregen = 0
		end
	end
end)

Any help would be appreciated, this is mostly a logic error (and I am no logician).

You’re probably better off using TweenService instead of a loop.

But you can’t tween script variables…?

Then have an IntValue or a NumberValue instead of a variable and tween its value. That’s what I did.

OK, I’ll try that but bear in mind that I’m 99% sure this is a logic error as opposed to something wrong with the methods themselves…

1 Like

Unfortunately this didn’t work and doesn’t show the stamina value regenerating.

local function regenerate()
	task.wait(2)
	if not sprinting.Value and stamina < maxstamina and tick() - lastSprintTime > 2 then
		local properties = {Value = maxstamina}
		local regeninfo = TweenInfo.new(
			4,
			Enum.EasingStyle.Linear,
			Enum.EasingDirection.InOut,
			0,
			false,
			0
		)
		local regentween = TS:Create(staminainstance, regeninfo, properties)
		updatestamina()
		regentween:Play()
		updatestamina()
	end
	task.wait()
end

Are you putting this code in LocalScript or Script?

LocalScript. Also, no errors are in the output (further re-enforcing that this is a logic error) as you can see from the video.

All good, so here are the steps that I would do

  1. Instead of using variables to track stamina. Create 2 new NumberValue called Stamina and MaxStamina. You can put these values somewhere inside the game.Players.LocalPlayer

  2. Build a logic for reducing stamina or increasing stamina, but you will reduce the NumberValue that you just created above.

  3. Use GetPropertyChangedSignal(Value) to track stamina. When the value is changed, update the bar.

local staminaNumberValue = player.Stamina
local maxStaminaNumberValue = player.MaxStamina

local BarUI = ???
staminaNumberValue.GetPropertyChangedSignal("Value"):Connect(function()
       BarUI.Size = UDim2.fromScale((staminaNumberValue.Value * 100) / maxStaminaNumberValue.Value,1)
end)

Note: This implementation is no longer use TweenService, but the bar should scale linearly as you would expect.

Note 2: The bar should be scaled smoothly as long as you do not abruptly change the value of stamina will a large number.

1 Like

Try this:

if not sprinting.Value and stamina < maxstamina and tick() - lastSprintTime > 2 then
        lastSprintTime = tick()
		
        local properties = {Value = maxstamina}
		local regeninfo = TweenInfo.new(
			4,
			Enum.EasingStyle.Linear,
			Enum.EasingDirection.InOut,
			0,
			false,
			0
		)
		local regentween = TS:Create(staminainstance, regeninfo, properties)
		updatestamina()
		regentween:Play()
		updatestamina()
	end

And why are you adding this?

task.wait(2)

I don’t think that would really solve my issue. The only issue lies within the sprint() function and the regenerate() function.
The reason for the jitter and the inconsistency, as shown by the video, is because of the lines near the bottom which change the value of stamregen in order to enable or disable regeneration. Using :GetPropertyChangedSignal to repeatedly call updatestamina() won’t really fix this… Hence, using numbervalues won’t do anything either. It’s solely in that last chunk which breaks the whole system. Sorry for not making this clear.

As I said, I’d like it so that it’ll only start to regenerate after 2 seconds of not sprinting.
I tried your script and unfortunately it doesn’t tween the stamina value either…

In regards to your Note 2, as I said, the reason it doesn’t scale smoothly is exactly as you say, because the value of stamregen (which controls the change in Stamina) is changed in the last segment. That’s particularly what needs fixing.

you don’t need to add task.wait(2) since tick() is already waiting 2 seconds

local last = tick()

task.wait(2)
print(tick() - last)

--Output: 2.009

I removed the task.wait(2) and it regenerates instantly, and doesn’t wait 2 seconds of non-sprinting. This probably means that the lastSprintTime isn’t being properly used so I’ll try fix that.

update: tried messing around with it, and what ends up happening is that once the bar reaches full, it glitches out and when you try and sprint, the bar doesn’t go down at all and starts to jitter. It’s most likely because its trying to regenerate and sprint at the same time.

Ok, so. Personally, I think your method should be reworked. So here is my idea.

How about we try regenerate() inside InputEnded()? Instead of using RenderStepped, we regenerate stamina right after the player stop running (stop pressing Shift key.) We create a new variable to keep tracking if the player is already regenerating their stamina. There should be only 1 thread running for this. task.spawn() can also be implemented.

local regenerationThreadCount = 0

local function regeneration()
      regenerationThreadCount += 1 -- Increasing thread, cancel previous regeneration function.
      -- This line will break the latest regeneration loop if there is any.

      task.wait(2) -- Hold for 2 sec before regen again.
     
      if regenerationThreadCount == 2 then
           regenerationThreadCount = 1 -- Resetting thread count. Keep it at 1 so the loop below can run.
      end
      -- The top line will make the thread count == 2, to see the picture clearly, I use extra if statement.

      -- Start new loop increasing stamina
      while regenerationThreadCount ~= 2 and stamina < maxStamina and not sprinting.Value do
              -- Regen
      end
end

Note: The code above is solely my idea, never tested, and never tried it.

Also, now you have to track players’ stamina when they are holding Shift key, but their stamina is already zero.

1 Like

Thank you so much, this works really well and really efficiently. I never though about considering the number of times regenerate was called, this was a clever approach! Thanks again!

1 Like

I edited my post about about an extra thing that you have to consider. Btw, nice! I’m glad I could help you with this! Enjoy coding!

There is a slight problem but I think it could be easily resolved using the RegenerationThreadCount.
If you try sprinting while it’s regenerating, it starts to jitter again. Is there a way to override regeneration if you are sprinting?