Module 10 · Client and server
Mini-project: Secure coin collection
Validate a pickup on the server and send only approved feedback to the client.
What you will learn
- Keep coin rewards server-owned
- Validate target, state, distance, and cooldown
- Separate client feedback from server mutation
- authoritative reward
- A balance change decided and stored by the server.
- throttling
- Limiting repeated request processing.
Loading saved progress…
Understand
Read the idea and follow a worked example.
Why this matters
Coin collection is the first Coin Quest feature where trusting a client would directly damage shared game state.
The mental model
The client may request a coin by stable ID. The server resolves that ID in its allowed folder, verifies collectible state and distance, applies a per-player cooldown, and owns the balance update.
A separate client controller receives an approved count for visible feedback. It never chooses the reward.
New words and symbols
--!strict asks Studio to check your code for type mistakes. It is an instruction for the editor and produces no Output message.
Matching quotes such as "hello" surround a string: a piece of text. The quotation marks tell Luau where the text starts and ends.
local is read as “local”: it creates a name that is available only in the current code scope.
~= is read as “not equal”: it asks whether two values differ.
if is read as “if”: it starts a decision by evaluating a condition.
then is read as “then”: it marks the body that can run when the condition is truthy.
end is read as “end”: it closes a block such as a decision, loop, or function.
return is read as “return”: it ends the current function call and sends a value to its caller.
A tiny example
--!strictlocal ReplicatedStorage = game:GetService("ReplicatedStorage")local requestCoin = ReplicatedStorage.Remotes.RequestCoinlocal coinFeedback = ReplicatedStorage.Remotes.CoinFeedbackassert(requestCoin:IsA("RemoteEvent") and coinFeedback:IsA("RemoteEvent"), "Coin remotes missing")local coinsByPlayer: { [Player]: number } = {}local lastRequestAt: { [Player]: number } = {}requestCoin.OnServerEvent:Connect(function(player: Player, coinId: unknown) if typeof(coinId) ~= "string" or #coinId > 40 then return end local coin = workspace.Coins:FindFirstChild(coinId) local character = player.Character if not coin or not coin:IsA("BasePart") or not character then return end if coin:GetAttribute("Collectible") ~= true then return end if (character:GetPivot().Position - coin.Position).Magnitude > 12 then return end local now = os.clock() if lastRequestAt[player] and now - lastRequestAt[player] < 0.2 then return end lastRequestAt[player] = now coin:SetAttribute("Collectible", false) coinsByPlayer[player] = (coinsByPlayer[player] or 0) + 1 coinFeedback:FireClient(player, coinsByPlayer[player])end)Line by line
- 3
Shared remotes come from a fixed path.
- 7
Balances are server-owned state.
- 11
The handler receives trusted Player identity and unknown payload.
- 12
Type and size are checked first.
- 13
The server resolves the coin inside its allowed folder.
- 16
Server-owned collectible state is required.
- 17
Server-observed distance bounds the action.
- 20
A short per-player cooldown limits request frequency.
- 22
The coin state changes before the reward.
- 24
Only the approved balance is sent back for display.
Read the code aloud
Read one statement at a time: name the value being read, what the operation does, and where its result goes. Keep identifiers exactly as written so you can find them again in the code.
Pause at calls, conditions, and loops. Explain when the body runs before moving to the next statement; use the line notes above when a symbol is unfamiliar.
Execution trace
Start with the statements outside function bodies. A function definition creates a callable value; its body runs when a call, event, or scheduled task reaches it.
Trace the arguments at each call and the branch or loop inside that call. Return to the caller when the function returns. For an event, use the action named in Try it to trigger the callback.
Line 3: Shared remotes come from a fixed path.
Line 7: Balances are server-owned state.
Line 11: The handler receives trusted Player identity and unknown payload.
Line 12: Type and size are checked first.
Line 13: The server resolves the coin inside its allowed folder.
Line 16: Server-owned collectible state is required.
Line 17: Server-observed distance bounds the action.
Line 20: A short per-player cooldown limits request frequency.
Line 22: The coin state changes before the reward.
Line 24: Only the approved balance is sent back for display.
State before and after
Before running, identify the starting values and any existing Studio objects this example needs. List what you expect to change.
After your practice change, check this result: Only plausible server-validated collection changes the balance, and the client displays approved state.
Reading or printing a value leaves it unchanged. Look for assignments, calls that change state, and property updates to explain each change you observe.
Client feedback controller
--!strictlocal ReplicatedStorage = game:GetService("ReplicatedStorage")local Players = game:GetService("Players")local coinFeedback = ReplicatedStorage.Remotes.CoinFeedbackassert(coinFeedback:IsA("RemoteEvent"), "CoinFeedback missing")local label = Players.LocalPlayer.PlayerGui.CoinHud.CoinCountassert(label:IsA("TextLabel"), "CoinCount must be a TextLabel")coinFeedback.OnClientEvent:Connect(function(coinCount: number) label.Text = `Coins: {coinCount}`end)Predict before running
What happens when the client requests a coin 50 studs away?
Try it
Mark each step after you have tried it.
Common mistake
Do not accept an amount from the client or award before disabling the coin. The server determines the increment and marks collection before sending feedback.
Checkpoint
Submit once you have considered every question. Retries are unlimited.
Recap
- Resolve and validate every client selector on the server.
- Apply authoritative state before feedback.
- Client code presents approved results rather than deciding them.
Next: Module 11 turns approved state into a responsive, all-device HUD and shop interface.
Studio challenge
Secure coin collection
Checks are formative feedback, not cheat-proof certification. You can finish this lesson without the plugin.
- Target
- ServerScriptService/LearningLuau/CoinService
- Check mode
- source structure
Challenge state: idle
Studio Companion
Not connected
Connect once. Compatible challenges will prepare automatically as you reach them.