Stamina System Problems

Hello again.

Currently, I am creating a stamina System, basic one, but the problem is that they are 2 scripts:

First Script, The MainMovement Script:

local Player = game.Players.LocalPlayer
local Character = Player.Character

local Humanoid = Character:WaitForChild("Humanoid")::Humanoid
local RootPart = Character:WaitForChild("HumanoidRootPart")::Part

local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local SlowSpeed = 8
local WalkSpeed = 16
local RunSpeed = 30 
local CurrentSpeed = WalkSpeed
local IsSlowWalking = false
local IsSpaceBugSpeed = false

local GroundAccel = 16 
local GroundDecel = 12 
local AirAccel = 15 

local PlayerVelocity = Vector3.zero 
local LinearVelocity = Instance.new("LinearVelocity",RootPart)
LinearVelocity.Attachment0 = RootPart:WaitForChild("RootAttachment")
LinearVelocity.MaxForce = 25500 
LinearVelocity.RelativeTo = Enum.ActuatorRelativeTo.World
LinearVelocity.VelocityConstraintMode = Enum.VelocityConstraintMode.Plane
LinearVelocity.PrimaryTangentAxis = Vector3.new(1,0,0)
LinearVelocity.SecondaryTangentAxis = Vector3.new(0,0,1)

function Move(direction, targetSpeed, rate1, rate2)
	local CurrentVelocity = PlayerVelocity
	local TargetVelocityMagnitude = direction * targetSpeed
	local Add = (TargetVelocityMagnitude - CurrentVelocity)
	local Rate = if CurrentVelocity.Magnitude < targetSpeed or direction.Magnitude > 0 then rate1 else rate2

	PlayerVelocity += Add * Rate * RunService.Heartbeat:Wait() 
end

local Connection = RunService.RenderStepped:Connect(function(dt)
	for i, v in Character:GetChildren() do
		if v:IsA("BasePart") then
			v.CustomPhysicalProperties = PhysicalProperties.new(.7,0,0,100,100)
		end
	end

	Humanoid.WalkSpeed = CurrentSpeed 
	Humanoid.UseJumpPower = true

	local Grounded = Humanoid:GetState() ~= Enum.HumanoidStateType.Freefall

	if Grounded and not Humanoid.Jump then
		Move(Humanoid.MoveDirection, CurrentSpeed, GroundAccel, GroundDecel)
	end

	if not (Humanoid:GetState() == Enum.HumanoidStateType.Climbing) then
		LinearVelocity.PlaneVelocity = Vector2.new(PlayerVelocity.X, PlayerVelocity.Z)
	else
		LinearVelocity.PlaneVelocity = Vector2.zero
		PlayerVelocity = Vector3.zero
	end
end)

UserInputService.InputBegan:Connect(function(input, p)
	if p then return end
	
	if input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.ButtonR1 then
		if Humanoid.MoveDirection.Magnitude > 0 then
			CurrentSpeed = RunSpeed
		end
		
	elseif input.KeyCode == Enum.KeyCode.V or input.KeyCode == Enum.KeyCode.ButtonR2 then
		IsSlowWalking = not IsSlowWalking
		if IsSlowWalking then
			CurrentSpeed = SlowSpeed
		else
			CurrentSpeed = WalkSpeed
		end	
	end
end)

UserInputService.InputEnded:Connect(function(input, p) 
	if p then return end
	
	if input.KeyCode == Enum.KeyCode.LeftShift or input.KeyCode == Enum.KeyCode.ButtonR1 then
		CurrentSpeed = WalkSpeed
	end
end)

Second Script, The StaminaBar Script:

local UIS = game.UserInputService
local TweenService = game.TweenService
local BarInfo = TweenInfo.new(0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut, 0, false, 0)

local Player = game.Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()

local BackGroundBar = script.Parent.Parent
local Bar = script.Parent
local Stamina = 250
local IsRunning = false

Stamina = math.clamp(Stamina, 0, 250)

local function updateBarVisibility()
	if IsRunning and Character.Humanoid.MoveDirection.Magnitude > 0 then
		TweenService:Create(BackGroundBar, BarInfo, {BackgroundTransparency = 0}):Play()
		TweenService:Create(Bar, BarInfo, {BackgroundTransparency = 0}):Play()
	else
		TweenService:Create(BackGroundBar, BarInfo, {BackgroundTransparency = 1}):Play()
		TweenService:Create(Bar, BarInfo, {BackgroundTransparency = 1}):Play()
	end
