Recently I got this custom number countdown system to work. Just wanna know if there’s a tidier way of writing it tho? Basically I have the set sprite offsets written down in a module (NumberSprite). But I just wanna know if this is clean and written well.
Also, would it better to use individual images for each number, or is what I’m doing (using a sprite sheet) the best option?
Ignore the lag, I think that’s my internet, not code related
local function Format(int)
return string.format('%02i', int)
end
local function ConvertTime(seconds)
local Seconds = seconds
local Minutes = (Seconds - Seconds%60) / 60
Seconds = Seconds - Minutes * 60
return Format(Minutes), Format(Seconds)
end
local function Update(timer, multiplier)
local Minutes, Seconds = ConvertTime(timer)
if Minutes >= '10' then
TimeBar['1'].ImageRectOffset = NumberSprite[tonumber(string.sub(Minutes, 1, 1))]
TimeBar['2'].ImageRectOffset = NumberSprite[tonumber(string.sub(Minutes, 1, 1))]
else
TimeBar['1'].ImageRectOffset = NumberSprite[0]
TimeBar['2'].ImageRectOffset = NumberSprite[tonumber(Minutes)]
end
if Seconds >= '10' then
TimeBar['3'].ImageRectOffset = NumberSprite[tonumber(string.sub(Seconds, 1, 1))]
TimeBar['4'].ImageRectOffset = NumberSprite[tonumber(string.sub(Seconds, 2))]
else
TimeBar['3'].ImageRectOffset = NumberSprite[0]
TimeBar['4'].ImageRectOffset = NumberSprite[tonumber(Seconds)]
end
-- Set shadows
TimeBar['1'].Shadow.ImageRectOffset = TimeBar['1'].ImageRectOffset
TimeBar['2'].Shadow.ImageRectOffset = TimeBar['2'].ImageRectOffset
TimeBar['3'].Shadow.ImageRectOffset = TimeBar['3'].ImageRectOffset
TimeBar['4'].Shadow.ImageRectOffset = TimeBar['4'].ImageRectOffset
end
Timer.OnClientEvent:Connect(Update)
Aside from the timer itself (Sending Start/Stop timer events using os.time rather than firing an event each time the counter changes), It looks good. Using a sprite sheet is probably better because you have to go through less moderation and preloading troubles.
If you want a tl;dr: Send start and stop messages from the clients to every server, get a starting value for os.time and then compare the new time to the old time until you get the time you need.
It insures network lag doesn’t make the timer change at different paces, and only has one event that allows for error.
Like @PuffoThePufferfish said, you should use os.time() to change the timer rather than relying on incoming server requests. Those requests could drop and or cause mass loads of lag for players with not so good devices. Other than that your code looks good. os | Documentation - Roblox Creator Hub (API if needed).
RemoteEvents and RemoteFunctions are the best option for client-server communication, but they’re not necessarily secure channels. A clever hacker may fake a remote event or change the values that are passed along with it. Because of this, you should use basic server-side validation to confirm that the incoming request is legal.
Exceptions (Also from API)
There are some client-side actions that replicate instantly and don’t require the server’s permission. These are generally linked to things that a player should see right away, while others are for your convenience.
In summary, using Remote Events can be bypassed by hackers and send false information. os.time() on the other hand can be a little off when it comes to checking the time but I don’t think that will affect your time script much. os.clock() could be used instead but is unreliable.
-- Server
print(1)
Timer:FireAllClients(Settings.Timer)
print(2)
-- Client
-- Set the timer
local function Set(timer)
print("Start client timer")
end
Timer.OnClientEvent:Connect(Set)
My output only ever prints the 1 and 2 from server. this is beacause the timer starts before the player has loaded in. This would create further problems, like if a player joined mid game
Not exactly. I’m pretty sure it states somewhere in the api that os.time goes off of the current epoch time. I’m pretty sure os.time goes of the UTC time rather than your local time. os.clock goes of your local time. Using os.time can still be used. I do suggest replacing the FireAllClients as sending information client → server, server → client can be risky when it comes to dropped requests and hackers.
-- Server
-- Fire timer to all clients
Timer:FireAllClients(Settings.Timer)
-- Start countdown timer
for i = Settings.Timer, 0, -1 do
Settings.Timer = i
wait(1 / Settings.Multiplier)
end
-- Timer has complete
TimerComplete:FireAllClients()
-- Client
-- Set the timer
local function Set(timer)
Frame.Visible = true
for i = timer, 0, -1 do
wait(1 / CurrentMultiplier)
end
end
-- Game complete
local function Complete()
-- Reset everything
Frame.Visible = false
CurrentMultiplier = 1
UpdateMultiplier(nil, CurrentMultiplier)
end
-- Event connections
Timer.OnClientEvent:Connect(Set)
TimerComplete.OnClientEvent:Connect(Complete)
os.time is actually a built in roblox function. When used, it will return the current time in minutes or seconds (I forgot) since the epoch of 1970. e.g
print(os.time()) --> 1600748677 time in UTC for me
Your result will however be different because some time has passed.
You can also input a set time if needed by doing something like this:
os.time() is much more efficient when it comes to checking the time rather than relying on checking every second via a number. os.time() will take up less memory every use which will help your game overall.
You are sending too much requests to the client. Specifically one every second. Requests will fill up and some with be dropped. It is also bad practice to send that many requests.