Quenty’s module is most likely a solution, but since the thread doesn’t have a marked solution here’s what I do:
When a timer needs to start, I fire all clients telling them how much time is left. From there, I let the client do the countdown itself. However, to try and get it closer to the server’s timer, every two seconds or so I would send another signal to each client telling them, again, how much time is left. Once the client receives the signal, compare how much time the client thinks is left to how much time the server told it was left. If the client thinks there’s less time left than the server just said, then don’t listen to the server and stick with what the client has. If the server says there’s less time left than the client thought before getting the signal, jump down to how much time the server said.
By doing what I said above, the timer on the client will stick as close to the correct time as possible. Some signals will come faster than others, which is why you always stick with the lower amount of time left.
When I call my client with the new stage countdown time I do something like this.
local Diff = SlaveClock:GetTime()-Args.MasterClockTime
CountdownTime = Args.CountdownTime-Diff
I use diff to compensate for the time it took the client to receive the event. However, at times, the client gets negative or very extreme values for “Diff”. Any idea why and how to fix it?
The clock system already accounts for ping time. Just use :GetTime() to get the accounted for time. If you want to print out ping for some reason you can write out _pneWayDelay.
Time is shared between the client and the server. So :GetTime() returns basically the same value between both.
So you just send the client the start time of your clock, and it’s length. Here’s some sample code.
You cannot just copy this code in, but it should give you some indication of how to program it.
--- Shows time countdown using TimeSyncManager
-- @classmod TimerTextLabel
-- @author Quenty
local require = require(game:GetService("ReplicatedStorage"):WaitForChild("Nevermore"))
local BasicPane = require("BasicPane")
local TimeSyncManager = require("TimeSyncManager")
local TimerTextLabel = setmetatable({}, BasicPane)
TimerTextLabel.__index = TimerTextLabel
TimerTextLabel.ClassName = "TimerTextLabel"
function TimerTextLabel.new(textLabel)
local self = setmetatable(BasicPane.new(textLabel), TimerTextLabel)
self._length = 0
self._startOffset = 0
self.Gui.Visible = false
self._maid:GiveTask(self.VisibleChanged:Connect(function(isVisible, doNotAnimate, visibleMaid)
self.Gui.Visible = isVisible
if isVisible then
-- Setup animation
local alive = true
spawn(function()
while alive do
self:_update()
wait(0.1)
end
end)
visibleMaid:GiveTask(function()
alive = false
end)
end
end))
self:_update()
return self
end
function TimerTextLabel:WithStartOffset(startOffset)
self._startOffset = startOffset or error("No startOffset")
return self
end
function TimerTextLabel:WithLength(length)
self._length = length or error("No length")
self:_update()
return self
end
function TimerTextLabel:WithStartTimeValue(startTimeValue)
self._startTimeValue = startTimeValue or error("No startTimeValue")
self._maid:GiveTask(self._startTimeValue.Changed:Connect(function()
self:_update()
end))
self:_update()
return self
end
function TimerTextLabel:_formatTime(time)
if time <= 0 then
return "0:00"
elseif time > 60*60 then
return string.format("%.2d:%.2d:%.2d", time/(60*60), time/60%60, time%60)
else
return string.format("%.1d:%.2d", time/60%60, time%60)
end
end
function TimerTextLabel:_update()
if not self._startTimeValue then
self.Gui.Text = "0:00"
return
end
local timePassed = TimeSyncManager:GetTime() - (self._startTimeValue.Value + self._startOffset)
local timeLeft = self._length - timePassed
self.Gui.Text = self:_formatTime(timeLeft)
end
return TimerTextLabel
So how do I fix the case of players first joining and they need the countdown updated instantly for them when the clock is not sync when the player first joins?
I notice that sometimes my friend’s clock is off sync at around 5 seconds when he joins.
The clock automatically synchronizes itself, as stated above. You can just wait. If your clock is structured like mine above, it’ll auto-correct. This is nice, since it accounts for ping, et cetera.
Honestly, it’s a lot easier to just use a NumberValue and update it every 0.1 seconds on the server, if this is too confusing.
So when my stage changed and I had a client Changed event, it didn’t pick up the change, since changing certain changes (loading in heavy maps n stuff) dropped the connection
FYI there is no such thing as a perfectly synced time across all devices using a certain service, anywhere in the world. You can fetch time and adjust it based on round-trip time, but that’s about the best you can do.
The only difference between tick() and os.time() is that os.time() is in UTC timezone and tick() is in your local timezone. They can both be completely wrong or inaccurate if your system time isn’t set correctly, and they will definitely not do anything except a timezone correction. Their values don’t attempt to sync with Roblox servers or anything like that – you’ll have to estimate that offset yourself and use it.
Pretty much the only thing you can assume somewhat safely is that os.time() will be similar (within a few seconds threshold) across all Roblox servers (not clients, clients are the wild west because the user owns the time configuration there).