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

CanCannot
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")):

MemberDescription
InputBegan, InputEnded, InputChangedSignals 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.

MemberDescription
.X, .YCursor position.
.HitWorld position under the cursor.
.TargetThe part under the cursor.
.UnitRayA ray from the camera through the cursor.
.Button1Down, .Button1Up, .Button2Down, .Button2UpClick signals.
.Move, .WheelForward, .WheelBackwardMovement and scroll signals.

The camera

workspace.CurrentCamera is read-only for now.

MemberDescription
.CFrame, .Position, .Focus, .LookVectorWhere it is and what it looks at.
.FieldOfView, .ViewportSizeLens 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

MemberSideDescription
remote:ToServer(...)ClientSends a message to the server.
remote.FromClient:On(function(player, ...) end)ServerReceives it. The first argument is the sender.
remote:Listen(schema, fn)ServerStudz addition. FromClient with a type check in front.
remote:ToPlayer(player, ...)ServerSends to one player.
remote:ToAll(...)ServerSends to every player.
remote.FromServer:On(fn)ClientReceives messages from the server.

RemoteFunction: ask and wait for an answer

MemberSideDescription
remote:CallServer(...)ClientSends a request and waits up to 10 seconds for the reply.
remote.OnServerCall = function(player, ...) endServerYour 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.

LimitValue
Table depth4 levels
Entries64
Total sizeabout 1 KB
Client call rate60 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:

TypeAccepts
"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)