mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 22:11:38 +00:00
update rules
This commit is contained in:
parent
4c390fdd37
commit
d5522b2fef
8549 changed files with 11783 additions and 1888 deletions
250
.cursor/rules/roblox/en-us/scripting/events/bindable.md
Normal file
250
.cursor/rules/roblox/en-us/scripting/events/bindable.md
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
---
|
||||
title: Bindable events and callbacks
|
||||
description: Bindable events and callbacks allow for back-and-forth communication on the same side of the client-server boundary.
|
||||
---
|
||||
|
||||
`Class.BindableEvent` and `Class.BindableFunction` objects let you bind behaviors between scripts **on the same side** of the [client-server](../../projects/client-server.md) boundary and communicate a specific desired outcome for in-experience actions.
|
||||
|
||||
The most common use case for bindable events is for experiences that have a round-based structure. For example, you might have a "match started" event that lets other scripts start a timer and display a leaderboard, with a corresponding "match ended" event that lets other scripts know when to move players back into a lobby and display the winners.
|
||||
|
||||
Because they coordinate activities between scripts, bindable events are typically used on the server, but you can use them on the client, too.
|
||||
|
||||
Depending on how your experience works, bindable events can help make your code more modular, but [module scripts](../module.md) are often a better alternative for situations in which you need to share data between scripts. You can also use bindable events in conjunction with module scripts for a cleaner syntax, as noted in [Custom events](../module.md#custom-events).
|
||||
|
||||
<Alert severity="info">
|
||||
To communicate between scripts **across** the client-server boundary, see [Remote events](remote.md).
|
||||
</Alert>
|
||||
|
||||
## Bindable events
|
||||
|
||||
The `Class.BindableEvent` object enables custom events through asynchronous, one-way communication between scripts.
|
||||
|
||||
When you fire a `Class.BindableEvent` through the `Class.BindableEvent:Fire()|Fire()` method, the firing script does **not** yield, and the target function receives the passed arguments with certain [limitations](#argument-limitations). Like all events, `Class.BindableEvent|BindableEvents` create threads of each connected function, so even if one errors, others continue.
|
||||
|
||||
To create a new `Class.BindableEvent` using the [Explorer](../../studio/explorer.md) window in Studio:
|
||||
|
||||
1. Hover over the container into which you want to insert the `Class.BindableEvent`. We recommend using `Class.ServerScriptService` for communication between server scripts and `Class.ReplicatedStorage` for communication between client scripts.
|
||||
2. Click the **⊕** button that appears to the right of the container's name and insert a **BindableEvent** instance.
|
||||
3. Rename the instance to `TestBindableEvent`.
|
||||
|
||||
After you've created a `Class.BindableEvent`, connect a function to its `Class.BindableEvent.Event|Event` event in one script, and then `Class.BindableEvent:Fire()|Fire()` the event from another script.
|
||||
|
||||
```lua title="Event Connection"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
-- Get reference to bindable event instance
|
||||
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
|
||||
|
||||
-- Connect anonymous function to event
|
||||
bindableEvent.Event:Connect(function(data)
|
||||
print(data) --> Round started!
|
||||
end)
|
||||
```
|
||||
|
||||
```lua title="Event Firing"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
-- Get reference to bindable event instance
|
||||
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
|
||||
|
||||
-- Fire bindable event
|
||||
bindableEvent:Fire("Round started!")
|
||||
```
|
||||
|
||||
<Alert severity="warning">
|
||||
You can connect multiple functions to the same `Class.BindableEvent`, but Luau executes them in an unpredictable order. To ensure that functions execute in a particular order, combine them into a single function and connect it to the event.
|
||||
</Alert>
|
||||
|
||||
## Custom callbacks
|
||||
|
||||
The `Class.BindableFunction` object allows for synchronous, two-way communication between scripts. You can use it to define a custom callback function and invoke it manually by calling `Class.BindableFunction:Invoke()`. The code invoking the function **yields** until the corresponding callback is found, and the callback receives the arguments that you passed to `Class.BindableFunction:Invoke()|Invoke()`. If the callback was never set, the script that invokes it doesn't resume execution.
|
||||
|
||||
To create a new `Class.BindableFunction` using the [Explorer](../../studio/explorer.md) window in Studio:
|
||||
|
||||
1. Hover over the container into which you want to insert the `Class.BindableFunction`. We recommend using `Class.ServerScriptService` for communication between server scripts and `Class.ReplicatedStorage` for communication between client scripts.
|
||||
1. Click the **⊕** button that appears to the right of the container's name and insert a **BindableFunction** instance.
|
||||
1. Rename the instance to `TestBindableFunction`.
|
||||
|
||||
Once you've created a `Class.BindableFunction`, you can connect to its `Class.BindableFunction.OnInvoke|OnInvoke` callback in one script, then `Class.BindableFunction:Invoke()|Invoke()` the callback function from another script.
|
||||
|
||||
```lua title="Callback Connection"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
-- Get reference to bindable function
|
||||
local bindableFunction = ServerScriptService:WaitForChild("TestBindableFunction")
|
||||
|
||||
-- Callback function
|
||||
local function addTwoNumbers(a, b)
|
||||
return a + b
|
||||
end
|
||||
|
||||
-- Set function as bindable function's callback
|
||||
bindableFunction.OnInvoke = addTwoNumbers
|
||||
```
|
||||
|
||||
```lua title="Event Invocation"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
-- Get reference to bindable function
|
||||
local bindableFunction = ServerScriptService:WaitForChild("TestBindableFunction")
|
||||
|
||||
-- Invoke callback function and output returned value
|
||||
local sum = bindableFunction:Invoke(2, 4)
|
||||
print(sum) --> 6
|
||||
```
|
||||
|
||||
<Alert severity="warning">
|
||||
Each `Class.BindableFunction` can only utilize one `Class.BindableFunction.OnInvoke|OnInvoke` callback. If you make multiple definitions, only the last one assigned runs. Also note that if the `Class.BindableFunction.OnInvoke|OnInvoke` callback does not have a `return` statement, the invocation returns `nil`.
|
||||
</Alert>
|
||||
|
||||
## Argument limitations
|
||||
|
||||
When you fire a `Class.BindableEvent` or invoke a `Class.BindableFunction`, it forwards any arguments that you pass with the event or to the callback function. You can pass any type of Roblox object (`Datatype.Enum`, `Class.Instance`, etc.), as well as Luau types like numbers, strings, and booleans, although you should carefully consider the following limitations.
|
||||
|
||||
### Non-string indices
|
||||
|
||||
If any **indices** of a passed table are non-string types, such as an `Class.Instance`, [userdata](../../luau/userdata.md), or [function](../../luau/functions.md), Roblox automatically converts those indices to strings.
|
||||
|
||||
```lua title="Event Connection"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
|
||||
|
||||
local function onEventFire(passedTable)
|
||||
for k, v in passedTable do
|
||||
print(typeof(k)) --> string
|
||||
end
|
||||
end
|
||||
|
||||
-- Connect function to event
|
||||
bindableEvent.Event:Connect(onEventFire)
|
||||
```
|
||||
|
||||
```lua title="Event Firing"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
|
||||
|
||||
-- Fire event with table containing a workspace instance as a key
|
||||
bindableEvent:Fire({
|
||||
[workspace.Baseplate] = true
|
||||
})
|
||||
```
|
||||
|
||||
### Table indexing
|
||||
|
||||
If you pass a table of data, do not pass a mixed table of numeric and string keys. Doing so can result in removed elements during the transfer. Instead, pass a table that consists **entirely** of key-value pairs (a dictionary) or **entirely** of numeric indices (an array).
|
||||
|
||||
<Alert severity="warning">
|
||||
In both cases, whether passing a dictionary table or a numerically indexed table, avoid `nil` values for any index.
|
||||
</Alert>
|
||||
|
||||
```lua title="Event Connection"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
|
||||
|
||||
local function onEventFire(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
|
||||
bindableEvent.Event:Connect(onEventFire)
|
||||
```
|
||||
|
||||
```lua title="Event Firing"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
|
||||
|
||||
-- Numerically indexed table
|
||||
local inventoryData = {
|
||||
"Sword", "Bow"
|
||||
}
|
||||
-- Dictionary table
|
||||
local characterData = {
|
||||
CharName = "Diva Dragonslayer",
|
||||
CharClass = "Rogue"
|
||||
}
|
||||
|
||||
-- Fire event with consistently-indexed tables
|
||||
bindableEvent:Fire(inventoryData)
|
||||
bindableEvent:Fire(characterData)
|
||||
```
|
||||
|
||||
### Table identities
|
||||
|
||||
Tables passed as arguments to bindable events and 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.BindableFunction` and observing how the table identities differ.
|
||||
|
||||
```lua title="Callback Connection"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
local bindableFunction = ServerScriptService:WaitForChild("TestBindableFunction")
|
||||
|
||||
-- Callback function
|
||||
local function returnTable(passedTable)
|
||||
-- Output table identity on invocation
|
||||
print(tostring(passedTable)) --> table: 0x48eb7aead27563d9
|
||||
return passedTable
|
||||
end
|
||||
|
||||
-- Set function as bindable function's callback
|
||||
bindableFunction.OnInvoke = returnTable
|
||||
```
|
||||
|
||||
```lua title="Event Invocation"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
local bindableFunction = ServerScriptService:WaitForChild("TestBindableFunction")
|
||||
|
||||
local inventoryData = {
|
||||
"Sword", "Bow"
|
||||
}
|
||||
-- Output original table identity
|
||||
print(tostring(inventoryData)) --> table: 0x059bcdbb2b576549
|
||||
|
||||
local invokeReturn = bindableFunction:Invoke(inventoryData)
|
||||
|
||||
-- Output table identity upon return
|
||||
print(tostring(invokeReturn)) --> table: 0x9fcae7919563a0e9
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
```lua title="Event Connection"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
|
||||
|
||||
local function onEvent(param)
|
||||
print(param) --> {["Name"] = "MyTruck"}
|
||||
end
|
||||
|
||||
-- Connect function to event
|
||||
bindableEvent.Event:Connect(onEvent)
|
||||
```
|
||||
|
||||
```lua title="Event Firing"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
|
||||
|
||||
local Car = {}
|
||||
Car.NumWheels = 4
|
||||
Car.__index = Car
|
||||
|
||||
local truck = {}
|
||||
truck.Name = "MyTruck"
|
||||
setmetatable(truck, Car)
|
||||
|
||||
-- Fire event with table including a metatable
|
||||
bindableEvent:Fire(truck)
|
||||
```
|
||||
101
.cursor/rules/roblox/en-us/scripting/events/deferred.md
Normal file
101
.cursor/rules/roblox/en-us/scripting/events/deferred.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
title: Deferred engine events
|
||||
description: Deferred engine events defer event handlers until certain resumption points.
|
||||
---
|
||||
|
||||
The `Class.Workspace.SignalBehavior` property controls whether event handlers are fired immediately or deferred. The `Enum.SignalBehavior.Deferred` option is recommended which helps improve the performance and correctness of the engine. The event handlers for **deferred events** are resumed at the next [resumption point](#resumption-points), along with any newly triggered event handlers.
|
||||
|
||||
<Alert severity="info">
|
||||
The `Enum.SignalBehavior.Default` value of `Class.Workspace.SignalBehavior` is currently equivalent to `Enum.SignalBehavior.Immediate`, but will eventually switch to being equivalent to `Enum.SignalBehavior.Deferred`. Template places are directly set to `Enum.SignalBehavior.Deferred` by default.
|
||||
</Alert>
|
||||
|
||||
The following diagram compares the `Enum.SignalBehavior.Immediate|Immediate` event behavior and the `Enum.SignalBehavior.Deferred|Deferred` event behavior.
|
||||
|
||||
- With the `Immediate` behavior, if an event triggers another event, the second event handler fires immediately.
|
||||
- With the `Deferred` behavior, the second event is added to the back of a queue and run later.
|
||||
|
||||
The total time taken does not change, but the ordering is different.
|
||||
|
||||
<img alt="A comparison of three event handlers firing with Immediate and Deferred behavior" src="../../assets/scripting/scripts/ImmediateVsDeferredEvents.png" width="100%" />
|
||||
|
||||
"Re-entrancy" prevents events from continuously firing one another when they reach a certain depth. The current limit for this is 10.
|
||||
|
||||
## Deferred event benefits
|
||||
|
||||
The `Immediate` behavior has some disadvantages. For every instance added to your experience, property that changes, or some other trigger that is invoked, the engine needs to run Luau code before anything else happens.
|
||||
|
||||
- To change 1,000 properties, 1,000 snippets of code potentially need to run after each change.
|
||||
- Strange, hard-to-diagnose bugs can occur, such as a removing event firing before something was even added.
|
||||
- Performance-critical systems can fire events requiring them to yield back and forth to Luau.
|
||||
- Event handlers can make changes to the place or trigger other events any time an event is fired.
|
||||
- An event can fire multiple times despite being redundant, such as a property changing twice.
|
||||
|
||||
By having specific portions of the engine life cycle in which Luau can run, the engine can gain improved performance by using a number of assumptions:
|
||||
|
||||
- Performance-critical systems don't need to yield to Luau, which leads to performance gains.
|
||||
- Unless the engine itself changes it, the place never changes outside of a resumption point.
|
||||
|
||||
## Resumption points
|
||||
|
||||
After being deferred, an event handler is resumed at the next resumption point. Currently, the set of resumption points includes:
|
||||
|
||||
- Input processing (resumes once per input to be processed, see `Class.UserInputService`)
|
||||
- `Class.RunService.PreRender`
|
||||
- Legacy waiting script resumption such as `wait()`, `spawn()`, and `delay()`
|
||||
- `Class.RunService.PreAnimation`
|
||||
- `Class.RunService.PreSimulation`
|
||||
- `Class.RunService.PostSimulation`
|
||||
- Waiting script resumption such as `Library.task.wait()`, `Library.task.spawn()`, and `Library.task.delay()`
|
||||
- `Class.RunService.Heartbeat`
|
||||
- `Class.DataModel.BindToClose`
|
||||
|
||||
## Common affected code patterns
|
||||
|
||||
With remote events, the following examples either stop working correctly or have subtly different behavior; they rely on events being resumed immediately.
|
||||
|
||||
### Trigger and catch events mid-execution
|
||||
|
||||
In this example, `false` is always returned when deferred events are enabled because the callback has not run. To work correctly, the thread must yield until at least when the event should have fired.
|
||||
|
||||
```lua
|
||||
local success = false
|
||||
event:Connect(function ()
|
||||
success = true
|
||||
end)
|
||||
doSomethingToTriggerEvent() -- Causes `event` to fire
|
||||
return success
|
||||
```
|
||||
|
||||
### Listen for the first occurrence of an event
|
||||
|
||||
```lua
|
||||
connection = event:Connect(function ()
|
||||
connection:Disconnect()
|
||||
-- do something
|
||||
end)
|
||||
```
|
||||
|
||||
With deferred events enabled, multiple event handler invocations can be queued before you disconnect from the event. Calling `Class.Instance.Disconnect()|Disconnect()` drops all pending event handler invocations—the same behavior that exists for immediate events.
|
||||
|
||||
<Alert severity="warning">
|
||||
Any other method of disconnection besides `Class.Instance.Disconnect()|Disconnect()`, such as calling `Class.Instance.Destroy()|Destroy()` on the `Class.Instance`, disconnects the signal immediately, but runs the associated event handler for any events that are still pending.
|
||||
</Alert>
|
||||
|
||||
Alternatively, use `Datatype.RBXScriptSignal.Once()|Once()` as a more convenient method for connecting to an event that you only need the first invocation of.
|
||||
|
||||
### Events that change ancestry or properties
|
||||
|
||||
Deferred events cause events that handle a change in ancestry or a property to fire after the ancestry or property is changed:
|
||||
|
||||
```lua
|
||||
local part = Instance.new("Part", workspace)
|
||||
|
||||
local function onPartDestroying()
|
||||
print("In signal:", part:GetFullName(), #part:GetChildren())
|
||||
end
|
||||
|
||||
part.Destroying:Connect(onPartDestroying)
|
||||
part:Destroy()
|
||||
```
|
||||
|
||||
Because `Class.Instance.Destroy()|Destroy()` works immediately after the script that called it yields, the instance has already been destroyed by the time `onPartDestroying()` is called. For more examples, see `Class.Instance.Destroying`.
|
||||
114
.cursor/rules/roblox/en-us/scripting/events/index.md
Normal file
114
.cursor/rules/roblox/en-us/scripting/events/index.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
---
|
||||
title: Events
|
||||
description: Provides an overview of events and a summary of Roblox's built-in events.
|
||||
---
|
||||
|
||||
Events are occurrences within your experience that you can listen for and respond to. Many Roblox services and objects have built-in events that automatically **fire** in response to specific actions or changes.
|
||||
|
||||
For example, a player's `Class.Player.Character|Character` touching a `Class.BasePart` automatically fires a `Class.BasePart.Touched|Touched` event. Each time a player joins your experience, the `Class.Players.PlayerAdded` event fires.
|
||||
|
||||
Due to the sheer number of events and client-server architecture, Roblox scripting is often referred to as **event-driven**. This approach is different from many other game engines, which emphasize running code on a frame-by-frame basis.
|
||||
|
||||
You don't have to listen for events or take any action in response to them, but the events are firing and available nevertheless. When you do want to respond to an event, you connect a function to it.
|
||||
|
||||
<Alert severity="info">
|
||||
Deferred events can help you ensure more performant and consistent event handling. See [here](deferred.md) for more information.
|
||||
</Alert>
|
||||
|
||||
## Connect functions to events
|
||||
|
||||
You connect a function to an event using `Datatype.RBXScriptSignal.Connect()|Connect()` to execute code each time the event fires. Most events pass arguments to their connected functions. For example, the `Class.BasePart.Touched` event passes the object that touched the part (such as a left hand or car wheel), and the `Class.Players.PlayerAdded` event passes the `Class.Player` that joined your experience.
|
||||
|
||||
The following code sample demonstrates how to connect a function named `onPartTouched()` to the `Class.BasePart.Touched|Touched` event of a part:
|
||||
|
||||
```lua
|
||||
-- Assumes the script is parented to the part
|
||||
local part = script.Parent
|
||||
|
||||
-- The function you want to run
|
||||
local function onPartTouched(object)
|
||||
print("Part was touched by", object:GetFullName())
|
||||
end
|
||||
|
||||
-- Connect the function to the part's Touched event
|
||||
part.Touched:Connect(onPartTouched)
|
||||
```
|
||||
|
||||
<Alert severity="success">
|
||||
As a best practice and for optimal performance, it's recommended that you [disconnect](#disconnect-functions-from-events) functions when the connection is no longer needed. Note, however, that this may not **always** be necessary, as all connections to an object's [non-deferred](deferred.md) events are automatically disconnected if an object is destroyed.
|
||||
</Alert>
|
||||
|
||||
You can also connect [anonymous functions](../../luau/functions.md#anonymous-functions) to events when you want to use variables in the parent scope and don't need to use the function elsewhere. For example, this code sample avoids the awkward intermediary function from the similar sample in [Services](../services.md):
|
||||
|
||||
```lua
|
||||
local Players = game:GetService("Players")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local SaveManager = require(ReplicatedStorage:WaitForChild("SaveManager"))
|
||||
|
||||
local function saveProgress(character)
|
||||
local position = character:FindFirstChild("HumanoidRootPart").Position
|
||||
SaveManager.saveData(character, position)
|
||||
end
|
||||
|
||||
-- Anonymous function that calls saveProgress() when a character is removed
|
||||
-- from the experience (in this case, when the player leaves).
|
||||
Players.PlayerAdded:Connect(function(player)
|
||||
player.CharacterRemoving:Connect(saveProgress)
|
||||
end)
|
||||
```
|
||||
|
||||
## Disconnect functions from events
|
||||
|
||||
The `Datatype.RBXScriptSignal.Connect()|Connect()` method returns an `Datatype.RBXScriptConnection` object. If you connect a function to an event, but don't want to call the function the next time an event fires (such as after some condition is met), disconnect it by calling `Datatype.RBXScriptConnection:Disconnect()|Disconnect()` on the `Datatype.RBXScriptConnection` object.
|
||||
|
||||
The following code sample shows how to connect and disconnect a function from the `Class.BasePart.Touched|Part.Touched` event:
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local part = Workspace.Part
|
||||
local targetPart = Workspace.TargetPart
|
||||
|
||||
-- Declare an empty placeholder variable for the connection
|
||||
local connection
|
||||
|
||||
local function onPartTouched(otherPart)
|
||||
if otherPart == targetPart then
|
||||
print("The part hit the target!")
|
||||
-- Disconnect the connection
|
||||
connection:Disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
-- Connect the above function to the Touched event
|
||||
connection = part.Touched:Connect(onPartTouched)
|
||||
```
|
||||
|
||||
<Alert severity="success">
|
||||
If you only want to connect a function to an event once — that is, only run the function the first time the event fires — use the `Datatype.RBXScriptSignal.Once()|Once()` method as a more convenient alternative to connecting and disconnecting the function.
|
||||
</Alert>
|
||||
|
||||
<Alert severity="info">
|
||||
When Luau destroys an event's object, such as the `Class.Player` object when a user leaves the experience, all of its [non-deferred](deferred.md) connections disconnect automatically.
|
||||
</Alert>
|
||||
|
||||
## Wait for events to fire
|
||||
|
||||
If you want a script to yield until a specific event fires, use the `Datatype.RBXScriptSignal:Wait()|Wait()` method. This method returns the event's arguments, which you can assign to variables for later use:
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local part = Workspace.Part
|
||||
local touchedPart = part.Touched:Wait()
|
||||
print("The part was touched by", touchedPart:GetFullName())
|
||||
```
|
||||
|
||||
## Other types of events
|
||||
|
||||
- [Bindable events](bindable.md) let you communicate between scripts on the **same side** of the client-server boundary.
|
||||
|
||||
- [Remote events](remote.md) let you communicate **across** the client-server boundary.
|
||||
|
||||
- [Deferred engine events](deferred.md) defer event handlers until certain resumption points.
|
||||
584
.cursor/rules/roblox/en-us/scripting/events/remote.md
Normal file
584
.cursor/rules/roblox/en-us/scripting/events/remote.md
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
---
|
||||
title: Remote events and callbacks
|
||||
description: 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 the experience's 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](bindable.md), 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 experience's 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 [Prompt subscription purchases](../../production/monetization/subscriptions.md#prompt-subscription-purchases).
|
||||
|
||||
## Quick reference
|
||||
|
||||
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.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Remote events">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><td colspan="2">[Client → Server](#client-server)</td></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="12%">Client</td>
|
||||
<td>`RemoteEvent:FireServer(args)`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Server</td>
|
||||
<td>`RemoteEvent.OnServerEvent:Connect(function(player, args))`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<thead>
|
||||
<tr><td colspan="2">[Server → Client](#server-client)</td></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="12%">Server</td>
|
||||
<td>`RemoteEvent:FireClient(player, args)`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Client</td>
|
||||
<td>`RemoteEvent.OnClientEvent:Connect(function(args))`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<thead>
|
||||
<tr><td colspan="2">[Server → All Clients](#server-all-clients)</td></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="12%">Server</td>
|
||||
<td>`RemoteEvent:FireAllClients(args)`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Client</td>
|
||||
<td>`RemoteEvent.OnClientEvent:Connect(function(args))`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</TabItem>
|
||||
<TabItem label="Remote functions">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><td colspan="2">[Client → Server → Client](#client-server-client)</td></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="12%">Client</td>
|
||||
<td>`serverResponse = RemoteFunction:InvokeServer(args)`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Server</td>
|
||||
<td>`RemoteFunction.OnServerInvoke = function(player, args)`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<thead>
|
||||
<tr><td colspan="2">[Server → Client → Server](#server-client-server)</td></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="2">See [risks](#server-client-server).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Remote events
|
||||
|
||||
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](../../studio/explorer.md) window in Studio:
|
||||
|
||||
1. 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 as `Class.ReplicatedStorage`, although in some cases it's appropriate to store it in `Class.Workspace` or inside a `Class.Tool`.
|
||||
1. Click the **⊕** button that appears to the right of the container's name and insert a **RemoteEvent** instance.
|
||||
1. Rename the instance to describe its purpose.
|
||||
|
||||
Once you've created a `Class.RemoteEvent`, it can facilitate one-way communication from [client to server](#client-server), from [server to client](#server-client), or from the [server to all clients](#server-all-clients).
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<center>
|
||||
<img src="../../assets/scripting/client-server/Remote-Flow-Client-Server.png" width="85%" />
|
||||
<figcaption>Client → Server</figcaption>
|
||||
</center>
|
||||
</figure>
|
||||
<figure>
|
||||
<center>
|
||||
<img src="../../assets/scripting/client-server/Remote-Flow-Server-Client.png" width="85%" />
|
||||
<figcaption>Server → Client</figcaption>
|
||||
</center>
|
||||
</figure>
|
||||
<figure>
|
||||
<center>
|
||||
<img src="../../assets/scripting/client-server/Remote-Flow-Server-All-Clients.png" width="85%" />
|
||||
<figcaption>Server → All Clients</figcaption>
|
||||
</center>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<Alert severity="info">
|
||||
Clients cannot communicate directly with other clients, although you can effectively dispatch an event from one client to another by using the `Class.RemoteEvent:FireServer()` method, then calling `Class.RemoteEvent:FireClient()|FireClient()` or `Class.RemoteEvent:FireAllClients()|FireAllClients()` in the event handler for `Class.RemoteEvent.OnServerEvent|OnServerEvent`.
|
||||
</Alert>
|
||||
|
||||
### Client → server
|
||||
|
||||
You can use a `Class.LocalScript` to trigger an event on the [server](../../projects/client-server.md) 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](#argument-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.
|
||||
|
||||
<table size="small">
|
||||
<tr>
|
||||
<td width="12%">Client</td>
|
||||
<td>`RemoteEvent:FireServer(args)`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Server</td>
|
||||
<td>`RemoteEvent.OnServerEvent:Connect(function(player, args))`</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
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.
|
||||
|
||||
```lua title="Event Connection - Script"
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
-- 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)
|
||||
```
|
||||
|
||||
```lua title="Event Firing - LocalScript"
|
||||
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))
|
||||
```
|
||||
|
||||
### Server → client
|
||||
|
||||
You can use a `Class.Script` to trigger an event on a [client](../../projects/client-server.md) 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](#argument-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`.
|
||||
|
||||
<table size="small">
|
||||
<tr>
|
||||
<td width="12%">Server</td>
|
||||
<td>`RemoteEvent:FireClient(player, args)`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Client</td>
|
||||
<td>`RemoteEvent.OnClientEvent:Connect(function(args))`</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
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.
|
||||
|
||||
```lua title="Event Connection - LocalScript"
|
||||
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)
|
||||
```
|
||||
|
||||
```lua title="Event Firing - Script"
|
||||
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)
|
||||
```
|
||||
|
||||
### Server → all clients
|
||||
|
||||
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.
|
||||
|
||||
<table size="small">
|
||||
<tr>
|
||||
<td width="12%">Server</td>
|
||||
<td>`RemoteEvent:FireAllClients(args)`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Client</td>
|
||||
<td>`RemoteEvent.OnClientEvent:Connect(function(args))`</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
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.
|
||||
|
||||
```lua title="Event Connection - LocalScript"
|
||||
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)
|
||||
```
|
||||
|
||||
```lua title="Event Firing - Script"
|
||||
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
|
||||
```
|
||||
|
||||
## Remote callbacks
|
||||
|
||||
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](../../studio/explorer.md) window in Studio:
|
||||
|
||||
1. 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 as `Class.ReplicatedStorage`, although in some cases it's appropriate to store it in `Class.Workspace` or inside a `Class.Tool`.
|
||||
1. Click the **⊕** button that appears to the right of the container's name and insert a **RemoteFunction** instance.
|
||||
1. Rename the instance to describe its purpose.
|
||||
|
||||
Once you've created a `Class.RemoteFunction`, it can facilitate two-way communication between [client and server](#client-server-client) or between [server and client](#server-client-server).
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<center>
|
||||
<img src="../../assets/scripting/client-server/Remote-Flow-Client-Server-Client.png" width="85%" />
|
||||
<figcaption>Client → Server → Client</figcaption>
|
||||
</center>
|
||||
</figure>
|
||||
<figure>
|
||||
<center>
|
||||
<img src="../../assets/scripting/client-server/Remote-Flow-Server-Client-Server.png" width="85%" />
|
||||
<figcaption>Server → Client → Server</figcaption>
|
||||
</center>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
### Client → server → client
|
||||
|
||||
You can use a `Class.LocalScript` to call a function on the [server](../../projects/client-server.md) by calling the `Class.RemoteFunction:InvokeServer()|InvokeServer()` method on a `Class.RemoteFunction`. Unlike a [remote event](#remote-events), 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](#argument-limitations). Note that if you define multiple callbacks to the same `Class.RemoteFunction`, only the last definition executes.
|
||||
|
||||
<table size="small">
|
||||
<tr>
|
||||
<td width="10%">Client</td>
|
||||
<td>`RemoteFunction:InvokeServer(args)`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Server</td>
|
||||
<td>`RemoteFunction.OnServerInvoke = function(player, args)`</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
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.
|
||||
|
||||
```lua title="Callback Connection - Script"
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
-- 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
|
||||
```
|
||||
|
||||
```lua title="Event Invocation - LocalScript"
|
||||
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)
|
||||
```
|
||||
|
||||
### Server → client → server
|
||||
|
||||
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](#server-client).
|
||||
|
||||
## Argument limitations
|
||||
|
||||
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.
|
||||
|
||||
### Non-string indices
|
||||
|
||||
If any **indices** of a passed table are non-string types such as an `Class.Instance`, [userdata](../../luau/userdata.md), or [function](../../luau/functions.md), Roblox automatically converts those indices to strings.
|
||||
|
||||
```lua title="Event Connection - LocalScript"
|
||||
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)
|
||||
```
|
||||
|
||||
```lua title="Event Firing - Script"
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local Players = game:GetService("Players")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
### Passed functions
|
||||
|
||||
Functions included as arguments for a `Class.RemoteEvent` or `Class.RemoteFunction` will **not** be replicated across the [client-server](../../projects/client-server.md) boundary, making it impossible to pass functions remotely. Instead, the resulting argument on the receiving side will be `nil`.
|
||||
|
||||
```lua title="Event Connection - LocalScript"
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
|
||||
|
||||
local function onClientEvent(func)
|
||||
print(func) --> nil
|
||||
end
|
||||
|
||||
remoteEvent.OnClientEvent:Connect(onClientEvent)
|
||||
```
|
||||
|
||||
```lua title="Event Firing - Script"
|
||||
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)
|
||||
```
|
||||
|
||||
### Table indexing
|
||||
|
||||
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.
|
||||
|
||||
<Alert severity="warning">
|
||||
Whether passing a dictionary table **or** a numerically indexed table, avoid `nil` values for any index.
|
||||
</Alert>
|
||||
|
||||
```lua title="Event Connection - Script"
|
||||
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)
|
||||
```
|
||||
|
||||
```lua title="Event Firing - LocalScript"
|
||||
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)
|
||||
```
|
||||
|
||||
### Table identities
|
||||
|
||||
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.
|
||||
|
||||
```lua title="Callback Connection - Script"
|
||||
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
|
||||
```
|
||||
|
||||
```lua title="Event Invocation - LocalScript"
|
||||
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
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
```lua title="Event Connection - Script"
|
||||
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)
|
||||
```
|
||||
|
||||
```lua title="Event Firing - LocalScript"
|
||||
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)
|
||||
```
|
||||
|
||||
### Non-replicated instances
|
||||
|
||||
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.
|
||||
|
||||
```lua title="Event Firing - Script"
|
||||
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.
|
||||
|
||||
```lua title="Event Firing - LocalScript"
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
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)
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue