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:
- 将鼠标悬停在你想要插入 RemoteEvent 的容器上。为了确保服务器和客户端访问,它必须放在两侧都能看到的地方,例如 ReplicatedStorage,虽然在某些情况下考虑存储在 Workspace 或 Tool 内是合适的。
- 单击容器名称右侧出现的 ⊕ 按钮,插入一个 RemoteEvent 实例。
- 重命名该实例以描述其用途。
创建 RemoteEvent 后,它可以促进从 客户端到服务器、从 服务器到客户端 或从 服务器到所有客户端 的单向通信。



客户端 → 服务器
你可以使用 LocalScript 在 服务器 上触发事件,方法是在 RemoteEvent 上调用 FireServer() 方法。如果你将参数传递给 FireServer(),它们将以某些 限制 传递给服务器上的事件处理程序。注意,事件处理程序的第一个参数始终是调用它的客户端的 Player 对象,后面的参数依次排列。
| 客户端 | RemoteEvent:FireServer(args) |
| 服务器 | RemoteEvent.OnServerEvent:Connect(function(player, args)) |
以下 Script 连接一个事件处理程序到 OnServerEvent,该事件处理程序在服务器上创建一个新的 Part。相应的 LocalScript 然后调用 FireServer() 在 RemoteEvent 实例上,以所需的 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 .. " 触发了远程事件")
local newPart = Instance.new("Part")
newPart.Color = partColor
newPart.Position = partPosition
newPart.Parent = Workspace
end
-- 将函数连接到事件
remoteEvent.OnServerEvent:Connect(onCreatePart)
事件触发 - LocalScriptlocal 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-- 每秒触发一次远程事件,直到倒计时结束for timeRemaining = -1, countdown doremoteEvent:FireAllClients(countdown - timeRemaining)task.wait(1)end
远程回调
RemoteFunction 对象促进了跨客户端-服务器边界的同步双向通信。远程函数的发送者将等待,直到从接收方收到响应。
要通过 Studio 中的 资源管理器 窗口创建新的 RemoteFunction:
- 将鼠标悬停在你想要插入 RemoteFunction 的容器上。为了确保服务器和客户端访问,它必须放在两侧都能看到的地方,例如 ReplicatedStorage,虽然在某些情况下考虑存储在 Workspace 或 Tool 内是合适的。
- 单击容器名称右侧出现的 ⊕ 按钮,插入一个 RemoteFunction 实例。
- 重命名该实例以描述其用途。
创建 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
事件调用 - LocalScriptlocal 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 并从 服务器到客户端 进行通信。
参数限制
当你触发一个 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("你好,世界!")
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 = 女武神
--> CharClass = 盗贼
end
end
-- 将函数连接到事件
remoteEvent.OnServerEvent:Connect(onEventFire)
事件触发 - LocalScriptlocal ReplicatedStorage = game:GetService("ReplicatedStorage")local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")-- 数字索引表local inventoryData = {"剑", "弓"}-- 字典表local characterData = {CharName = "女武神",CharClass = "盗贼"}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
事件调用 - LocalScriptlocal ReplicatedStorage = game:GetService("ReplicatedStorage")local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")local inventoryData = {"剑", "弓"}-- 输出原始表身份print(tostring(inventoryData)) --> table: 0x059bcdbb2b576549local 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"] = "我的卡车"}
end
-- 将函数连接到事件
remoteEvent.OnServerEvent:Connect(onEvent)
事件触发 - LocalScriptlocal ReplicatedStorage = game:GetService("ReplicatedStorage")local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")local Car = {}Car.NumWheels = 4Car.__index = Carlocal truck = {}truck.Name = "我的卡车"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,因为该零件对服务器不可复制。
事件触发 - LocalScriptlocal ReplicatedStorage = game:GetService("ReplicatedStorage")local Workspace = game:GetService("Workspace")local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")-- 将被接收为 "nil",因为服务器不知道这个零件local clientPart = Instance.new("Part")clientPart.Parent = WorkspaceremoteEvent:FireServer(clientPart)