Events | Documentation - Roblox Creator Hub (2024)

Scripting on Roblox is primarily event-driven. The engine supports multiple types of events. When implementing your logic, you can connect functions to built-in events fired by the engine to respond to them. You can also create custom events that you fire and respond to. Additionally, you can use networking events that allow event-driven communication across the client-server boundary.

Built-in Events

Many objects have built-in events provided by their APIs that automaticallyrespond to specific actions or changes related to those objects. For example, aPlayer.Character touching a Part automatically fires aBasePart.Touched event. Most built-in events are synchronous, so you canconnect a function for following custom behaviors in response to a certain built-inevent by scripting.

Connecting Functions to Events

You can connect a function to an event using Connect() to execute code each time the event fires. Most events pass arguments to their connected functions when they fire. For example, the BasePart.Touched event passes the object that touched the Part, and the Players.PlayerAdded event passes the Player that joined your experience.

It's the best practice to name the function with the pattern onEventName to help you find the function in the future. The following code sample demonstrates how to connect a function named onPartTouched to the BasePart.Touched event of a Part in the Workspace.

-- Script in ServerScriptService

local part = workspace.Part

local function onPartTouched(object)

print("Part was touched by", object:GetFullName())

end

part.Touched:Connect(onPartTouched)

You can connect anonymous functions to events when you want to use variables in the parent scope and don't need to use the function elsewhere. The following code sample shows how to connect an anonymous function to the Players.PlayerAdded event.

-- Script in ServerScriptService

local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(player)

print(player.Name, " joined the game!")

end)

Disconnecting Functions from Events

In Luau, the RBXScriptSignal data type represents events. The RBXScriptSignal:Connect() method returns an RBXScriptConnection object. If you connect a function to an event but don't want the function to call in subsequent event firings, disconnect the function from the event by calling the RBXScriptConnection:Disconnect() method.

When Luau destroys an event's object, such as the Player object when a user leaves the experience, all of its connections disconnect automatically. The following code sample shows how to connect and disconnect a function to the Part.Touched event.

-- Script in ServerScriptService

local part = workspace.Part

local targetPart = workspace.TargetPart

-- Declare the variable before defining the function so it can be disconnected from within the function

local connection

local function onPartTouched(otherPart)

if otherPart == targetPart then

print("The part hit the target")

connection:Disconnect()

end

end

connection = part.Touched:Connect(onPartTouched)

Waiting for Events to Fire

If you want a script to yield or pause until a specific event fires, use the RBXScriptSignal.Wait() function.

-- Script in ServerScriptService

local part = workspace.Part

local touchedPart = part.Touched:Wait()

print("The part was touched by", touchedPart:GetFullName())

The RBXScriptSignal.Wait() function returns the event's arguments. You can assign these arguments to variables where RBXScriptSignal.Wait() is called.

-- Script in ServerScriptService

local VirtualInputManager = game:GetService("VirtualInputManager")

local isPressed, keyCode, isRepeatedKey, layerCollector = VirtualInputManager.SendKeyEvent:Wait()

print(isPressed)

print(keyCode)

--..etc

Custom Events

Custom events allow you to bind behaviors between scripts and communicate your specific desired outcome for certain in-experience actions. Custom events can be both asynchronous and synchronous. and they can only communicate scripts within the same side of the client-server model.

Custom Asynchronous Events

BindableEvent allows asynchronous, one-way communication between scripts. You can use it to define a custom event and fire it manually by calling its BindableEvent:Fire() method without yielding for return values. The connected function receives arguments that you pass to Fire.

Creating Custom Asynchronous Events

To create a new BindableEvent in the Explorer:

  1. Hover over the container in the Explorer into which you want to insert a BindableEvent. It's common to put BindableEvents in an Event folder in ServerScriptService to use them with Scripts and ReplicatedStorage to use them with LocalScripts.

  2. Click the button that appears to the right of the container to open the Insert Object menu.

  3. Select BindableEvent.

  4. Rename the event to describe its purpose using PascalCase.

To create a new BindableEvent in a Script, use Instance.new():

-- Script in ServerScriptService

local roundStartedEvent = Instance.new("BindableEvent")

roundStartedEvent.Name = RoundStarted

