Inaccurate Stopwatch

Hello! I’m currently making a stopwatch for my new game that will time the player on how long they complete a course (Obstacle Course, I forgot to mention this)and will fire a RemoteEvent to the server to save the time into a DataStore.

The problems that I’m having is that the stopwatch is inaccurate. Seen by this image.Screenshot_63
I’m currently printing out a variable which is subtracting tick() with another variable with tick() saved

Here’s the code:

-- Services

local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Variables
	
local Stopwatch = script.Parent
local EndTime = game.Workspace:WaitForChild("Part")
local StartTime = game.Workspace:WaitForChild("Part2")
local TickToServer = ReplicatedStorage.Remotes:WaitForChild("TickToServer")
local Debounce = false

-- Main

StartTime.Touched:Connect(function(hit)
	local HumanoidRootPart = hit.Parent:FindFirstChild("HumanoidRootPart")
	local Start = tick()
	Stopwatch.Visible = true

	if HumanoidRootPart ~= nil then
		local connection
		local connection2
		connection = RunService.RenderStepped:Connect(function()
			local Now = tick() - Start
			local Minutes = (math.floor(Now) / 60) % 60
			local Seconds = math.floor(Now) % 60
			local Milliseconds = math.floor((Now % 1) *  1000)
			
			print(Now)
			Stopwatch.Text = string.format("%0.2i:%.02i.%.03i", Minutes, Seconds, Milliseconds)
			
			connection2 = EndTime.Touched:Connect(function()
				connection:Disconnect()
				Stopwatch.TextColor3 = Color3.fromRGB(45, 255, 34)
				
				if not Debounce then
					Debounce = true
					TickToServer:FireServer(Now)
					wait(1)
					Debounce = false
				end
			end)
		end)
	end
end)

It’s in a Localscript if any of you are wondering.

1 Like

minutes = math.floor(now % 60)
seconds = math.floor(now - (minutes * 60))

I actually made something similar to what you are trying to do for A drag racing timing system I made for me and my friends. It used to run on elapsedTime, however that has since been depracated and I opted for os.clock. Basically what I did to get each split time, or each time in between parts being touched was as follows…

local function calculateSplit(beginTime)
	return os.clock() - beginTime -- this will give you the amount of time that has passed since they started the race 
end

local startTime = os.clock()

-- than somewhere else in your code you just do

local endTime = calculateSplit(startTime)

And than for the timing conversions to get the minutes, seconds, milliseconds I used an algorithm similar to that written by @Wesley1041 which is here :

function convertT(timeN)
	-- Credit to Wes for his time version algorithim
	
	local min = math.floor(timeN/60)
	local sec = math.floor(timeN)-60*min
	local milisec = math.floor((timeN-sec-min*60)*1000+0.5)
	
	if min < 1 then
		min = ""
	else
		min = min..":"
	end
	if sec < 10 then
		sec = "0"..sec
	end
	if milisec < 100 and milisec >= 10 then
		milisec = "0"..milisec
	elseif milisec < 10 then
		milisec = "00"..milisec
	end
	
	local output = min..sec.."."..milisec
	
	return output
end

Wesleys Model can be found here
https://www.roblox.com/library/2184218273/Timing-System-3-v1-1-by-Wesley1041

1 Like

The inaccuracy is because you’re doing this on a frame basis, the accuracy of the time is limited by how quickly a frame can pass. So 1 fps = 1 extra second of inaccuracy, 700fps = 0.0014 inaccuracy, etc.

The solution to this is to just not time it on a frame basis at all, all you need to do is; whenever what you’re trying to do ends, subtract the currentTime from the startTime. And that’s as high of an accuracy as you can get.

Also worth mentioning, tick() is deprecated and only counts up to a smaller amount of decimals, whereas os.clock is 6 decimals (micro seconds); so os.clock would be a better choice for time comparison here.

I’m doing as you told but obviously I’m doing something wrong here. The problem still persists and I’m now getting this after subtracting currentTime from startTime
Screenshot_64
Screenshot_66

Here’s the updated code if you’re curious:

-- Services

local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Variables

local Stopwatch = script.Parent
local EndTime = game.Workspace:WaitForChild("Part")
local StartTime = game.Workspace:WaitForChild("Part2")
local TickToServer = ReplicatedStorage.Remotes:WaitForChild("TickToServer")
local BrokenLoop = false
local Debounce = false

-- Main

StartTime.Touched:Connect(function(hit)
	local HumanoidRootPart = hit.Parent:FindFirstChild("HumanoidRootPart")
	local startTime = os.clock()
	Stopwatch.Visible = true

	if HumanoidRootPart ~= nil then
		while BrokenLoop == false do
			wait()
			
			local currentTime = os.clock() - startTime
			local Minutes = (math.floor(currentTime) / 60) % 60
			local Seconds = math.floor(currentTime) % 60
			local Milliseconds = math.floor((currentTime % 1) *  1000)
			print(currentTime)

			Stopwatch.Text = string.format("%0.2i:%.02i.%.03i", Minutes, Seconds, Milliseconds)

			EndTime.Touched:Connect(function()	
				local endTime = currentTime - startTime
				
				if not Debounce then
					Debounce = true
					BrokenLoop = true
					print(endTime)
					Stopwatch.TextColor3 = Color3.fromRGB(45, 255, 34)
					TickToServer:FireServer(endTime)
					wait(1)
					Debounce = false
				end
			end)
		end
	end
end)

What might help you here is actually instead of looking for if A part got touched by a humanoidrootpart, look for if the humanoidrootpart was touched by a part, so this method of reversing what you did might work better. In my case it did for my racing timers Which work a little bit like this

rootPt.Touched:connect(function(obj)
	if obj.Parent.Name == "DragTimer" and char.Humanoid.Sit then
		
		if obj.Name == "start" then
			startHits = startHits + 1
			
			if resetTime() == false then
				timeTable.t1 = os.clock()
			end
		end
		
		if string.find(obj.Name, "S") then
			timeTable.t2 = os.clock()
			if timeTable.t1 ~= "" then
				local timeN = getSplit(obj.Name)
				if timeN then
					mainFolder.timeRelay:FireServer(obj.Name, timeN)
				end
			end
		end
     end
end)

I have provided you with this small snippet, but hopefully it helps with your timing system, reply to this if you continue to find issues.

Forgive me if the code is a bit messy, the timing system was one of my first large undertakings when I began to learn and become more proficient with coding.

To add to this I have made a simpler version of what I sent previously and commented it out for ease of understanding, since not everyone learns from just one little slice of code from a different project. I have reformatted it in simpler terms whilst preserving some of the variable names in the original piece of code you provided.

local player = game.Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local HumanoidRootPart = char:WaitForChild("HumanoidRootPart")

local timeData = {
	startVal = nil,
	endVal = nil
}

local function calcSplit()
	if timeData.startVal and timeData.endVal then 
		return timeData.endVal - timeData.startVal
	end
end

HumanoidRootPart.Touched:Connect(function(object)
	if object ~= nil then
		if object == StartTime then -- this will get triggered when the part touching the humanoidrootpart is the StartTime part 
			timeData.startVal = os.clock()
		elseif object == EndTime then -- this will get triggered when the part touching the humanoidrootpart is the EndTime part
			timeData.endVal = os.clock()
			
			local splitTime = calcSplit() or 0 -- the split time is the amount of time that has passed since the start part was touched 
			
			-- after this you can just do your FE related stuff, sending the value to the server etc
		end
	end
end)

I was able to solve this myself. Thanks for the suggestions people!