I made a simple timer on studio for my game and it is supposed to decrease by one every second but when more clients are added it decreases by more. For example: for 2 clients it counts down 2 every second. For 3 clients 3 every second and so on. Here is the code:
local function intermission() do local iTime = game.Workspace.Countdowns.intermission script.Parent.CounterGui.CounterFrame.Counter.Text = "0:" .. iTime.Value while wait(1) and iTime.Value > 0 do if(iTime.Value <= 10) then script.Parent.CounterGui.CounterFrame.Counter.TextColor3 = Color3.fromRGB(255,0,0) game.Workspace.ServerSounds.tick:Play() end iTime.Value = iTime.Value - 1 if(iTime.Value >= 10) then script.Parent.CounterGui.CounterFrame.Counter.Text = "0:" .. iTime.Value else script.Parent.CounterGui.CounterFrame.Counter.Text = "0:0" .. iTime.Value end end end end intermission()
Introduction and Understanding
This is due to Filtering Enabled. Each Client runs this code locally—individually on their local machine. However, they all make requests to manipulate a ValueObject
from the Server; the more Clients making subtraction requests, the further it's decrements increase.
To solve this issue, you must perform the arithmetic within a ServerScript. These programs do not run on the local machine but instead on ROBLOX's linked Server. The Server has replication priority, therefore any action it performs will be shown to all Clients within a singular operation.
We still need to somehow have the Client update it's UI in correspondence to the timer, so how do we link both the ServerScript and LocalScript together? This can be done using a specially designed Object called a RemoteEvent, that will allow us securely build a trusted communication between both ends.
Using it's method :FireAllClients()
, and the RBXScriptSignal .OnClientEvent
, we can tell it to ask the Client's to update their PlayerGui
with the information passed.
This Instance need's to be available for both the Server and the Client connected, so we need to create this Object under ReplicatedStorage
, a service that will transfer any information stored to both ends.
Getting to making our new ServerScript
It's of best practice to store Instance under their affiliated containers, thus, we should store a newly create Script within ServerScriptService
. Let's get into the code:
local ReplicatedStorage = game:GetService("ReplicatedStorage") local RemoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent") local CountdownTime = --// Intermission length
We start off with allocating all the necessary information into variables, notice how I use workspace
instead of game.Workspace
? There is a special keyword built for this container, it will allow us immediate reference to it. On another note, I use the :WaitForChild()
method, this will tell the code to find the Object, and yield (wait) for it if it doesn't exist, this ensures no nil
declarations in our variables.
You may realize that Intermission
is no longer declared, that's because it is not needed anymore. With the new code we'll be creating, the ValueObject
will have no use anymore.
Let's get to re-creating the countdown:
for TimeLeft = CountdownTime,1,-1 do local String = ("Intermission: %0x2d:%0x2d"):format(TimeLeft/60, TimeLeft%60) RemoteEvent:FireAllClients(String) wait(1) end
StackEdit is recognizing the special format syntax provided in the string concatenation above, this is a small issue that I'm unable to avoid. Please note that %0x2:%0x2
is not the authentic format syntax, please take the snippet from this pastebin that contains the real code for the String
variable.
Whoa! Things got a little complicated there! It's time I explain! It's of best practice to use something called a for
loop for numerical iteration. This loop will allow us to traverse over a set of instructions through a specified amount of time. This time is fed into a variable called TimeLeft
, which is formatted to count down to 1
from (n) Time
by 1
number. We then use a complex method of the String
class called string.format(), which allows us to use special characters, to identify how we wish to push the information we supply into. We ask that it creates two 0
-digit placeholders conjoined via a colon. This translates to 00:00
. By then dividing the current number we're on by 60
, we can get the minutes left, and by using the modulo %
operator, we can get the remainder of the identical division, which returns the seconds left. The :format()
method will push these calculations into 00:00
respectfully, giving us a proper digital-time effect.
Here is our finished ServerScript:
local ReplicatedStorage = game:GetService("ReplicatedStorage") local RemoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent") local CountdownTime = --// Intermission length for TimeLeft = CountdownTime,1,-1 do local String = ("Intermission: %0x2d:%0x2d"):format(TimeLeft/60, TimeLeft%60) RemoteEvent:FireAllClients(String) wait(1) end --// You can optionally put the for loop back into a function.
We're not done unfortunately, we still need to re-program our LocalScript. This shouldn't be too hard however, as all we need to do is declare the GUIObject
that we wish to display the time for, and the same information for indexing the RemoteEvent
.
local ReplicatedStorage = game:GetService("ReplicatedStorage") local RemoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent") local Player = game:GetService("Players").LocalPlayer local PlayerGui = Player:WaitForChild("PlayerGui") local CounterGui = PlayerGui:WaitForChild("CounterGui") local CounterFrame = CounterGui:WaitForChild("CounterFrame") local Counter = CounterFrame:WaitForChild("Counter") RemoteEvent.OnClientEvent:Connect(function(Time) Counter.Text = Time end)
If this helps, don’t forget to accept and upvote this answer! If you have any questions, feel free to comment below!