Making my timer using RunService.Heartbeat:Wait()

Hey devs, I’m making a timer and want it to be precise, counting time by the millisecond. I could get it to work using wait(), although that would prove to be very inaccurate and the timer wouldn’t be good (as seen in this article) I don’t want to use wait, but use RunService.Heartbeat:Wait() instead. I tried using it but time now goes by way too fast and the timer is still inaccurate. Help? Here is the code I have so far:

The script makes the timer start when a part is touched and stop when another is touched.

local Players = game.Players
local RunService = game:GetService("RunService")

local player = Players.LocalPlayer

local minutes = 0
local seconds = 0
local miliseconds = 0

local stop = false

game.Workspace.Timer.Touched:Connect(function()
	
	wait(.5)
	
	if minutes ~= 0 or seconds ~= 0 or miliseconds ~= 0 then
		minutes = 0
		seconds = 0
		miliseconds = 0
	end
	
	repeat

		script.Parent.Text = minutes..":"..seconds..":"..miliseconds
		
		miliseconds = miliseconds + 1
		
		if miliseconds == 1000 then
			miliseconds = 0
			seconds = seconds + 1
		end
		
		if seconds == 60 then
			seconds = 0
			minutes = minutes + 1
		end
		
		RunService.Heartbeat:Wait()
		
	until stop == true
	
end)


game.Workspace.Stop.Touched:Connect(function()
	print("Stopped")
	stop = true
	wait(.1)
	stop = false
end)

RunService.Heartbeat fires every frame, meaning if someone has a low frame-rate of 20 it’s only gonna run 20 times a second. You can use tick() instead. See here: Tick() How do I use it?

3 Likes
  1. Small improvement: you could fix up how you use your stop variable a little bit so you don’t need to wait(0.1):

    local stop = false
    
    game.Workspace.Timer.Touched:Connect(function()
      -- ...
      repeat
        -- ...
      until stop
    
      -- just set to false here:
      stop = false;
    end)
    
    game.Workspace.Stop.Touched:Connect(function()
      print("Stopped")
      stop = true
      -- don't do anything else
    end)
    
  2. Don’t increment every frame. Just save the time you started and figure out how long it’s been:

    local stop = false
    local startTime = nil
    
    game.Workspace.Timer.Touched:Connect(function()
      local startTime = tick()
    
      repeat
        local elapsed = tick() - startTime -- in seconds
        print("ELAPSED:", elapsed, "seconds")
      until stop
    
      stop = false;
    end)
    
    game.Workspace.Stop.Touched:Connect(function()
      print("Stopped")
      stop = true
    end)
    

edit:

  1. tick() is already ms-precise. You can extract other variables from it with math:

    local elapsed = 9965.0234
    local milliseconds = math.floor(1000 * math.fmod(elapsed, 1))
    local seconds = math.floor(elapsed % 60)
    local minutes = math.floor(elapsed / 60)
    
    print(string.format("Elapsed time: %d:%.02d.%.03d", minutes, seconds, milliseconds))
    -- Elapsed time: 166:05.023
    
3 Likes

Alright, using tick is great, but then the loop repeats without a wait and that would obviously make studio crash… How should I go about avoiding that?

1 Like

@nicemike40 Basically I still need to use a wait() here, although they are glitchy… What should I do to avoid needing that?

I would remove the loop and connect to RunService.Stepped instead:

local RunService = game:GetService("RunService")

local connection = nil

workspace.Timer.Touched:Connect(function()
    if (connection) then return end -- already running, do nothing

    local startTime = tick()

    -- create a connection that will run every frame until 
    -- connection is disconnected in the stop method
    connection = RunService.Stepped:Connect(function()
        local elapsed = tick() - startTime
        local milliseconds = math.floor(1000 * math.fmod(elapsed, 1))
        local seconds = math.floor(elapsed % 60)
        local minutes = math.floor(elapsed / 60)

        -- prints out every single frame
        print(string.format("Elapsed time: %d:%.02d.%.03d", minutes, seconds, milliseconds))
    end)
end)

workspace.Stop.Touched:Connect(function()
    print("Stopped")
    if (connection) then
        connection:Disconnect()
        connection = nil
    end
end)
1 Like

@nicemike40 Thank you so much. Now I’m trying to display it on the GUI by changing the print statement to script.Parent.Text but that just displays “ELAPSED” on the Ui… I tried to fix it but was never able to without breaking the rest…

You’d just replace the print call with

script.Parent.Parent.Text = string.format(...)
1 Like

Could you post your exact code?

Sorry :confused: I was just quite idiotic lol. I resolved it thank you!

1 Like

There is something called “DeltaTime”