远程事件和回调

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处。

Roblox 游戏默认是多人游戏,因此所有游戏本质上都在服务器和玩家的连接客户端之间进行通信。在最简单的情况下,当玩家移动他们的角色时,某些 Humanoid 属性(例如状态)会传达给服务器,服务器再将这些信息传递给其他连接的客户端。

远程事件和回调让你能够在客户端和服务器之间进行 跨越 通信:

  • RemoteEvents 允许单向通信(发送请求并 不 等待响应)。
  • UnreliableRemoteEvents 允许对不断变化或对游戏状态不关键的数据进行单向通信。这些事件以提高网络性能为代价,牺牲了顺序和可靠性。
  • RemoteFunctions 允许双向通信(发送请求并等待直到收到来自接收方的响应)。

与 可绑定事件 不同,后者的实用性更有限,远程事件和函数的用例太多,无法一一列举:

  • 游戏玩法 - 基本游戏玩法,例如玩家到达关卡的尽头,可能需要一个远程事件。客户端脚本通知服务器,服务器脚本重置玩家的位置。
  • 服务器验证 - 如果玩家尝试喝药水,他们真的 拥有 那个药水吗?为了确保公平,服务器必须是游戏的真实来源。客户端脚本可以使用远程事件通知服务器玩家正在喝药水,然后服务器脚本可以决定玩家是否真的拥有那个药水以及是否给予任何好处。
  • 用户界面更新 - 随着游戏状态的变化,服务器脚本可以使用远程事件通知客户端分数、目标等的变化。
  • 游戏内市场购买 - 有关使用远程函数的示例实现,请参见 提示订阅购买。

快速参考

以下表格作为快速参考,说明如何使用 RemoteEvents 和 RemoteFunctions 在客户端和服务器之间进行通信。

客户端 → 服务器
客户端RemoteEvent:FireServer(args)
服务器RemoteEvent.OnServerEvent:Connect(function(player, args))
服务器 → 客户端
服务器RemoteEvent:FireClient(player, args)
客户端RemoteEvent.OnClientEvent:Connect(function(args))
服务器 → 所有客户端
服务器RemoteEvent:FireAllClients(args)
客户端RemoteEvent.OnClientEvent:Connect(function(args))

远程事件

RemoteEvent 对象促进了跨客户端和服务器边界的异步单向通信,而无需等待响应。

要通过 Studio 中的 资源管理器 窗口创建新的 RemoteEvent:

  1. 将鼠标悬停在要插入 RemoteEvent 的容器上。为了确保服务器和客户端都能访问,它必须放在双方都能看到的地方,例如 ReplicatedStorage,尽管在某些情况下,将其存储在 Workspace 或 Tool 中也是合适的。
  2. 点击出现在容器名称右侧的 ⊕ 按钮并插入一个 RemoteEvent 实例。
  3. 重命名实例以描述其用途。

创建 RemoteEvent 后,它可以促进从 客户端到服务器、从 服务器到客户端 或从 服务器到所有客户端 的单向通信。

客户端 → 服务器
服务器 → 客户端
服务器 → 所有客户端

客户端 → 服务器

你可以使用 LocalScript 通过在 RemoteEvent 上调用 FireServer() 方法来触发 服务器 上的事件。如果你将参数传递给 FireServer(),它们将以某些 限制 传递给服务器上的事件处理程序。请注意,事件处理程序的第一个参数始终是调用它的客户端的 Player 对象,后续参数依次跟随。

客户端RemoteEvent:FireServer(args)
服务器RemoteEvent.OnServerEvent:Connect(function(player, args))

以下 Script 将事件处理程序连接到 OnServerEvent,该处理程序在服务器上创建一个新的 Part。随附的 LocalScript 然后在 RemoteEvent 实例上调用 FireServer(),并传递所需的 Color 和 Position 作为部分。

事件连接 - 脚本
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
-- 获取远程事件实例的引用
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onCreatePart(player, partColor, partPosition)
print(player.Name .. " 触发了 RemoteEvent")
local newPart = Instance.new("Part")
newPart.Color = partColor
newPart.Position = partPosition
newPart.Parent = Workspace
end
-- 将函数连接到事件
remoteEvent.OnServerEvent:Connect(onCreatePart)
事件触发 - LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- 获取远程事件实例的引用
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- 触发远程事件并传递额外参数
remoteEvent:FireServer(Color3.fromRGB(255, 0, 0), Vector3.new(0, 25, -20))

服务器 → 客户端

