Custom sprite number system

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)
3 Likes

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.

I mean I do the countdown stuff on the server. How could I achieve it cleanly on the client without desync issues??

Desync with timers usually isn’t that important as long as events are controlled on the server. This thread should explain the timer system better → Handling server-wide countdown timers - #7 by buildthomas

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).

Remote Event Security (From this API.)

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.

I can’t seem to get it to work

-- 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

cc @aven_ue

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.

Oh im not using os.time()?

-- 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:

print(os.time(
{Year = 2020, Month = 9, Day = 21, Seconds = 34, Minutes = 27}
)

Wiki Explanations:

Further information can be provided if needed.

Not sure how this would correlate to a count down timer tho?

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.