远程事件和回调

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

Roblox 体验默认为多人游戏,所有体验内在上都与服务器和玩家的连接客户端之间通信。在最简单的情况下,当玩家移动角色时,某些 Humanoid 属性,例如状态,会通过服务器传递给其他连接的客户端,这些客户端将传达这些信息给其他连接的客户端。

远程事件和回调可以让你在客户端-服务器边界上通信

  • RemoteEvents 启用单向通信(发送请求并 回应)。
  • UnreliableRemoteEvents 启用连续更改或不对游戏状态造成重大影响的数据的单向通信这些事件交易订单和可靠性以提高网络性能。
  • RemoteFunctions 启用双向通信(向收件人发送请求并等待收到回复)。

可绑定事件 相比,远程事件和函数的使用案例太多以至无法列出:

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

快速参考

以下表格作为快速参考,用于客户端和服务器之间通信的方法 RemoteEventsRemoteFunctions

> > >
客户端 → 服务器
客户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 的容器上。为了确保服务器和客户端1) 使用权 2)通行证 3)访问权限,它必须位于两方都能看到它的地方,例如 ReplicatedStorage , 虽然在某些情况下,存储它在 Workspace 或内部的 Tool 是适当的。
  2. 点击容器名称右侧的 按钮,然后插入一个 远程事件 实例。
  3. 重命名实例以描述其目的。

一旦你创建了一个 RemoteEvent , 它可以简化从 客户端到服务器 、从 服务器到客户端 或从 服务器到所有客户端 的单向通信。

客户端 → 服务器
>

服务器 → 客户端
>

服务器 → 所有客户端
>

客户端 → 服务器

您可以使用 来在 服务器 上触发事件,调用 方法在 上。如果您向 FireServer() 传递参数,它们将传递到服务器上的事件处理器,受到某些 限制 。请注意,服务器上事件处理器的第一个参数总是客户端调用它的 Player 对象,其他参数随后跟关注。

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

以下 Script 连接一个事件处理器到 OnServerEvent 创建一个新的 Part 在服务器上。随后的 LocalScript 然后调用 FireServer()RemoteEvent 实例上需要的 ColorPosition 部分。

事件连接 - 脚本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
-- 获取远程事件实例的参考
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
-- 将连接函数连接到事件
remoteEvent.OnServerEvent:Connect(onCreatePart)
事件发射 - 本地脚本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- 获取远程事件实例的参考
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- 发射远程事件并传递额外参数
remoteEvent:FireServer(Color3.fromRGB(255, 0, 0), Vector3.new(0, 25, -20))

服务器 → 客户端

您可以使用 来触发客户端上的事件,调用 方法在 上。第一个为 的第一个参数是您想回应事件的客户端对象,其余参数传给客户端以某些限制 。请注意,事件处理器不需要包含 Player 对象作为第一个参数,因为你可以使用 Players.LocalPlayer 来确定客户端上的玩家。

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

以下 LocalScript 连接一个事件处理器到 OnClientEvent 事件。然后,附带的 Script 接收服务器收到的玩家,并为每个随机数据调用 FireClient()

事件连接 - 本地脚本

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("[Client] Event received by player", 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("[Server] Firing event to player", player.Name)
remoteEvent:FireClient(player, Players.MaxPlayers, Players.RespawnTime)
end
Players.PlayerAdded:Connect(onPlayerAdded)

服务器→所有客户端

您可以使用 Script 来触发所有客户端上的事件,调用 FireAllClients() 方法在 RemoteEvent 上。与 FireClient() 不同,FireAllClients() 方法不需要 Player 对象,因为它向所有客户发射 RemoteEvent

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

以下 LocalScript 连接一个事件处理器到输出剩余倒计时时间的 OnClientEvent 事件。然后附带的 Script 每秒调用 FireAllClients() 在循环中发射所有客户端的 RemoteEvent

事件连接 - 本地脚本

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 do
remoteEvent:FireAllClients(countdown - timeRemaining)
task.wait(1)
end

远程回调

一个 RemoteFunction 对象可以简化客户端-服务器边界上的同步、双向通信。远程函数的发送者将继续输出直到收到接收收件人的回复。

要在 Studio 中通过 RemoteFunction 窗口创建一个新的

  1. 将鼠标悬停在你想要插入 RemoteFunction 的容器上。为了确保服务器和客户端1) 使用权 2)通行证 3)访问权限,它必须位于两方都能看到它的地方,例如 ReplicatedStorage , 虽然在某些情况下,存储它在 Workspace 或内部的 Tool 是适当的。
  2. 点击容器名称右侧的 按钮,然后插入一个 远程函数 实例。
  3. 重命名实例以描述其目的。

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

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

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