end

local function regenerateStamina()
	while Stamina < 250 and not IsRunning do
		Stamina = Stamina + 0.3
		script.Parent:TweenSize(UDim2.new(Stamina / 250, 0, 1, 0), "InOut", "Quad", 0)
		task.wait()
	end
end

UIS.InputBegan:Connect(function(input, p)
	if p then return end
	
	if input.KeyCode == Enum.KeyCode.LeftShift then
		if Character.Humanoid.MoveDirection.Magnitude > 0 then
			IsRunning = true
			updateBarVisibility()
			
			while Stamina > 0 and IsRunning do
				Stamina = Stamina - 0.1
				script.Parent:TweenSize(UDim2.new(Stamina / 250, 0, 1, 0), "InOut", "Quad", 0)
				task.wait()
				if Stamina <= 0 then
					IsRunning = false
				end
			end
		end
	end
end)

UIS.InputEnded:Connect(function(input, p)
	if p then return end
	
	if input.KeyCode == Enum.KeyCode.LeftShift then
		IsRunning = false
		
		task.wait(1) 
		updateBarVisibility()
		regenerateStamina()
	end
end)

Now, the problem is the stamina, isn’t staminaing it. I mean, those 2 scripts are not in 1. If the stamina bar is 0/ the bar is disappeared , the player can still run. I only need to fix this bug, can someone help me?

Best, XMLcard

2 Likes

You can share the stamina in three ways, ordered by what I would recommend:

  1. ModuleScript
    The module can store a Stamina value. While you can manage the data directly (i.e. module.Stamina -= 1), I would recommend you set a setter and getter, or an add or subtract method.

  2. Attributes
    You can have the stamina system in the MainMovement script instead because of its connection with movement. The StaminaBar script can then listen to the attribute’s change event via MainMovement:GetAttributeChangedSignal("<change the string here to the name of the attribute>"). In the editor, each object has an attributes section that you can use to add and edit attributes. In code, you can use Object:SetAttribute("<name of attribute here>", <value of attribute here>) and Object:GetAttribute("").

  3. NumberValue
    This is a more, legacy method of handling numbers since attributes can largely do the same things that most value classes do. There are some exceptions to when these value objects will be useful, notably the ObjectValue. These objects have a .Value property which you can use. Then, use .Changed to listen to the value changes.

For deciding which speed should be used based on input and stamina, consider using a function that decides that instead, then connect that function to the change in stamina from both user input and attributes [3] or NumberValues, [2] or using RunService’s scheduling signals so it can actively check the module’s [1] value.

Extra: I recommend SOLID principles, especially the single-responsibility principle. You can completely separate the stamina system from either script using the first method, ensuring that other future scripts that only specifically needs the stamina system don’t have to know about MainMovement or StaminaBar’s existence.

4 Likes

Thanks man, But I’m currently trying to do all Solutions, but still, nothing works…

If you were me, How should I write the code? Should I add more scripts? I just don’t know if I’m doing wrong.

Um, so after hours… Nothing still worked, and I really want to make this stamina system by my own with a costum movement.

Sorry for the late reply. What else isn’t working in your attempts?

The stamina isn’t staminaing it. Those 2 things can’t be assambled or something. If you were me, How to fix this bug step by step, cuz I don’t know what am I doing. Maybe, if you write step by step, I can try to succeed?

make a module script

in the module script

local module = {} :: {
  stamina : number
}

module.stamina = 0

return module

then in both scripts

local stamina = 10 -- REMOVE and use staminaShared.stamina instead
local staminaShared = require(the module script)

--when editing stamina:
staminaShared.stamina = 10

now they share the same stamina variable

1 Like

Man I’m jumping, it doesn’t work :frowning:

Maybe I should quit this stamina system.

Oh, sorry for the late reply, got busy with studies.