roundStartedEvent.Parent = ServerScriptService

Using Custom Asynchronous Events

You can connect multiple functions to the same BindableEvent, but Luau executes them in an unpredictable order. To ensure that functions execute in a particular order, combine their bodies or calls into a single function to connect to the event.

To fire a BindableEvent in ServerScriptService named ShareData:

-- Script in ServerScriptService named DataSender

local ServerScriptService = game:GetService("ServerScriptService")

local shareData = ServerScriptService.ShareData

local HELLO_WORLD = "Hello world!"

-- Shares HELLO_WORLD after 2 seconds

task.wait(2)

shareData:Fire(HELLO_WORLD)

To connect to a BindableEvent in ServerScriptService named ShareData:

-- Script in ServerScriptService named DataReceiver

local ServerScriptService = game:GetService("ServerScriptService")

local shareData = ServerScriptService.ShareData

shareData.Event:Connect(function(data)

print(data)

end)

Custom Synchronous Events

BindableFunction objects allow for synchronous, two-way communication between scripts. They contain an OnInvoke callback that you can define in one script and call from other scripts. The callback function runs when you call the Invoke() method on the BindableFunction. The callback function arguments that you pass to Invoke(). The code invoking the function yields until the function halts or returns a value.

Creating Custom Synchronous Events

To create a new BindableFunction in the Explorer:

  1. Hover over the container in the Explorer into which you want to insert a BindableFunction. It's common to put BindableFunction objects in a Functions folder in ServerScriptService to use them with Scripts and ReplicatedStorage to use them with LocalScripts.

  2. Click the button that appears to the right of the container to open the Insert Object menu.

  3. Select BindableFunction.

  4. Rename the function to describe its purpose using PascalCase.

To create a new BindableFunction in a Script:

-- Script in ServerScriptService

local getHelloWorld = Instance.new("BindableFunction")

getHelloWorld.Name = GetHelloWorld

getHelloWorld.Parent = ServerScriptService

Using Custom Synchronous Events

Each BindableFunction has only one OnInvoke callback. If you have multiple definitions, then only the one defined latest runs. If the OnInvoke callback does not have a return statement, it returns nil.

To define the OnInvoke callback of a BindableFunction in ServerScriptService named GetHelloWorld:

-- Script in ServerScriptService named DataManager

local ServerScriptService = game:GetService("ServerScriptService")

local getHelloWorld = ServerScriptService.GetHelloWorld

local HELLO_WORLD = "Hello world!"

getHelloWorld.OnInvoke = function()

return HELLO_WORLD

end

To invoke a BindableFunction in ServerScriptService named GetHelloWorld:

-- Script in ServerScriptService named DataReceiver

local ServerScriptService = game:GetService("ServerScriptService")

local getHelloWorld = ServerScriptService.GetHelloWorld

print(getHelloWorld:Invoke())

print("Hello world again!")

Notice that the "Hello world!" prints before "Hello world again!" because the Script yields until the BindableFunction halts or returns a value.

Networking Events

All experiences require communication between the server and the players' clients. For example when a player presses W to move forward, the keyboard input is received on the player's client. The client then communicates this to the server, which responds by moving the position of the player's character forward. RemoteEvent and RemoteFunction objects allow you to create your own events and functions to communicate your own behavior between the client-server boundary.

RemoteEvents allow for asynchronous, one-way communication across the boundary, while RemoteFunctions allow for synchronous, two-way communication (sending a request across the boundary and yielding until a response is received from the recipient).

The following table serves as a quick reference for how to use remote events and functions to communicate between the client and server.

Remote Events

Client → Server
ClientRemoteEvent:FireServer(args)
ServerRemoteEvent.OnServerEvent:Connect(function(player, args))
Server → Client
ServerRemoteEvent:FireClient(player, args)
ClientRemoteEvent.OnClientEvent:Connect(function(args))
Server → All Clients
ServerRemoteEvent:FireAllClients(args)
ClientRemoteEvent.OnClientEvent:Connect(function(args))

Remote Functions

Client → Server → Client
ClientserverResponse = RemoteFunction:InvokeServer(args)
ServerRemoteFunction.OnServerInvoke = function(player, args)
Server → Client → Server

