Feedback and help about my stamina system

Hi, devs.
This is my first time posting in the forum. I would like to hear your opinions on the code for my stamina system, to see if there’s anything I can optimize that I haven’t noticed. I also want to implement a kind of “cooldown” for when stamina reaches 0, so that it waits a few seconds before it can regenerate; I haven’t found a way to do that yet.

-- [[ SERVICES ]]

local ContActServ = game:GetService('ContextActionService')
local RunServ = game:GetService('RunService')
local TweenServ = game:GetService('TweenService')

-- [[ PLAYER ]]

local Player = game:GetService('Players').LocalPlayer
local PlayerHum = Player.Character:WaitForChild('Humanoid')
local Camera = workspace.CurrentCamera

-- [[ VALUES ]]

local Stamina = 100
local staminaRegen = 0.3
local staminaDrain = 0.5

local defaultSpeed = 16
local sprintSpeed = 25

local sprintView = 100
local defaultView = Camera.FieldOfView

-- [[ BOOLEANS ]]

local isSprinting = false

-- [[ TWEENS ]]

local tweenInfo = TweenInfo.new(0.3)

local sprintViewTween = {
	FieldOfView = sprintView
}
local defaultViewTween = {
	FieldOfView = defaultView
}

-- [[ FUNCTIONS ]]

local function HandleSprintInput(actionName, inputState, input)
	if inputState == Enum.UserInputState.Begin then
		isSprinting = true
	else
		isSprinting = false
	end	
	
	--// print('Input state: '..tostring(inputState))
end

local function UpdateSprintState()
	if isSprinting and PlayerHum.MoveDirection.Magnitude > 0 then
		if Stamina > 0 then 
			TweenServ:Create(Camera, tweenInfo, sprintViewTween):Play()
			PlayerHum.WalkSpeed = sprintSpeed
			Stamina = math.max(0, Stamina - staminaDrain)
		else
			TweenServ:Create(Camera, tweenInfo, defaultViewTween):Play()
			PlayerHum.WalkSpeed = defaultSpeed
			isSprinting = false
		end
	else
		TweenServ:Create(Camera, tweenInfo, defaultViewTween):Play()

		if Stamina < 100 then
			Stamina = math.min(100, Stamina + staminaRegen) 
		end
	end

	-- print('Speed: '..PlayerHum.WalkSpeed)
	print('Stamina: '..Stamina)
end


-- [[ MAIN ]]

ContActServ:BindAction('Sprinting', HandleSprintInput, false, Enum.KeyCode.LeftShift)
RunServ.RenderStepped:Connect(UpdateSprintState)
2 Likes

Its a pretty good stamina script, by the way, it has some issues on it

  1. Memory Management Issues:
local tweenInfo = TweenInfo.new(0.3)
local sprintViewTween = { FieldOfView = sprintView }
  • Creates new tween instances every frame in UpdateSprintState
  • Wastes memory by repeatedly creating identical tweens
  • Can lead to memory leaks over time
  1. State Management Issues:
local Stamina = 100
local isSprinting = false
  • Global-like variables scattered throughout
  • No centralized state management
  • Makes it harder to track state changes
  • Could lead to race conditions
  1. Performance Issues:
TweenServ:Create(Camera, tweenInfo, sprintViewTween):Play()
PlayerHum.WalkSpeed = sprintSpeed
  • Updates WalkSpeed even when it hasn’t changed
  • Creates new tween instances unnecessarily
  • No optimization for frequent operations
  1. Code Structure Issues:
local staminaRegen = 0.3
local staminaDrain = 0.5
local defaultSpeed = 16
local sprintSpeed = 25
  • Scattered configuration values
  • No clear separation between config and logic
  • Hard to maintain and modify
  1. Missing Error Handling:
local PlayerHum = Player.Character:WaitForChild('Humanoid')
  • No timeout on WaitForChild
  • Could potentially hang indefinitely
  • No error handling for missing character
  • If the script is in StarterCharacterScripts (which I assume it is) it is simpler just to put script.Parent:WaitForChild(“Humanoid”,true)
  1. Function Design Issues:
local function UpdateSprintState()
  • Large function doing multiple things
  • Mixed responsibilities
  • Hard to test and maintain
  • No use of deltaTime for smooth updates
  1. Input Handling Issues:
local function HandleSprintInput(actionName, inputState, input)
  • Unnecessary parameters on Function (input)
  • No debounce protection
  • Could lead to input spamming
  1. Resource Management:
local Camera = workspace.CurrentCamera
  • No caching of frequently accessed objects
  • Repeated service calls
  • Could impact performance
  1. Update Logic Issues:
