'Stopwatch in Roblox Lua using a while loop

I am making a Roblox game and I want it to have a stopwatch. The stopwatch works, but it counts very slowly for some reason. Here's my ScreenGui in StarterGui: ScreenGui stopwatch

Here's the code inside the LocalScript:

local timer = script.Parent.Timer
local tms = 00
local ts = 00
local tm = 00
local tt
local tts
local y = 0
local whichtower = game.Players.LocalPlayer:FindFirstChild("WhichTower")
while true do
    wait(0.01)
    if whichtower.Value == "" then
        tms = 00
        ts = 00
        tm = 00
        tts = 0
    else
        tms = tms + 1
        if tms == 100 then
            ts = ts + 1
            tms = 0
            tts = tts + 1
            if ts == 60 then
                tm = tm + 1
                ts = 0
            end
        end
        tt = tostring(tm)..":"..tostring(ts)..":"..tostring(tms)
        timer.Text = tt
        game.Players.LocalPlayer:FindFirstChild("Time").Value = tt
    end
end


Solution 1:[1]

I have figured it out using a different set of code. Roblox limits the wait parameters to a minimum of 0.03 seconds, so that is why my previous code was not working.

Solution 2:[2]

The arbitrary wait() and loop is a possible cause of timing issues, although I can't see anything specific that might be slowing it down. Are you sure that :FindFirstChild on WHichTower is always returning results? Add a print statement there, so your debug window has a constant stream of values, and you can confirm if it's finding a suitable tower.

Also, you are only updating the text if there's a Tower; for the code where you set the values to 0, there's no timer.Text update.

But if you don't think that's the issue:

I'd try put your code in a Heartbeat function, called regularly and tied to the refresh rate (I think). Then you don't need the while loop, nor the wait() commands. The Heartbeat only runs as fast as the refresh rate, therefore, there's no point trying to running anything quicker than that because the screen won't update.

local lPlayers = game:GetService("Players")
local lRunSvc = game:GetService("RunService")
 
local function onPlayerAdded(pPlayer) -- pPlayer (variable name is up to you) is the ref to the joined player.
    print(pPlayer.Name .. " joined the game.")

    lRunSvc.Heartbeat:Connect(function()

        print("whichtower.value is:" .. whichtower.Value) -- View prints in the Output console
        if whichtower.Value == "" then
            tms = 00
            ts = 00
            tm = 00
            tts = 0
        else
            tms = tms + 1
            if tms == 100 then
                ts = ts + 1
                tms = 0
                tts = tts + 1
                if ts == 60 then
                    tm = tm + 1
                    ts = 0
                end
            end
            tt = tostring(tm)..":"..tostring(ts)..":"..tostring(tms)
            timer.Text = tt
            game.Players.LocalPlayer:FindFirstChild("Time").Value = tt
        end
    end)
end

lPlayers.PlayerAdded:Connect(onPlayerAdded) -- This is called when a player joins

Solution 3:[3]

Just as Vexen Crabtree has pointed out, the time that wait() will actually pause a script is based on a system clock's best guess of how much time has passed. Rather than counting up the milliseconds, a more reliable method for calculating passed time is to use the tick() function.

tick() will give you the current epoch time, which is the number of milliseconds that have passed since January 1st, 1970.

So, if you know a starting time, you can subtract it from the current time and get the number of milliseconds that have passed. This method doesn't rely on loop timings and will give a more accurate measure of the actual time that has passed. The amount of time that you chose with wait() will only reflect the speed at which the value will be updated.

local timer = script.Parent.Timer
local whichtower = game.Players.LocalPlayer:FindFirstChild("WhichTower")
local playerTime = game.Players.LocalPlayer:FindFirstChild("Time")

local startingTime = 0
local isTiming = false

-- attach a listener to know when to start the clock
whichTower.Changed:Connect(function(newValue)
    -- reset the clock to zero when the value is empty
    isTiming = newValue ~= ""

    if not isTiming then
        startingTime = 0
    else
        -- start the clock!
        startingTime = tick()
    end
end)

local refreshTime = 0.01
while true do
    wait(refreshTime)

    if isTiming then
        -- calculate the time that has passed
        local ms = tick() - startingTime

        -- calculate how many minutes have passed
        local tm = ms - (ms % (60 * 1000))
        ms = ms - tm
        tm = tm / 1000

        -- calculate how many seconds have passed
        local ts = ms - (ms % 1000)
        ms = ms - ts
        ts = ts / 1000

        -- format the remainder
        local tms = ms / 1000
        if #tostring(tms) > 2 then
            tms = string.sub(tostring(tms), 3)
        else
            tms = "0"
        end

        -- format the time into mm:ss.ss ex) 123:01.123
        local tt = string.format("%.2d:%.2d.%s", tm, ts, tms) 

        timer.Text = tt
        playerTime.Value = tt
    end
end

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 littleBitsman
Solution 2
Solution 3