Serious Risks

Asynchronous Networking Events

A RemoteEvent object allows asynchronous, one-way communication across the client-server boundary. You can use it to send requests across the boundary without yielding for a response.

Creating Asynchronous Networking Events

Create RemoteEvent objects in a location that both the client and server can access, such as ReplicatedStorage.

  1. In the Explorer window, hover over ReplicatedStorage and click the button.

  2. Click RemoteEvent from the contextual menu.

    Events | Documentation - Roblox Creator Hub (1)
  3. Rename the new RemoteEvent to describe its purpose.

    Events | Documentation - Roblox Creator Hub (2)

Using Asynchronous Networking Events

You can use RemoteEvents to facilitate one-way communication from one client to the server, the server to one client, or the server to all clients.

Client to Server

You can use a LocalScript to trigger an event on the server by calling the FireServer() method on a RemoteEvent. If you pass arguments to FireServer(), they pass to the event handler on the server. The first parameter of the event handler is always the Player object of the client that calls it, and additional parameters follow.

ClientRemoteEvent:FireServer(args)
ServerRemoteEvent.OnServerEvent:Connect(function(player, args))

The following LocalScript and Script fire and respond to a RemoteEvent instance in ReplicatedStorage named RemoteEventTest. The LocalScript calls FireServer() on the RemoteEvent instance. In response, the Script connects an event handler to OnServerEvent that creates a new Part on the server.

LocalScript in StarterPlayerScripts

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")

-- Fire the RemoteEvent and pass additional arguments

remoteEvent:FireServer(BrickColor.Red(), Vector3.new(0, 25, 0))

Script in ServerScriptService

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")

local function onCreatePart(player, partColor, partPosition)

print(player.Name .. " fired the RemoteEvent")

local newPart = Instance.new("Part")

newPart.BrickColor = partColor

newPart.Position = partPosition

newPart.Parent = workspace

end

remoteEvent.OnServerEvent:Connect(onCreatePart)

Notice how the LocalScript fires the event with two parameters, but the Script's event handler has three parameters. This is because the first parameter is always the Player object of the client that calls it.

Server to Client

You can use a Script to trigger an event on a client by calling the FireClient() method on a RemoteEvent. The first argument of the FireClient() is the Player object of the client that you want to respond to the event, and additional arguments pass to the client.

ServerRemoteEvent:FireClient(player, args)
ClientRemoteEvent.OnClientEvent:Connect(function(args))

The following Script and LocalScript fire and respond to a RemoteEvent instance in ReplicatedStorage named RemoteEventTest. The Script calls FireClient() on the RemoteEvent to fire it. The LocalScript connects an event handler to the OnClientEvent event. The event handler doesn't need to list the Player object as its first argument because you can determine the player on the client with Players.LocalPlayer.

Script in ServerScriptService

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Players = game:GetService("Players")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")

local function onPlayerAdded(player)

print("[Server] Firing event to player", player.Name)

remoteEvent:FireClient(player, Players.MaxPlayers, Players.RespawnTime)

end

Players.PlayerAdded:Connect(onPlayerAdded)

LocalScript in StarterPlayerScripts

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Players = game:GetService("Players")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")

local player = Players.LocalPlayer

local function onNotifyPlayer(maxPlayers, respawnTime)

print("[Client] Event received by player", player.Name)

print(maxPlayers, respawnTime)

end

remoteEvent.OnClientEvent:Connect(onNotifyPlayer)

Server to All Clients

You can use a Script to trigger an event on all clients by calling the FireAllClients() method on a RemoteEvent. Unlike FireClient(), the FireAllClients() method doesn't require a Player object because it fires the RemoteEvent to all clients.

ServerRemoteEvent:FireAllClients(args)
ClientRemoteEvent.OnClientEvent:Connect(function(args))

The following Script and LocalScript fire and respond to a RemoteEvent instance in ReplicatedStorage named RemoteEventTest. The Script calls FireAllClients() on the RemoteEvent to fire it. The LocalScript connects an event handler to the OnClientEvent event that prints to the Output window.

Script in ServerScriptService

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")

local secondsRemaining = 5

-- Fire the RemoteEvent every second until time expires