I’ll try to enumerate steps for the movement and stamina:

  • Stamina

    1. Use a ModuleScript
    2. Have a method of setting and getting the value. What I have in mind is:
    • a table with a stamina member
    • getters and setters (i.e. getStamina(), subtract(num), add(num))
      • Make sure methods that change the stamina don’t result in unexpected numbers, like negative numbers or numbers past a certain limit)
    1. Optional: Add Bindable events that listen to changes, increases, and/or decreases, as well as if the stamina is full/empty.
    • This is better implemented with getters/setters.
  • Movement

    1. Require the new Stamina ModuleScript
    2. For each frame you sprint, subtract a certain amount of the stamina.
    • If your Stamina MS is just a table with a member, set the member directly, like StaminaModule.stamina -= <num>.
    • If your Stamina MS has getters/setters, call the setters instead (StaminaModule:subtract(<num>))
    1. For every time you jump (with being grounded considered), do the same thing, just not every frame.
    2. You can stop sprinting (or jumping) by checking the Stamina if it’s zero.
    • If your Stamina MS is just a table with a member, get the member directly, like if (StaminaModule.stamina) > 0 then ....
    • If your Stamina MS has getters/setters, call the getters instead (if (StaminaModule:isEmpty(<num>) == true) then...) or (if (StaminaModule:getStamina() > 0) then ...).

I have my implementation of a Stamina system you can analyse, although it will surely not work without setting the BindableEvents under it.

My Stamina module
-- BindableEvents for simplicity.
-- Fired on any change to the stamina except if the difference is non-zero.
local StaminaChanged = script.StaminaChanged

-- Fired on the relevant change to the stamina except if the difference is non-zero.
local StaminaGained = script.StaminaGained
local StaminaLost = script.StaminaLost

-- Fired if the change resulted in reaching the numeric boundaries of the stamina except if the difference is non-zero.
local StaminaReplenished = script.StaminaReplenished -- When stamina reaches the capacity.
local StaminaDepleted = script.StaminaDepleted -- When stamina reaches 0.

-- Fired if the capacity changed.
local CapacityChanged = script.CapacityChanged


local staminaCapacity = 100
local staminaRemaining = 0



-- Little helper function to manage the stamina change events.
local function tryFireEventsByStaminaChange(lastStamina : number, newStamina : number)
	local difference = lastStamina - newStamina
	
	if (difference == 0) then return end
	
	StaminaChanged:Fire(newStamina)
	
	if (difference > 0) then
		StaminaGained:Fire(newStamina)
		
		if (newStamina == staminaCapacity) then
			StaminaReplenished:Fire()
		end
	else
		StaminaLost:Fire(newStamina)
		
		if (newStamina == 0) then
			StaminaDepleted:Fire()
		end
	end
end



local Stamina = {}



function Stamina:add(gain : number) : number
	local currentStamina = staminaRemaining
	
	-- Cap the new stamina amount to stamina capacity
	staminaRemaining = math.min(currentStamina + gain, staminaCapacity)
	
	tryFireEventsByStaminaChange(currentStamina, staminaRemaining)
	
	-- The actual gain if the stamina has been capped. If the current stamina was at 92, then gained
	-- 20 pts, the real gain would have been 112, but because it was capped to 100, the actual gain
	-- is 20 - (112 - 100), or 8 pts. This is easier done with math.min().
	return math.min(gain, staminaCapacity - currentStamina)
end



function Stamina:subtract(cost : number) : number
	local currentStamina = staminaRemaining
	local newStamina = currentStamina - cost

	-- Cap the new stamina amount to 0
	staminaRemaining = math.max(newStamina, 0)
	
	tryFireEventsByStaminaChange(currentStamina, staminaRemaining)
	
	-- Somewhat the same idea as with Stamina:add(). However, if the `newStamina` variable is below
	-- 0, two negatives make a positive, so add `newStamina` to cost instead (i.e. 20 + (-5) = 15).
	return math.min(cost, cost + newStamina)
end



function Stamina:setCapacity(size : number)
	if (size < 0) then
		error(`Stamina capacity cannot be less than zero (got {size})`)
	end
	
	staminaCapacity = size
	CapacityChanged:Fire(size)
end



function Stamina:getCapacity() : number
	return staminaCapacity
end



function Stamina:getRemaining() : number
	return staminaRemaining
end



-- Little helper function to have no messy lines just to reference the table of Stamina and its
-- events. (i.e. no need to store the object in one variable, the module itself [with require()]
-- on another. Just call this.)
function Stamina:getSignal(name : string)
	local object = script:FindFirstChild(name)
	
	if (not object:IsA("BindableEvent")) then
		return nil
	end
	
	return object
end



return Stamina

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.