if isSprinting and PlayerHum.MoveDirection.Magnitude > 0 then
  • Nested if statements make logic hard to follow
  • No clear separation between state update and visual update
  • Could lead to edge cases
  1. Constants Management:
local sprintView = 100
local defaultView = Camera.FieldOfView
  • Magic numbers throughout code (if you dont know what is a Magic number click this)
  • No clear configuration section
  • Hard to modify game feel
  1. Initialization Issues:
  • No clear initialization block
  • Services and variables mixed together
  • No cleanup handling
  1. Documentation Issues:
  • i forgor

To fix these issues, you would need to:

  1. Implement proper state management
  2. Cache frequently used objects
  3. Pre-create tweens
  4. Add proper error handling
  5. Optimize performance-critical code
  6. Add proper documentation
  7. Organize code better
  8. Add configuration management
  9. Implement proper cleanup

btw here the code with the cooldown function and some optimization:

-- [[ SERVICES ]]
local ContActServ = game:GetService('ContextActionService')
local RunServ = game:GetService('RunService')
local TweenServ = game:GetService('TweenService')

-- [[ CONSTANTS ]]
local STAMINA_CONFIG = {
	MAX = 100,
	REGEN_RATE = 0.3,
	DRAIN_RATE = 0.5,
	COOLDOWN_DURATION = 2
}

local SPEED_CONFIG = {
	DEFAULT = 16,
	SPRINT = 25
}

local VIEW_CONFIG = {
	DEFAULT = workspace.CurrentCamera.FieldOfView,
	SPRINT = 100,
	TWEEN_INFO = TweenInfo.new(0.3)
}

-- [[ STATE ]]
local State = {
	stamina = STAMINA_CONFIG.MAX,
	isSprinting = false,
	cooldownRemaining = 0
}

-- [[ CACHE ]]
local camera = workspace.CurrentCamera
local humanoid = script.Parent:WaitForChild("Humanoid",true)

-- [[ VIEW TWEENS ]]
local viewTweens = {
	sprint = TweenServ:Create(camera, VIEW_CONFIG.TWEEN_INFO, {
		FieldOfView = VIEW_CONFIG.SPRINT
	}),
	default = TweenServ:Create(camera, VIEW_CONFIG.TWEEN_INFO, {
		FieldOfView = VIEW_CONFIG.DEFAULT
	})
}

-- [[ UTILITY FUNCTIONS ]]
local function updateSpeed(speed)
	if humanoid.WalkSpeed ~= speed then
		humanoid.WalkSpeed = speed
	end
end

local function updateView(isSprinting)
	local tween = isSprinting and viewTweens.sprint or viewTweens.default
	tween:Play()
end

-- [[ CORE FUNCTIONS ]]
local function handleSprintInput(_, inputState)
	local isStarting = inputState == Enum.UserInputState.Begin

	if State.isSprinting ~= isStarting then
		State.isSprinting = isStarting

		if not isStarting and State.stamina < STAMINA_CONFIG.MAX then
			State.cooldownRemaining = STAMINA_CONFIG.COOLDOWN_DURATION
		end
	end
end

local function updateStaminaState(deltaTime)
	local isMoving = humanoid.MoveDirection.Magnitude > 0
	local canSprint = State.stamina > 0 and isMoving
	local isActiveSprint = State.isSprinting and canSprint

	-- Update stamina based on state
	if isActiveSprint then
		State.stamina = math.max(0, State.stamina - STAMINA_CONFIG.DRAIN_RATE)
		State.cooldownRemaining = 0
	elseif State.cooldownRemaining > 0 then
		State.cooldownRemaining = math.max(0, State.cooldownRemaining - deltaTime)
	elseif State.stamina < STAMINA_CONFIG.MAX then
		State.stamina = math.min(STAMINA_CONFIG.MAX, State.stamina + STAMINA_CONFIG.REGEN_RATE)
	end

	-- Update character state
	updateSpeed(isActiveSprint and SPEED_CONFIG.SPRINT or SPEED_CONFIG.DEFAULT)
	updateView(isActiveSprint)
	
	print(
		string.format(
			"Stamina: %.1f | Cooldown: %.1f", 
			State.stamina, 
			State.cooldownRemaining
		)
	)
end

-- [[ SCRIPT/INIT ]]

ContActServ:BindAction('Sprinting', handleSprintInput, false, Enum.KeyCode.LeftShift)
RunServ.RenderStepped:Connect(updateStaminaState)

The optimized version I provided earlier addresses most of these issues, try to analyze the code and its structure, it may help you to minimize the mistakes you may make in the future.

(I feel it was excessive but I hope I helped you :coefficients::boom:)

2 Likes

Excessive? Nah man, this is what I need, a good explanation from a good person. Thanks!

1 Like