for t = secondsRemaining, 1, -1 do

remoteEvent:FireAllClients(t)

task.wait(1)

end

LocalScript in StarterPlayerScripts

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")

local function onTimerUpdate(seconds)

print(seconds)

end

-- Call "onTimerUpdate()" when the server fires the RemoteEvent

remoteEvent.OnClientEvent:Connect(onTimerUpdate)

Client to Client

Clients cannot communicate directly with other clients, although you can effectively dispatch an event from one client to another by using the FireServer() method, then calling FireClient() or FireAllClients() in the event handler for OnServerEvent.

Synchronous Networking Events

RemoteFunction allows synchronous, two-way communication across the client-server boundary. The sender of a remote function will wait to receive a response from the recipient.

Creating Synchronous Networking Events

Create RemoteFunction objects in a location that both the client and server can access, such as ReplicatedStorage.

  1. In the Explorer window, hover over ReplicatedStorage and click the button.

  2. Click RemoteFunction from the contextual menu.

    Events | Documentation - Roblox Creator Hub (3)
  3. Rename the new RemoteFunction to describe its purpose.

    Events | Documentation - Roblox Creator Hub (4)

Using Synchronous Networking Events

You can use RemoteFunction objects to facilitate two-way communicationbetween one client and the server or the serverand one client.

Client to Server to Client

You can use a Script to call a function on the server by calling the InvokeServer() method on a RemoteFunction. The LocalScript that invokes the RemoteFunction yields until the callback of the RemoteFunction returns. Arguments that you pass to InvokeServer() pass to the OnServerInvoke callback of the RemoteFunction. If you define multiple callbacks to the same RemoteFunction, the last definition executes.

ClientRemoteFunction:InvokeServer(args)
ServerRemoteFunction.OnServerInvoke = function(player, args)

The following LocalScript and Script have two-way communication using a RemoteFunction instance in ReplicatedStorage named RemoteFunctionTest. The LocalScript calls InvokeServer() on the RemoteFunction instance with extra arguments. In response, the Script defines the callback of the RemoteFunction and calls it using the Player of the client and the extra arguments. The Script then returns the return value of the callback to the LocalScript.

LocalScript in StarterPlayerScripts

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")

-- Invoke the Function

-- Pass a brick color and position when invoking the function

local newPart = remoteFunction:InvokeServer(BrickColor.Red(), Vector3.new(0, 25, 0))

print("The server created the requested part:", newPart)

Script in ServerScriptService

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")

-- Script in ServerScriptService to create a Part with the passed properties

local function createPart(player, partColor, partPosition)

print(player.Name .. " requested a new part")

local newPart = Instance.new("Part")

-- Use partColor and partPosition to set the part's BrickColor and Position

newPart.BrickColor = partColor

newPart.Position = partPosition

newPart.Parent = workspace

return newPart

end

-- Bind createPart() to the remote function's OnServerInvoke callback

remoteFunction.OnServerInvoke = createPart

Server to Client to Server

You can use a Script to call a function on the client by calling the InvokeClient() method on a RemoteFunction, but it has the following serious risks:

If the client throws an error, the server throws the error too.

If the client disconnects while it's being invoked, then 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 RemoteEvent and communicate from the server to client.

Argument Limitations

When an event is fired or invoked, it forwards any arguments that you pass with it or to the callback function. For built-in events, follow the argument limitations on the API reference. For custom events and networking events, though the arguments can be all types of Roblox objects and Luau date types such as numbers, strings, and booleans, there are certain limitations that apply to the arguments.

Non-String Indices

If any indices of a passed table are non-string types such as an Instance, userdata, or function, Roblox automatically converts those indices to strings.

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local bindableEvent = ReplicatedStorage:WaitForChild("BindableEvent")

local function onEventFire(passedTable)

for k, v in passedTable do

print(typeof(k)) --> string

end

end

bindableEvent.Event:Connect(onEventFire)

-- Fire event with a table containing a workspace instance as a key

bindableEvent:Fire({

[workspace.Baseplate] = true

})

Functions in Remotes

Functions referenced as arguments in a RemoteEvent or 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.

Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")

local function testFunction()

print("Hello world!")

