title | description |
---|---|
Remote Events and Callbacks |
Remote network events and callbacks allow for back-and-forth communication across the client-server boundary. |
Roblox experiences are multiplayer by default, so all experiences inherently communicate between the server and the players' connected clients. In the simplest case, as players move their characters, certain Class.Humanoid
properties, such as states, are communicated to the server, which passes this information to other connected clients.
Remote events and callbacks let you communicate across the client-server boundary:
Class.RemoteEvent|RemoteEvents
enable one-way communication (sending a request and not yielding for a response).Class.UnreliableRemoteEvent|UnreliableRemoteEvents
enable one-way communication for data that changes continuously or isn't critical to game state. These events trade ordering and reliability for improved network performance.Class.RemoteFunction|RemoteFunctions
enable two-way communication (sending a request and yielding until a response is received from the recipient).
Unlike Bindable Events, which have more limited utility, the use cases for remote events and functions are too numerous to list:
- Gameplay - Basic gameplay, such as a player reaching the end of a level, can require a remote event. A client script notifies the server, and server scripts reset the player's position.
- Server verification - If a player tries to drink a potion, do they actually have that potion? To ensure fairness, the server has to be the source of truth for an experience. A client script can use a remote event to notify the server that the player is drinking a potion, and then server scripts can decide whether the player actually has that potion and whether to confer any benefits.
- User interface updates - As the game state changes, server scripts can use remote events to notify clients of changes to scores, objectives, etc.
- In-experience Marketplace purchases - For an example implementation that uses remote functions, see Prompting Subscription Purchases.
The following tables serve as a quick reference for how to use Class.RemoteEvent|RemoteEvents
and Class.RemoteFunction|RemoteFunctions
to communicate between the client and server.
[Client → Server](#client-server) | |
Client | `RemoteEvent:FireServer(args)` |
Server | `RemoteEvent.OnServerEvent:Connect(function(player, args))` |
[Server → Client](#server-client) | |
Server | `RemoteEvent:FireClient(player, args)` |
Client | `RemoteEvent.OnClientEvent:Connect(function(args))` |
[Server → All Clients](#server-all-clients) | |
Server | `RemoteEvent:FireAllClients(args)` |
Client | `RemoteEvent.OnClientEvent:Connect(function(args))` |
[Client → Server → Client](#client-server-client) | |
Client | `serverResponse = RemoteFunction:InvokeServer(args)` |
Server | `RemoteFunction.OnServerInvoke = function(player, args)` |
[Server → Client → Server](#server-client-server) | |
(serious risks as outlined [here](#server-client-server)) |
A Class.RemoteEvent
object facilitates asynchronous, one-way communication across the client-server boundary without yielding for a response.
To create a new Class.RemoteEvent
via the Explorer window in Studio:
- Hover over the container into which you want to insert the
Class.RemoteEvent
. In order to ensure both server and client access, it must be in a place where both sides can see it, such asClass.ReplicatedStorage
, although in some cases it's appropriate to store it inClass.Workspace
or inside aClass.Tool
. - Click the ⊕ button that appears to the right of the container's name and insert a RemoteEvent instance.
- Rename the instance to describe its purpose.
Once you've created a Class.RemoteEvent
, it can facilitate one-way communication from client to server, from server to client, or from the server to all clients.
You can use a Class.LocalScript
to trigger an event on the server by calling the Class.RemoteEvent:FireServer()|FireServer()
method on a Class.RemoteEvent
. If you pass arguments to Class.RemoteEvent:FireServer()|FireServer()
, they pass to the event handler on the server with certain limitations. Note that the first parameter of the event handler on the server is always the Class.Player
object of the client that calls it, and additional parameters follow.
Client | `RemoteEvent:FireServer(args)` |
Server | `RemoteEvent.OnServerEvent:Connect(function(player, args))` |
The following Class.Script
connects an event handler to Class.RemoteEvent.OnServerEvent|OnServerEvent
that creates a new Class.Part
on the server. The accompanying Class.LocalScript
then calls Class.RemoteEvent:FireServer()|FireServer()
on the Class.RemoteEvent
instance with the desired Class.BasePart.Color|Color
and Class.BasePart.Position|Position
for the part.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onCreatePart(player, partColor, partPosition)
print(player.Name .. " fired the RemoteEvent")
local newPart = Instance.new("Part")
newPart.Color = partColor
newPart.Position = partPosition
newPart.Parent = workspace
end
-- Connect function to event
remoteEvent.OnServerEvent:Connect(onCreatePart)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- Fire the remote event and pass additional arguments
remoteEvent:FireServer(Color3.fromRGB(255, 0, 0), Vector3.new(0, 25, -20))
You can use a Class.Script
to trigger an event on a client by calling the Class.RemoteEvent:FireClient()|FireClient()
method on a Class.RemoteEvent
. The first argument for Class.RemoteEvent:FireClient()|FireClient()
is the Class.Player
object of the client that you want to respond to the event, and additional arguments pass to the client with certain limitations. Note that the event handler doesn't need to include the Class.Player
object as its first argument because you can determine the player on the client with Class.Players.LocalPlayer
.
Server | `RemoteEvent:FireClient(player, args)` |
Client | `RemoteEvent.OnClientEvent:Connect(function(args))` |
The following Class.LocalScript
connects an event handler to the Class.RemoteEvent.OnClientEvent|OnClientEvent
event. The accompanying Class.Script
then listens for incoming players to the server and calls Class.RemoteEvent:FireClient()|FireClient()
for each with arbitrary data.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local player = Players.LocalPlayer
local function onNotifyPlayer(maxPlayers, respawnTime)
print("[Client] Event received by player", player.Name)
print(maxPlayers, respawnTime)
end
-- Connect function to event
remoteEvent.OnClientEvent:Connect(onNotifyPlayer)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- Listen for incoming players and dispatch remote event to each
local function onPlayerAdded(player)
print("[Server] Firing event to player", player.Name)
remoteEvent:FireClient(player, Players.MaxPlayers, Players.RespawnTime)
end
Players.PlayerAdded:Connect(onPlayerAdded)
You can use a Class.Script
to trigger an event on all clients by calling the Class.RemoteEvent:FireAllClients()|FireAllClients()
method on a Class.RemoteEvent
. Unlike Class.RemoteEvent:FireClient()|FireClient()
, the Class.RemoteEvent:FireAllClients()|FireAllClients()
method doesn't require a Class.Player
object because it fires the Class.RemoteEvent
to all clients.
Server | `RemoteEvent:FireAllClients(args)` |
Client | `RemoteEvent.OnClientEvent:Connect(function(args))` |
The following Class.LocalScript
connects an event handler to the Class.RemoteEvent.OnClientEvent|OnClientEvent
event which outputs a remaining countdown time. The accompanying Class.Script
then calls Class.RemoteEvent:FireAllClients()|FireAllClients()
in a loop every second to fire the Class.RemoteEvent
for all clients.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onTimerUpdate(seconds)
print(seconds)
end
-- Connect function to event
remoteEvent.OnClientEvent:Connect(onTimerUpdate)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local countdown = 5
-- Fire the RemoteEvent every second until time expires
for timeRemaining = -1, countdown do
remoteEvent:FireAllClients(countdown - timeRemaining)
task.wait(1)
end
A Class.RemoteFunction
object facilitates synchronous, two-way communication across the client-server boundary. The sender of a remote function will yield until it receives a response from the recipient.
To create a new Class.RemoteFunction
via the Explorer window in Studio:
- Hover over the container into which you want to insert the
Class.RemoteFunction
. In order to ensure both server and client access, it must be in a place where both sides can see it, such asClass.ReplicatedStorage
, although in some cases it's appropriate to store it inClass.Workspace
or inside aClass.Tool
. - Click the ⊕ button that appears to the right of the container's name and insert a RemoteFunction instance.
- Rename the instance to describe its purpose.
Once you've created a Class.RemoteFunction
, it can facilitate two-way communication between client and server or between server and client.
You can use a Class.LocalScript
to call a function on the server by calling the Class.RemoteFunction:InvokeServer()|InvokeServer()
method on a Class.RemoteFunction
. Unlike a remote event, the Class.LocalScript
that invokes the Class.RemoteFunction
yields until the callback returns. Arguments that you pass to Class.RemoteFunction:InvokeServer()|InvokeServer()
pass to the Class.RemoteFunction.OnServerInvoke|OnServerInvoke
callback of the Class.RemoteFunction
with certain limitations. Note that if you define multiple callbacks to the same Class.RemoteFunction
, only the last definition executes.
Client | `RemoteFunction:InvokeServer(args)` |
Server | `RemoteFunction.OnServerInvoke = function(player, args)` |
The following Class.Script
defines the callback function via Class.RemoteFunction.OnServerInvoke|OnServerInvoke
and returns the requested Class.Part
through its return
value. The accompanying Class.LocalScript
then calls Class.RemoteFunction:InvokeServer()|InvokeServer()
with extra arguments defining the requested part color and position.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get reference to remote function instance
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
-- Callback function
local function createPart(player, partColor, partPosition)
print(player.Name .. " requested a new part")
local newPart = Instance.new("Part")
newPart.Color = partColor
newPart.Position = partPosition
newPart.Parent = workspace
return newPart
end
-- Set function as remote function's callback
remoteFunction.OnServerInvoke = createPart
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get reference to remote function instance
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
-- Pass a color and position when invoking the callback
local newPart = remoteFunction:InvokeServer(Color3.fromRGB(255, 0, 0), Vector3.new(0, 25, -20))
-- Output the returned part reference
print("The server created the requested part:", newPart)
You can use a Class.Script
to call a function on the client by calling the Class.RemoteFunction:InvokeClient()|InvokeClient()
method on a Class.RemoteFunction
, but it has serious risks as follows:
- If the client throws an error, the server throws the error too.
- If the client disconnects while it's being invoked,
Class.RemoteFunction:InvokeClient()|InvokeClient()
throws an error. - If the client doesn't return a value, the server yields forever.
For actions that don't require two-way communications, such as updating a GUI, use a Class.RemoteEvent
and communicate from server to client.
When you fire a Class.RemoteEvent
or invoke a Class.RemoteFunction
, it forwards any arguments that you pass with the event or to the callback function. Any type of Roblox object such as an Datatype.Enum
, Class.Instance
, or others can be passed, as well as Luau types such as numbers, strings, and booleans, although you should carefully explore the following limitations.
If any indices of a passed table are non-string types such as an Class.Instance
, userdata, or function, Roblox automatically converts those indices to strings.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onEventFire(passedTable)
for k, v in passedTable do
print(typeof(k)) --> string
end
end
-- Connect function to event
remoteEvent.OnClientEvent:Connect(onEventFire)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- Listen for incoming players and dispatch remote event to each
local function onPlayerAdded(player)
remoteEvent:FireClient(player,
{
[workspace.Baseplate] = true
}
)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Functions included as arguments for a Class.RemoteEvent
or Class.RemoteFunction
will not be replicated across the client-server boundary, making it impossible to pass functions remotely. Instead, the resulting argument on the receiving side will be nil
.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onClientEvent(func)
print(func) --> nil
end
remoteEvent.OnClientEvent:Connect(onClientEvent)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function testFunction()
print("Hello world!")
end
-- Fire remote event with function as an argument
remoteEvent:FireAllClients(testFunction)
If you pass a table of data, do not pass a mixed table of numeric and string keys. Instead, pass a table that consists entirely of key-value pairs (dictionary) or entirely of numeric indices.
Whether passing a dictionary table **or** a numerically indexed table, avoid `nil` values for any index.local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onEventFire(player, passedTable)
for k, v in passedTable do
print(k .. " = " .. v)
--> 1 = Sword
--> 2 = Bow
--> CharName = Diva Dragonslayer
--> CharClass = Rogue
end
end
-- Connect function to event
remoteEvent.OnServerEvent:Connect(onEventFire)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- Numerically indexed table
local inventoryData = {
"Sword", "Bow"
}
-- Dictionary table
local characterData = {
CharName = "Diva Dragonslayer",
CharClass = "Rogue"
}
remoteEvent:FireServer(inventoryData)
remoteEvent:FireServer(characterData)
Tables passed as arguments to remote events/callbacks are copied, meaning they will not be exactly equivalent to those provided when firing the event or invoking the callback. Nor will tables returned to the invoker be exactly equivalent to those provided. You can demonstrate this by running the following script on a Class.RemoteFunction
and observing how the table identities differ.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
-- Callback function
local function returnTable(player, passedTable)
-- Output table identity on invocation
print(tostring(passedTable)) --> table: 0x48eb7aead27563d9
return passedTable
end
-- Set function as remote function's callback
remoteFunction.OnServerInvoke = returnTable
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
local inventoryData = {
"Sword", "Bow"
}
-- Output original table identity
print(tostring(inventoryData)) --> table: 0x059bcdbb2b576549
local invokeReturn = remoteFunction:InvokeServer(inventoryData)
-- Output table identity upon return
print(tostring(invokeReturn)) --> table: 0x9fcae7919563a0e9
If a table has a metatable, all of the metatable information is lost in the transfer. In the following code sample, the NumWheels
property is part of the Car
metatable. When the server receives the following table, the truck
table has the Name
property but not the NumWheels
property.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onEvent(player, param)
print(param) --> {["Name"] = "MyTruck"}
end
-- Connect function to event
remoteEvent.OnServerEvent:Connect(onEvent)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local Car = {}
Car.NumWheels = 4
Car.__index = Car
local truck = {}
truck.Name = "MyTruck"
setmetatable(truck, Car)
-- Fire event with table including a metatable
remoteEvent:FireServer(truck)
If a Class.RemoteEvent
or Class.RemoteFunction
passes a value that's only visible to the sender, Roblox doesn't replicate it across the client-server boundary and passes nil
instead of the value. For example, if a Class.Script
passes a descendant of Class.ServerStorage
, the client listening to the event will receive a nil
value because that object isn't replicable for the client.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local Players = game:GetService("Players")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- Will be received as "nil" because client can't access ServerStorage
local storedPart = Instance.new("Part")
storedPart.Parent = ServerStorage
local function onPlayerAdded(player)
remoteEvent:FireClient(player, storedPart)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Similarly, if you create a part in a Class.LocalScript
and try to pass it to a Class.Script
, the server will see nil
because the part isn't replicable for the server.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- Will be received as "nil" because the server doesn't know about this part
local clientPart = Instance.new("Part")
clientPart.Parent = workspace
remoteEvent:FireServer(clientPart)