你可以使用 Script 通过在 RemoteEvent 上调用 FireClient() 方法来触发 客户端 上的事件。FireClient() 的第一个参数是你希望响应事件的客户端的 Player 对象,额外参数将以某些 限制 传递给客户端。请注意,事件处理程序不需要将 Player 对象作为第一个参数,因为你可以通过 Players.LocalPlayer 在客户端确定玩家。

服务器RemoteEvent:FireClient(player, args)
客户端RemoteEvent.OnClientEvent:Connect(function(args))

以下 LocalScript 将事件处理程序连接到 OnClientEvent 事件。随附的 Script 然后监听进入服务器的玩家,并为每个玩家调用 FireClient(),并传递任意数据。

事件连接 - LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
-- 获取远程事件实例的引用
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local player = Players.LocalPlayer
local function onNotifyPlayer(maxPlayers, respawnTime)
print("[客户端] 玩家接收到事件", player.Name)
print(maxPlayers, respawnTime)
end
-- 将函数连接到事件
remoteEvent.OnClientEvent:Connect(onNotifyPlayer)
事件触发 - 脚本
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
-- 获取远程事件实例的引用
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- 监听进入的玩家并向每个玩家分发远程事件
local function onPlayerAdded(player)
print("[服务器] 向玩家触发事件", player.Name)
remoteEvent:FireClient(player, Players.MaxPlayers, Players.RespawnTime)
end
Players.PlayerAdded:Connect(onPlayerAdded)

服务器 → 所有客户端

你可以使用 Script 通过在 RemoteEvent 上调用 FireAllClients() 方法来触发所有客户端上的事件。与 FireClient() 不同,FireAllClients() 方法不需要 Player 对象,因为它会将 RemoteEvent 发送给所有客户端。

服务器RemoteEvent:FireAllClients(args)
客户端RemoteEvent.OnClientEvent:Connect(function(args))

以下 LocalScript 将事件处理程序连接到 OnClientEvent 事件,该事件输出剩余的倒计时。随附的 Script 然后每秒在循环中调用 FireAllClients(),以向所有客户端触发 RemoteEvent。

事件连接 - LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- 获取远程事件实例的引用
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onTimerUpdate(seconds)
print(seconds)
end
-- 将函数连接到事件
remoteEvent.OnClientEvent:Connect(onTimerUpdate)
事件触发 - 脚本
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- 获取远程事件实例的引用
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local countdown = 5
-- 每秒触发 RemoteEvent,直到时间到期
for timeRemaining = -1, countdown do
remoteEvent:FireAllClients(countdown - timeRemaining)
task.wait(1)
end

远程回调

RemoteFunction 对象促进了跨客户端和服务器边界的同步双向通信。远程函数的发送者将等待,直到收到接收方的响应。

要通过 Studio 中的 资源管理器 窗口创建新的 RemoteFunction:

  1. 将鼠标悬停在要插入 RemoteFunction 的容器上。为了确保服务器和客户端都能访问,它必须放在双方都能看到的地方,例如 ReplicatedStorage,尽管在某些情况下,将其存储在 Workspace 或 Tool 中也是合适的。
  2. 点击出现在容器名称右侧的 ⊕ 按钮并插入一个 RemoteFunction 实例。
  3. 重命名实例以描述其用途。

创建 RemoteFunction 后,它可以促进 客户端和服务器 或 服务器和客户端 之间的双向通信。

客户端 → 服务器 → 客户端
服务器 → 客户端 → 服务器

客户端 → 服务器 → 客户端

你可以使用 LocalScript 通过在 RemoteFunction 上调用 InvokeServer() 方法来调用 服务器 上的函数。与 远程事件 不同,调用 RemoteFunction 的 LocalScript 会等待,直到回调返回。你传递给 InvokeServer() 的参数将以某些 限制 传递给 OnServerInvoke 回调。请注意,如果你为同一个 RemoteFunction 定义多个回调,只有最后一个定义会执行。

客户端RemoteFunction:InvokeServer(args)
服务器RemoteFunction.OnServerInvoke = function(player, args)

以下 Script 通过 OnServerInvoke 定义回调函数,并通过其 return 值返回请求的 Part。随附的 LocalScript 然后调用 InvokeServer(),并传递额外参数以定义请求的部分颜色和位置。

