A simple, reusable, and allocation-free FPS counter designed for any Roblox experience and is super configurable, and lightweight.
This module can be used in multiple scenarios, and can be safely used in games, plugins, tools, or diagnostics systems which require FPS counting.
It avoids per-frame string updates and instead samples FPS over a configurable time window, making it both accurate and performance-friendly.
Why is this any better than other built in roblox methods?
- Configurable sample window (default: 0.5s)
- Event-based updates (no polling required)
- Zero UI assumptions — use with TextLabels, graphs, logs, or analytics
- Clean lifecycle control (Enable / Disable)
- Safe to enable/disable repeatedly
- RenderStepped-based accuracy, the best possible way to calculate FPS during runtime
How does this work?
- Accumulating dt from RenderStepped
- Counting frames during a sampling window
- Calculating FPS as:
FPS = frames / accumulatedTime - When the window completes, the FPS value is:
• Stored internally
• Emitted via an optional callback
All of these methods ensure smooth FPS calculations, based on whether a device is laggy or not it ACTUALLY calculates the amount of frames per update!
Installation
Place the module anywhere accessible, then require it:
local FPSCounter = require(path.to.FPSCounter)
Then call the .new() function
local fps = FPSCounter.new(0.5)
After you create a new FPS counter, you will be given methods and variables to control how FPS is displayed!
--[[
:Enable updates every interval (configurable) so that you can externally
update a textlabel every interval
]]
fps:Enable(function(value)
label.Text = string.format("FPS: %d", math.round(value))
end)
FULL DEMONSTRATION:
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FPSCounter = require(ReplicatedStorage.Modules.FPSCounter)
-- UI setup
local player = Players.LocalPlayer
local gui = Instance.new("ScreenGui")
gui.Parent = player:WaitForChild("PlayerGui")
local label = Instance.new("TextLabel")
label.Size = UDim2.fromOffset(200, 40)
label.Position = UDim2.fromOffset(20, 20)
label.BackgroundTransparency = 0.3
label.TextScaled = true
label.Text = "FPS: --"
label.Parent = gui
-- Create FPS counter
local fps = FPSCounter.new(0.5)
-- Enable with callback
fps:Enable(function(value)
label.Text = string.format("FPS: %d", math.round(value))
end)
-- Example manual query after 5 seconds
task.delay(5, function()
print("Manual FPS check:", fps:GetFPS())
print("Is Enabled:", fps:IsEnabled())
end)
-- Example disable after 10 seconds
task.delay(10, function()
fps:Disable()
label.Text = "FPS: Paused"
end)
Let me know any improvements or suggestions below, if this module isn’t helpful for you, I don’t mind if you copy my code to use in a different way, as this is free and open-sourced ![]()