end

-- Fire remote event with function as an argument

remoteEvent:FireAllClients(testFunction)

LocalScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")

local function onClientEvent(func)

print(func) --> nil

end

remoteEvent.OnClientEvent:Connect(onClientEvent)

Mixed Tables

If you pass a table with a mix of numeric and string keys, the values indexed by string keys are lost. In the following code sample, colorData is a mixed table. When the server receives colorData, it receives only indices [1] and [2] containing "Blue" and "Yellow". The remaining data is lost.

LocalScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")

-- Mixed table

local colorData = {}

colorData[1] = "Blue"

colorData[2] = "Yellow"

colorData["Color1"] = "Green"

colorData["Color2"] = "Red"

-- Table with two key-indexed sub-tables

local playerData = {}

playerData["CharData"] = {

-- All children indexed by key

CharName = "Diva Dragonslayer",

CharClass = "Knight"

}

playerData["Inventory"] = {

-- All children numerically indexed

"Sword",

"Bow",

"Rope"

}

remoteEvent:FireServer(colorData)

remoteEvent:FireServer(playerData)

You don't need to index subtables in the same way as their parents. As long as you index each subtable with the same type, Roblox preserves the data.

Table Identities

Tables passed as arguments to custom events are copied, meaning they will not be exactly equivalent to those provided when firing an asynchronous event or invoking a synchronous event. Tables returned to the invoker will also be copied and not equivalent to those provided. You can demonstrate this by running the following script on a BindableFunction and observing how the table identities differ.

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local bindableFunction = ReplicatedStorage:WaitForChild("BindableFunction")

bindableFunction.OnInvoke = function(passedTable)

print(tostring(passedTable))

return passedTable

end

local testTable = {message = "Hello world!"}

print(tostring(testTable))

local invokeReturn = bindableFunction:Invoke(testTable)

print(tostring(invokeReturn))

Original Identitytable: 0xf775b8bcc5e44cce
Identity on Invocationtable: 0x03b4045c4f99ec0f
Identity on Returntable: 0x11f690dfe56daf6e

Empty Indices

If a table has numeric indices, and one of the values is nil, that index and its subsequent indices are lost. In the following code example, the message table has [3] = nil, so that index and [4] = "Goodbye world!" are lost in the transmission.

Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")

local message = {

[1] = "Hello",

[2] = "world!",

[3] = nil,

[4] = "Goodbye world!"

}

remoteEvent:FireAllClients(message)

LocalScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")

local function onClientEvent(param)

print(param) --> [1] = "Hello", [2] = "world!"

end

remoteEvent.OnClientEvent:Connect(onClientEvent)

Metatables

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.

Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")

local Car = {}

Car.NumWheels = 4

Car.__index = Car

local truck = {}

truck.Name = "MyTruck"

setmetatable(truck, Car)

remoteEvent:FireAllClients(truck)

LocalScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")

local function onClientEvent(param)

print(param) --> {["Name"] = "MyTruck"}

end

remoteEvent.OnClientEvent:Connect(onClientEvent)

Non-Replicated Instances

If an event or function 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 Script tries to pass a descendant of ServerStorage, the client listening to the event receives a nil value because that object isn't replicable for the client.

Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local ServerStorage = game:GetService("ServerStorage")

local Players = game:GetService("Players")

local remoteEvent = ReplicatedStorage:WaitForChild("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 LocalScript and try to pass it to a Script, the server sees nil because the part isn't replicable for the server.

LocalScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:WaitForChild("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)

Events | Documentation - Roblox Creator Hub (2024)

References

Top Articles
Latest Posts
Article information

Author: Domingo Moore

Last Updated:

Views: 6003

Rating: 4.2 / 5 (53 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Domingo Moore

Birthday: 1997-05-20

Address: 6485 Kohler Route, Antonioton, VT 77375-0299

Phone: +3213869077934

Job: Sales Analyst

Hobby: Kayaking, Roller skating, Cabaret, Rugby, Homebrewing, Creative writing, amateur radio

Introduction: My name is Domingo Moore, I am a attractive, gorgeous, funny, jolly, spotless, nice, fantastic person who loves writing and wants to share my knowledge and understanding with you.