Client and server
A Studz game runs on a server and on every player's client. Server scripts are trusted; clients are not. This page covers what a LocalScript can do, and how the two sides talk through remotes.
LocalScripts
A LocalScript runs on one player's computer. It sees the replicated world, Players.LocalPlayer, workspace.CurrentCamera, input and its own GUI.
local Players = game:Service("Players")
local UIS = game:Service("UserInputService")
local player = Players.LocalPlayer
local label = Instance.new("TextLabel") -- drawn for this player only
label.Text = "Hello, " .. player.Name
label.Position = UDim2.new(0.5, 0, 0.1, 0)
UIS.InputBegan:On(function(input, gameProcessed)
if gameProcessed then return end -- typing in chat, menu open
if input.KeyCode == Enum.KeyCode.E then
print("E pressed")
end
end)
LocalScripts only start with the place. Creating one with Instance.new at runtime does nothing.
What a client can and cannot do
| Can | Cannot |
|---|---|
| Read the world and change it locally (colour, transparency, position). | Change the character's speed or health. The server decides. |
Create its own GUI, and its own parts (Instance.new("Part", workspace)), which only this player sees. | Use Touched or ClickDetector events. |
| Read input and the camera. | Affect other players. Local changes never leave the machine. |
A part changed locally is overwritten when the server next changes it.
Input
UserInputService (game:Service("UserInputService")):
| Member | Description |
|---|---|
InputBegan, InputEnded, InputChanged | Signals with (input, gameProcessed). gameProcessed is true if the game interface already used the input. |
:IsKeyDown(Enum.KeyCode.X) | Whether a key is held. |
:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) | Whether a mouse button is held. |
:GetMouseLocation() | Cursor position in pixels. |
:GetKeysPressed() | Every key currently held. |
Enums for input: Enum.KeyCode, Enum.UserInputType, Enum.UserInputState and Enum.NormalId.
The mouse
player:GetMouse() returns a mouse object.
| Member | Description |
|---|---|
.X, .Y | Cursor position. |
.Hit | World position under the cursor. |
.Target | The part under the cursor. |
.UnitRay | A ray from the camera through the cursor. |
.Button1Down, .Button1Up, .Button2Down, .Button2Up | Click signals. |
.Move, .WheelForward, .WheelBackward | Movement and scroll signals. |
The camera
workspace.CurrentCamera is read-only for now.
| Member | Description |
|---|---|
.CFrame, .Position, .Focus, .LookVector | Where it is and what it looks at. |
.FieldOfView, .ViewportSize | Lens and window size. |
:ScreenPointToRay(x, y) / :ViewportPointToRay(x, y) | A ray through a screen point. |
:WorldToViewportPoint(pos) / :WorldToScreenPoint(pos) | Where a world position lands on screen. |
Remotes
A remote is an object that carries a message between server and client. Put a RemoteEvent or RemoteFunction in the Workspace or in ReplicatedStorage. Insert it from the Script ribbon or the Explorer in Polycarbonate. Both sides find it by name.
The containers ReplicatedStorage, ServerStorage, ReplicatedFirst, ServerScriptService and StarterPlayer all exist. Objects placed anywhere are also found through them.
RemoteEvent: one-way messages
| Member | Side | Description |
|---|---|---|
remote:ToServer(...) | Client | Sends a message to the server. |
remote.FromClient:On(function(player, ...) end) | Server | Receives it. The first argument is the sender. |
remote:Listen(schema, fn) | Server | Studz addition. FromClient with a type check in front. |
remote:ToPlayer(player, ...) | Server | Sends to one player. |
remote:ToAll(...) | Server | Sends to every player. |
remote.FromServer:On(fn) | Client | Receives messages from the server. |
RemoteFunction: ask and wait for an answer
| Member | Side | Description |
|---|---|---|
remote:CallServer(...) | Client | Sends a request and waits up to 10 seconds for the reply. |
remote.OnServerCall = function(player, ...) end | Server | Your function's return value is the reply. |
BindableEvent
For signalling between scripts on the same side. :Fire(...) sends, .Event:On(fn) receives.
A complete example
-- Server Script
local remote = game.ReplicatedStorage:GetChild("Buy")
remote:Listen({ "string", "integer" }, function(player, item, count)
remote:ToPlayer(player, "bought", item) -- or :ToAll(...)
end)
game.ReplicatedStorage.GetCoins.OnServerCall = function(player)
return coinsOf[player.Name]
end
-- LocalScript
remote:ToServer("sword", 1)
remote.FromServer:On(function(what, item) print(what, item) end)
local coins = game.ReplicatedStorage.GetCoins:CallServer() -- waits up to 10 s for the reply
What can be sent
Arguments can be nil, booleans, numbers, strings, Vector3, Color3, players, parts, and tables of those. Functions and other objects are refused with an error.
| Limit | Value |
|---|---|
| Table depth | 4 levels |
| Entries | 64 |
| Total size | about 1 KB |
| Client call rate | 60 remote calls per second |
Validating with Listen
Never trust a client. remote:Listen(schema, handler) checks every argument before your function sees it. schema lists the expected types in order:
| Type | Accepts |
|---|---|
"string" | A string up to 1000 characters. |
"number" | A finite number (no NaN or infinity). |
"integer" | A whole number. |
"boolean" | true or false. |
"table" | A table. |
"any" | Anything allowed on a remote. |
A typeof name | "Vector3", "Instance" and so on. |
Add ? for an optional argument: "Vector3?". A call with the wrong type, a NaN, an overlong string or too many arguments is dropped with a warning and never reaches your handler.
Listen checks types, not meaning. You still have to check that the item exists, the count is sensible and the player may do this:
local prices = { sword = 10, bow = 25 }
buy:Listen({ "string", "integer" }, function(player, item, count)
local price = prices[item]
if not price or count < 1 or count > 10 then return end
-- charge the player, give the item ...
end)
