roblox-remote-events
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRoblox Remote Events & Functions
Roblox Remote Events & Functions
RemoteEvent vs RemoteFunction
RemoteEvent vs RemoteFunction
| Type | Direction | Returns value? | Use when |
|---|---|---|---|
| Any direction | No (fire-and-forget) | Notifying server of player action, broadcasting state |
| Client→Server | Yes (yields caller) | Client needs a result back (e.g. fetch inventory) |
| Any direction | No | High-frequency updates where dropped packets are fine |
Default to RemoteEvent. Avoid server→client — an exploiter's frozen callback stalls your server thread indefinitely.
RemoteFunction| 类型 | 方向 | 是否返回值? | 使用场景 |
|---|---|---|---|
| 任意方向 | 否(即发即弃) | 通知服务器玩家操作、广播状态 |
| 客户端→服务器 | 是(会阻塞调用方) | 客户端需要获取返回结果(例如:获取背包物品) |
| 任意方向 | 否 | 允许丢包的高频更新场景 |
优先使用RemoteEvent。避免使用服务器→客户端的——攻击者的冻结回调会无限期阻塞你的服务器线程。
RemoteFunctionWhere to Put Remotes
远程对象的存放位置
Always store Remotes in . Create them from a server Script that runs before any LocalScript.
ReplicatedStorageReplicatedStorage/
Remotes/
DealDamage (RemoteEvent)
GetInventory (RemoteFunction)
SyncPosition (UnreliableRemoteEvent)lua
-- Script in ServerScriptService
local folder = Instance.new("Folder")
folder.Name = "Remotes"
folder.Parent = game:GetService("ReplicatedStorage")
local function make(class, name)
local r = Instance.new(class)
r.Name = name
r.Parent = folder
return r
end
make("RemoteEvent", "DealDamage")
make("RemoteFunction", "GetInventory")
make("UnreliableRemoteEvent", "SyncPosition")请始终将远程对象存放在中。从服务器端Script创建它们,且该Script要早于任何LocalScript运行。
ReplicatedStorageReplicatedStorage/
Remotes/
DealDamage (RemoteEvent)
GetInventory (RemoteFunction)
SyncPosition (UnreliableRemoteEvent)lua
-- Script in ServerScriptService
local folder = Instance.new("Folder")
folder.Name = "Remotes"
folder.Parent = game:GetService("ReplicatedStorage")
local function make(class, name)
local r = Instance.new(class)
r.Name = name
r.Parent = folder
return r
end
make("RemoteEvent", "DealDamage")
make("RemoteFunction", "GetInventory")
make("UnreliableRemoteEvent", "SyncPosition")Firing Patterns
触发模式
Client → Server (FireServer)
客户端→服务器(FireServer)
lua
-- LocalScript
local DealDamage = game:GetService("ReplicatedStorage").Remotes:WaitForChild("DealDamage")
DealDamage:FireServer({ targetId = 12345, amount = 50 })
-- First arg on server is always the firing Player (injected automatically, cannot be spoofed)lua
-- Script (server) — VALIDATE everything in the payload
DealDamage.OnServerEvent:Connect(function(player, data)
-- player identity is trustworthy; data contents are not
end)lua
-- LocalScript
local DealDamage = game:GetService("ReplicatedStorage").Remotes:WaitForChild("DealDamage")
DealDamage:FireServer({ targetId = 12345, amount = 50 })
-- First arg on server is always the firing Player (injected automatically, cannot be spoofed)lua
-- Script (server) — VALIDATE everything in the payload
DealDamage.OnServerEvent:Connect(function(player, data)
-- player identity is trustworthy; data contents are not
end)Server → One Client
服务器→单个客户端
lua
local Notify = game:GetService("ReplicatedStorage").Remotes:WaitForChild("Notify")
Notify:FireClient(player, { message = "Welcome!" })lua
-- LocalScript
Notify.OnClientEvent:Connect(function(data)
print(data.message)
end)lua
local Notify = game:GetService("ReplicatedStorage").Remotes:WaitForChild("Notify")
Notify:FireClient(player, { message = "Welcome!" })lua
-- LocalScript
Notify.OnClientEvent:Connect(function(data)
print(data.message)
end)Server → All Clients
服务器→所有客户端
lua
AnnounceEvent:FireAllClients({ text = "Game starting in 10 seconds!" })lua
AnnounceEvent:FireAllClients({ text = "Game starting in 10 seconds!" })RemoteFunction (Client Calls, Server Returns)
RemoteFunction(客户端调用,服务器返回)
lua
-- Script (server)
GetInventory.OnServerInvoke = function(player)
return getPlayerInventory(player.UserId)
endlua
-- LocalScript
local inventory = GetInventory:InvokeServer() -- yields until server returnslua
-- Script (server)
GetInventory.OnServerInvoke = function(player)
return getPlayerInventory(player.UserId)
endlua
-- LocalScript
local inventory = GetInventory:InvokeServer() -- yields until server returnsUnreliableRemoteEvent (High-Frequency Sync)
UnreliableRemoteEvent(高频同步)
lua
-- LocalScript
RunService.Heartbeat:Connect(function()
SyncPosition:FireServer(character.HumanoidRootPart.CFrame)
end)lua
-- Script (server) — still validate
SyncPosition.OnServerEvent:Connect(function(player, cframe)
if typeof(cframe) ~= "CFrame" then return end
-- apply with sanity bounds check
end)lua
-- LocalScript
RunService.Heartbeat:Connect(function()
SyncPosition:FireServer(character.HumanoidRootPart.CFrame)
end)lua
-- Script (server) — still validate
SyncPosition.OnServerEvent:Connect(function(player, cframe)
if typeof(cframe) ~= "CFrame" then return end
-- apply with sanity bounds check
end)CRITICAL: Server-Side Security
重中之重:服务器端安全
The client is hostile. Treat every argument as untrusted input.
lua
local MAX_DAMAGE = 100
local COOLDOWNS = {}
local COOLDOWN_SECONDS = 0.5
DealDamage.OnServerEvent:Connect(function(player, data)
-- 1. Rate limit
local now = tick()
if COOLDOWNS[player.UserId] and now - COOLDOWNS[player.UserId] < COOLDOWN_SECONDS then
return
end
COOLDOWNS[player.UserId] = now
-- 2. Type checks
if type(data) ~= "table" then return end
if type(data.targetId) ~= "number" then return end
if type(data.amount) ~= "number" then return end
-- 3. Range clamp
local amount = math.clamp(data.amount, 0, MAX_DAMAGE)
-- 4. Server-side weapon lookup — never trust client-provided Instance
local weapon = getEquippedWeapon(player)
if not weapon then return end
-- 5. Server-side target lookup
local target = getPlayerByUserId(data.targetId)
if not target then return end
applyDamage(target, amount, player)
end)客户端是不可信的。请将所有参数视为未受信任的输入。
lua
local MAX_DAMAGE = 100
local COOLDOWNS = {}
local COOLDOWN_SECONDS = 0.5
DealDamage.OnServerEvent:Connect(function(player, data)
-- 1. Rate limit
local now = tick()
if COOLDOWNS[player.UserId] and now - COOLDOWNS[player.UserId] < COOLDOWN_SECONDS then
return
end
COOLDOWNS[player.UserId] = now
-- 2. Type checks
if type(data) ~= "table" then return end
if type(data.targetId) ~= "number" then return end
if type(data.amount) ~= "number" then return end
-- 3. Range clamp
local amount = math.clamp(data.amount, 0, MAX_DAMAGE)
-- 4. Server-side weapon lookup — never trust client-provided Instance
local weapon = getEquippedWeapon(player)
if not weapon then return end
-- 5. Server-side target lookup
local target = getPlayerByUserId(data.targetId)
if not target then return end
applyDamage(target, amount, player)
end)Exploit Patterns & Defenses
攻击模式与防御手段
| Exploit | What the attacker does | Defense |
|---|---|---|
| Argument injection | Sends unexpected types to crash handler | Type-check all arguments |
| Damage amplification | Sends | Clamp to sane maximum |
| Remote spam | Fires thousands of times per second | Per-player cooldown |
| Spoofed target | Sends another player's UserId | Server resolves from its own state |
| Infinite yield | Never returns from | Avoid server→client RemoteFunction |
| Duplicate action | Replays a valid fire to buy twice | Check state / consume token before acting |
| 攻击手段 | 攻击者行为 | 防御方法 |
|---|---|---|
| 参数注入 | 发送异常类型数据以崩溃处理程序 | 对所有参数进行类型检查 |
| 伤害放大 | 发送 | 将数值限制在合理最大值内 |
| 远程调用 spam | 每秒触发数千次调用 | 为每个玩家设置冷却时间 |
| 伪造目标 | 发送其他玩家的UserId | 服务器从自身状态中解析目标 |
| 无限阻塞 | 从不返回 | 避免使用服务器→客户端的RemoteFunction |
| 重复操作 | 重放合法调用以重复执行(如重复购买) | 执行操作前检查状态/消耗令牌 |
Quick Reference
速查指南
FireServer(args) LocalScript → server
FireClient(player, args) server → one client
FireAllClients(args) server → every client
InvokeServer(args) LocalScript → server, waits for return
OnServerEvent server-side listener for FireServer
OnClientEvent client-side listener for FireClient/FireAllClients
OnServerInvoke server-side function assigned for InvokeServerFireServer(args) LocalScript → server
FireClient(player, args) server → one client
FireAllClients(args) server → every client
InvokeServer(args) LocalScript → server, waits for return
OnServerEvent server-side listener for FireServer
OnClientEvent client-side listener for FireClient/FireAllClients
OnServerInvoke server-side function assigned for InvokeServerCommon Mistakes
常见错误
| Mistake | Fix |
|---|---|
| Use |
| Remotes in ServerStorage | Move to ReplicatedStorage |
| Trusting payload beyond player identity | Validate every field in the payload |
| Server→client RemoteFunction | Use RemoteEvent; frozen client stalls server thread |
No | Remotes may not exist yet; always use |
Multiple | Only the last assignment wins; keep it in one place |
| Firing inside tight loop without throttle | Use |
| 错误 | 修复方法 |
|---|---|
在LocalScript中使用 | 在客户端使用 |
| 将远程对象存放在ServerStorage | 移至ReplicatedStorage |
| 信任玩家身份之外的负载数据 | 验证负载中的每个字段 |
| 服务器→客户端的RemoteFunction | 使用RemoteEvent;冻结的客户端会阻塞服务器线程 |
LocalScript中未使用 | 远程对象可能尚未加载;请始终使用 |
多次赋值 | 只有最后一次赋值生效;请将赋值放在同一位置 |
| 在无节流的紧密循环中触发调用 | 使用 |