客户端 → 服务器 → 客户端

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

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

以下 Script 定义了回调函数通过 OnServerInvoke 返回请求的 Part 通过其 return 值。随后的 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 .. " requested a new part")
local newPart = Instance.new("Part")
newPart.Color = partColor
newPart.Position = partPosition
newPart.Parent = Workspace
return newPart
end
-- 将函数设置为远程函数的回调
remoteFunction.OnServerInvoke = createPart
事件调用 - 本地脚本

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("The server created the requested part:", newPart)

服务器 → 客户端 → 服务器

您可以使用 Script 来调用客户端上的函数,调用 InvokeClient() 方法在 RemoteFunction 上,但它有以下严重风险:

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

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

论据限制

当你发射一个 RemoteEvent 或调用一个 RemoteFunction 时,它会将你通过事件或调用回调函数传递的任何参数转发到前方。任何类型的 Roblox 对象,例如 Enum , Instance , 或其他对象,以及 Luau 类型,例如数字、字符串和布林,虽然你应该仔细探索以下限制,

非字符索引

如果传递的表中的任何 索引 是非字符串类型,例如 Instanceuserdata函数,Roblox 将自动将这些索引转换为字符串。

事件连接 - 本地脚本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onEventFire(passedTable)
for k, v in passedTable do
print(typeof(k)) --> 字符串
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)

通过的函数

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

事件连接 - 本地脚本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onClientEvent(func)
print(func) --> 零
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 = 弓
--> 角色名称 = 迪瓦龙杀手
--> 字符类 = 恶行
end
end
-- 将连接函数连接到事件
remoteEvent.OnServerEvent:Connect(onEventFire)
事件发射 - 本地脚本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- 数值索引表
local inventoryData = {
"Sword", "Bow"
}
-- 词典表
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)) --> 表:0x48eb7aead27563d9
return passedTable
end
-- 将函数设置为远程函数的回调
remoteFunction.OnServerInvoke = returnTable
事件调用 - 本地脚本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
local inventoryData = {
"Sword", "Bow"
}
-- 输出原始表身份
print(tostring(inventoryData)) --> 表:0x059bcdbb2b576549
local invokeReturn = remoteFunction:InvokeServer(inventoryData)
-- 返回传时输出表识别符
print(tostring(invokeReturn)) --> table: 0x9fcae7919563a0e9

元表

如果表有一个可转换字段,所有可转换信息在传输中丢失。在以下代码示例中,NumWheels 属性是 Car 可交换的一部分。当服务器收到以下表时,表 具有 属性,但不具有 属性。

事件连接 - 脚本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onEvent(player, param)
print(param) --> {["名称"] = "我的卡车"}
end
-- 将连接函数连接到事件
remoteEvent.OnServerEvent:Connect(onEvent)
事件发射 - 本地脚本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local Car = {}
Car.NumWheels = 4
Car.__index = Car
local truck = {}
truck.Name = "MyTruck"
setmetatable(truck, Car)
-- 包含可转换表的火焰事件
remoteEvent:FireServer(truck)

非复制实例

如果 RemoteEventRemoteFunction 传递一个只对发送者可见的值,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"
local storedPart = Instance.new("Part")
storedPart.Parent = ServerStorage
local function onPlayerAdded(player)
remoteEvent:FireClient(player, storedPart)
end
Players.PlayerAdded:Connect(onPlayerAdded)

同样,如果你在 LocalScript 中创建了一个部分,并尝试将其传给 Script ,服务器会看到 nil 因为部分不适合服务器。

事件发射 - 本地脚本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- 因为服务器不知道这部分,将被收到为“零”
local clientPart = Instance.new("Part")
clientPart.Parent = Workspace
remoteEvent:FireServer(clientPart)