§ How to · with Picoo

How to make sprint and stamina in Roblox

By Sametcan Tasgiran, Founder & Developer·Published ·Updated

Sprint is the one mechanic where the exploit and the feature are literally the same line of code.

Players own their own character, so WalkSpeed is theirs to change — which makes stamina a server-validated budget rather than a client-side lock.

Shift to sprint

Hold to sprint on keyboard, a button on mobile, both driving the same state.

Stamina budget

Drain while sprinting, regen after a delay, with the server holding the authoritative number.

Dash

A burst of movement with its own cost, cooldown and a direction that cannot be a zero vector.

Server validation

Speed checked against travelled distance with tolerance, so lag does not read as cheating.

Stamina bar

Smooth bar with a distinct low-stamina state so exhaustion is visible before it bites.

Exhaustion rules

Hitting zero enforces a recovery window instead of letting players stutter-sprint at 1%.

Files Picoo ships for this prompt

2 files · 180 lines · ~30s · 1 credit

StaminaService

Authoritative stamina, drain/regen, dash cost, speed validation.

115 lines

SprintClient

Input, WalkSpeed application, dash impulse, bar updates.

65 lines

Sample output: ServerScriptService.StaminaService

-- Roblox gives each client network ownership of its own character, so a player
-- can already set their own WalkSpeed — that is not a hole you can close, it is
-- how the physics model works. The defence is not "block the change", it is
-- "measure what actually happened and compare it to what stamina allowed".
local MAX_SPRINT = 24
local TOLERANCE = 1.35        -- lag, slopes and knockback all inflate distance

local function validate(plr: Player, dt: number)
	local root = getRoot(plr); if not root then return end
	local moved = (root.Position - last[plr]).Magnitude
	last[plr] = root.Position

	local allowed = (stamina[plr] > 0 and MAX_SPRINT or WALK) * dt * TOLERANCE
	if moved > allowed then
		strikes[plr] += 1
		if strikes[plr] > 5 then teleportBack(plr) end   -- sustained, not one frame
	else
		strikes[plr] = math.max(0, strikes[plr] - 1)
	end
end

-- Regen with a DELAY after the last drain. Regenerating immediately lets players
-- tap sprint forever and makes the bar meaningless.
local REGEN_DELAY = 1.5
local function step(plr: Player, dt: number, sprinting: boolean)
	if sprinting then
		stamina[plr] = math.max(0, stamina[plr] - DRAIN * dt)
		lastDrain[plr] = os.clock()
	elseif os.clock() - lastDrain[plr] >= REGEN_DELAY then
		stamina[plr] = math.min(MAX, stamina[plr] + REGEN * dt)
	end
end

Building sprint and stamina in Roblox

Sprint is where new Roblox developers first meet the platform's trust model, and it is an uncomfortable introduction.

The problem is that Roblox hands each client network ownership of its own character. That is what makes movement feel responsive — the player's machine simulates their character and tells the server where it ended up. It also means WalkSpeed is a value the player controls. You can set it on the server, and they can set it back. There is no version of this where a property assignment is a lock.

So stamina stops being a client-side restriction and becomes a server-side budget. The client applies the speed change because that is what keeps sprinting responsive, and the server independently tracks how much stamina the player should have and how far they should therefore have travelled. When measured distance exceeds what stamina allowed — repeatedly, not once — that is your signal. The repetition matters: a single frame over budget is a lag spike, a slope, or knockback, and punishing it produces angry reports from players doing nothing wrong.

The feel of the system lives in regen. Refill instantly on release and the bar stops mattering, because tapping sprint is free. A delay after the last drain, and a hard recovery window when the bar empties, turn stamina into a decision about when to spend rather than a rhythm-tapping exercise.

Dash brings one specific trap worth naming: normalising a zero vector. If the player is stationary when they dash, their move direction has no length, and dividing by that length produces not-a-number, which spreads into the velocity and either launches the character somewhere absurd or does nothing at all. Check the magnitude first and fall back to where the character is facing.

See more on the Luau generator, the game builder, or browse the full blog.

Frequently asked

Can't an exploiter just set WalkSpeed?+

Yes, and no amount of client code stops it — the player's client owns their character's physics, so speed is theirs to change. What you can do is measure: compare the distance actually travelled against what their stamina permitted, over several samples. Flag sustained impossibility, not a single frame, or lag spikes and knockback will punish honest players.

Should the server set WalkSpeed?+

Setting it on the server replicates, but the client can immediately set it back, so it is not a lock. Treat the server's value as the intended state and the distance check as the enforcement. Applying the change on the client keeps sprint responsive, which matters more than a lock that does not hold anyway.

Why does my stamina bar feel bad?+

Usually instant regen. If stamina refills the moment sprinting stops, players tap the key and effectively sprint forever, and the bar becomes decoration. A regen delay of a second or two after the last drain, plus a forced recovery window when it hits zero, is what makes stamina a real decision.

Why does my dash sometimes throw the player nowhere?+

The dash direction was a zero vector — the player was standing still, or their move direction was flattened to nothing — and calling .Unit on a zero vector produces not-a-number, which propagates into the velocity. Check the magnitude before normalising and fall back to the character's facing direction.

How do I make sprint work on mobile?+

Route input through a single sprint state that both a key binding and an on-screen button set. ContextActionService can create the mobile button for you from the same action, which keeps you from writing the mechanic twice and letting the two drift apart.

Related Picoo prompts