回调连接 - 脚本
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
-- 获取远程函数实例的引用
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
-- 回调函数
local function createPart(player, partColor, partPosition)
print(player.Name .. " 请求了一个新部分")
local newPart = Instance.new("Part")
newPart.Color = partColor
newPart.Position = partPosition
newPart.Parent = Workspace
return newPart
end
-- 将函数设置为远程函数的回调
remoteFunction.OnServerInvoke = createPart
事件调用 - LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- 获取远程函数实例的引用
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
-- 在调用回调时传递颜色和位置
local newPart = remoteFunction:InvokeServer(Color3.fromRGB(255, 0, 0), Vector3.new(0, 25, -20))
-- 输出返回的部分引用
print("服务器创建了请求的部分:", newPart)

服务器 → 客户端 → 服务器

你可以使用 Script 通过在 RemoteFunction 上调用 InvokeClient() 方法来调用客户端上的函数,但这有严重的风险,如下所示:

  • 如果客户端抛出错误,服务器也会抛出该错误。
  • 如果客户端在调用时断开连接,InvokeClient() 会抛出错误。
  • 如果客户端没有返回值,服务器将永远等待。

对于不需要双向通信的操作,例如更新 GUI,请使用 RemoteEvent 并从 服务器到客户端 进行通信。

交付保证

每种远程类型对消息是否到达以及以何种顺序到达做出不同的承诺。这些承诺仅适用于单个连接的单个方向,意味着从服务器到一个特定客户端,或从一个特定客户端到服务器。

RemoteEvents

RemoteEvents 是可靠且有序的:

  • 如果消息在传输中丢失,Roblox 会重新传输,直到它到达,只要接收方保持连接。
  • 消息按你触发它们的顺序到达,包括跨不同的 RemoteEvent 实例。例如,如果服务器先触发事件 A,然后是 B,再到同一客户端触发 A,该客户端将接收到 A、B、A。无论你使用 FireClient() 还是 FireAllClients(),这都适用。
  • 如果客户端触发事件的速度超过 节流 限制,服务器会稍后处理多余的事件,而不是丢弃它们,并保持它们的顺序。

可靠、有序的消息在以下情况下仍然可能无法到达你的代码:

  • 没有处理程序连接。 如果在消息到达时没有任何内容连接到 OnServerEvent 或 OnClientEvent,Roblox 会将其排队并在连接处理程序后按顺序交付。队列在数量和内存上都是有限的。一旦满了,Roblox 会丢弃进一步的消息,并在 输出 窗口中记录 Remote event invocation 错误。
  • 接收方断开连接。 当客户端离开时,尚未交付的消息会丢失。

顺序适用于每个处理程序 开始 的时间,而不是完成的时间。如果处理程序阻塞,例如通过调用 task.wait() 或 WaitForChild(),Roblox 可以在早期处理程序恢复之前开始处理下一个消息。

RemoteFunctions

RemoteFunctions 可靠地发送调用和响应,因此只要双方保持连接,Roblox 会重新传输丢失的消息。由于 InvokeServer() 和 InvokeClient() 在响应到达之前会阻塞,因此单个线程不能同时有多个调用在进行中。尽管不同的线程可以同时调用,如果回调阻塞,响应可能会以不同于发送调用的顺序返回。

如果没有回调分配给 OnServerInvoke 或 OnClientInvoke,Roblox 会将调用排队,直到有一个可用。这个队列与 RemoteEvent 队列有相同的限制。

UnreliableRemoteEvents

UnreliableRemoteEvents 不保证交付或顺序。它们与 RemoteEvents 或 RemoteFunctions 之间也没有顺序关系。Roblox 可能在以下任何情况下丢弃消息,而不重新发送:

  • 消息在传输中丢失。
  • 网络拥塞,消息等待发送的时间过长。
  • 有效负载大于 1,000 字节。
  • 客户端触发的速度超过 节流 限制。与 RemoteEvent 不同,服务器会丢弃多余的消息,而不是延迟它们。
  • 当消息到达时,没有任何内容连接到 OnServerEvent 或 OnClientEvent。与 RemoteEvent 不同,Roblox 会立即丢弃消息,而不是将其排队。

Roblox 也不会等待早期消息到达后再处理后续消息,因此消息可能会无序到达。如果顺序很重要,请在每条消息中包含时间戳或序列号,并忽略比你处理的最新消息更早的消息。

节流和大小限制

客户端到服务器的消息受到速率限制,每种远程类型在其所有实例之间共享。有关当前限制,请参见 RemoteEvent 的 节流 部分和 UnreliableRemoteEvent 中的有效负载限制。

参数限制

当你触发 RemoteEvent 或调用 RemoteFunction 时,它会转发你与事件或回调函数一起传递的任何参数。任何类型的 Roblox 对象,例如 Enum、Instance 或其他对象都可以传递,以及 Luau 类型,例如数字、字符串和布尔值,尽管你应该仔细探索以下限制。

非字符串索引

如果传递的表的任何 索引 是非字符串类型,例如 Instance、userdata 或 function,Roblox 会自动将这些索引转换为字符串。

事件连接 - 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
-- 将函数连接到事件
remoteEvent.OnClientEvent:Connect(onEventFire)
事件触发 - 脚本
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- 监听进入的玩家并向每个玩家分发远程事件
local function onPlayerAdded(player)
remoteEvent:FireClient(player,
{
[Workspace.Baseplate] = true
}
)
end
Players.PlayerAdded:Connect(onPlayerAdded)

传递的函数

作为 RemoteEvent 或 RemoteFunction 参数包含的函数将 不会 跨越 客户端-服务器 边界进行复制,因此无法远程传递函数。相反,接收方的结果参数将为 nil。

事件连接 - LocalScript
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
-- 触发远程事件并传递函数作为参数
remoteEvent:FireAllClients(testFunction)

表索引

如果你传递一个数据表,请不要传递一个混合了数字和字符串键的表。相反,请传递一个完全由键值对(字典)或完全由数字索引组成的表。

事件连接 - 脚本
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 = 剑
--> 2 = 弓
--> CharName = Diva Dragonslayer
--> CharClass = Rogue
end
end
-- 将函数连接到事件
remoteEvent.OnServerEvent:Connect(onEventFire)
事件触发 - LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- 数字索引表
local inventoryData = {
"剑", "弓"
}
-- 字典表
local characterData = {
CharName = "Diva Dragonslayer",
CharClass = "Rogue"
}
remoteEvent:FireServer(inventoryData)
remoteEvent:FireServer(characterData)

表身份

作为参数传递给远程事件/回调的表会被复制,这意味着它们与触发事件或调用回调时提供的表不会完全相同。返回给调用者的表也不会完全相同。你可以通过在 RemoteFunction 上运行以下脚本并观察表身份的不同来证明这一点。

回调连接 - 脚本
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
-- 回调函数
local function returnTable(player, passedTable)
-- 输出调用时的表身份
print(tostring(passedTable)) --> table: 0x48eb7aead27563d9
return passedTable
end
-- 将函数设置为远程函数的回调
remoteFunction.OnServerInvoke = returnTable
事件调用 - LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
local inventoryData = {
"剑", "弓"
}
-- 输出原始表身份
print(tostring(inventoryData)) --> table: 0x059bcdbb2b576549
local invokeReturn = remoteFunction:InvokeServer(inventoryData)
-- 输出返回时的表身份
print(tostring(invokeReturn)) --> table: 0x9fcae7919563a0e9

元表

如果一个表具有元表,所有的元表信息在传输中都会丢失。在以下代码示例中,NumWheels 属性是 Car 元表的一部分。当服务器接收到以下表时,truck 表具有 Name 属性,但 没有 NumWheels 属性。

事件连接 - 脚本
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onEvent(player, param)
print(param) --> {["Name"] = "MyTruck"}
end
-- 将函数连接到事件
remoteEvent.OnServerEvent:Connect(onEvent)
事件触发 - 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)
-- 触发事件,传递包含元表的表
remoteEvent:FireServer(truck)

非复制实例

如果 RemoteEvent 或 RemoteFunction 传递一个仅对发送者可见的值,Roblox 不会在客户端和服务器边界上复制它,而是传递 nil 代替该值。例如,如果 Script 传递 ServerStorage 的后代,监听事件的客户端将收到 nil 值,因为该对象对客户端不可复制。

事件触发 - 脚本
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local Players = game:GetService("Players")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- 将被接收为 "nil",因为客户端无法访问 ServerStorage
local storedPart = Instance.new("Part")
storedPart.Parent = ServerStorage
local function onPlayerAdded(player)
remoteEvent:FireClient(player, storedPart)
end
Players.PlayerAdded:Connect(onPlayerAdded)

同样,如果你在 LocalScript 中创建一个部分并尝试将其传递给 Script,服务器将看到 nil,因为该部分对服务器不可复制。

事件触发 - LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- 将被接收为 "nil",因为服务器不知道这个部分
local clientPart = Instance.new("Part")
clientPart.Parent = Workspace
remoteEvent:FireServer(clientPart)
©2026 Roblox Corporation、Roblox、Roblox 标志及 Powering Imagination 是我们在美国及其他国家或地区的注册与未注册商标。