roblox-remote-events

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Roblox Remote Events & Functions

Roblox Remote Events & Functions

RemoteEvent vs RemoteFunction

RemoteEvent vs RemoteFunction

TypeDirectionReturns value?Use when
RemoteEvent
Any directionNo (fire-and-forget)Notifying server of player action, broadcasting state
RemoteFunction
Client→ServerYes (yields caller)Client needs a result back (e.g. fetch inventory)
UnreliableRemoteEvent
Any directionNoHigh-frequency updates where dropped packets are fine
Default to RemoteEvent. Avoid server→client
RemoteFunction
— an exploiter's frozen callback stalls your server thread indefinitely.

类型方向是否返回值?使用场景
RemoteEvent
任意方向否(即发即弃)通知服务器玩家操作、广播状态
RemoteFunction
客户端→服务器是(会阻塞调用方)客户端需要获取返回结果(例如:获取背包物品)
UnreliableRemoteEvent
任意方向允许丢包的高频更新场景
优先使用RemoteEvent。避免使用服务器→客户端的
RemoteFunction
——攻击者的冻结回调会无限期阻塞你的服务器线程。

Where to Put Remotes

远程对象的存放位置

Always store Remotes in
ReplicatedStorage
. Create them from a server Script that runs before any LocalScript.
ReplicatedStorage/
  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")

请始终将远程对象存放在
ReplicatedStorage
中。从服务器端Script创建它们,且该Script要早于任何LocalScript运行。
ReplicatedStorage/
  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)
end
lua
-- LocalScript
local inventory = GetInventory:InvokeServer()  -- yields until server returns
lua
-- Script (server)
GetInventory.OnServerInvoke = function(player)
    return getPlayerInventory(player.UserId)
end
lua
-- LocalScript
local inventory = GetInventory:InvokeServer()  -- yields until server returns

UnreliableRemoteEvent (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

攻击模式与防御手段

ExploitWhat the attacker doesDefense
Argument injectionSends unexpected types to crash handlerType-check all arguments
Damage amplificationSends
amount = math.huge
Clamp to sane maximum
Remote spamFires thousands of times per secondPer-player cooldown
Spoofed targetSends another player's UserIdServer resolves from its own state
Infinite yieldNever returns from
OnClientEvent
callback
Avoid server→client RemoteFunction
Duplicate actionReplays a valid fire to buy twiceCheck state / consume token before acting

攻击手段攻击者行为防御方法
参数注入发送异常类型数据以崩溃处理程序对所有参数进行类型检查
伤害放大发送
amount = math.huge
将数值限制在合理最大值内
远程调用 spam每秒触发数千次调用为每个玩家设置冷却时间
伪造目标发送其他玩家的UserId服务器从自身状态中解析目标
无限阻塞从不返回
OnClientEvent
回调
避免使用服务器→客户端的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 InvokeServer

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 InvokeServer

Common Mistakes

常见错误

MistakeFix
OnServerEvent
in a LocalScript
Use
OnClientEvent
on client;
OnServerEvent
is server-only
Remotes in ServerStorageMove to ReplicatedStorage
Trusting payload beyond player identityValidate every field in the payload
Server→client RemoteFunctionUse RemoteEvent; frozen client stalls server thread
No
WaitForChild
in LocalScript
Remotes may not exist yet; always use
WaitForChild
Multiple
OnServerInvoke
assignments
Only the last assignment wins; keep it in one place
Firing inside tight loop without throttleUse
UnreliableRemoteEvent
or accumulate delta time
错误修复方法
在LocalScript中使用
OnServerEvent
在客户端使用
OnClientEvent
OnServerEvent
仅适用于服务器端
将远程对象存放在ServerStorage移至ReplicatedStorage
信任玩家身份之外的负载数据验证负载中的每个字段
服务器→客户端的RemoteFunction使用RemoteEvent;冻结的客户端会阻塞服务器线程
LocalScript中未使用
WaitForChild
远程对象可能尚未加载;请始终使用
WaitForChild
多次赋值
OnServerInvoke
只有最后一次赋值生效;请将赋值放在同一位置
在无节流的紧密循环中触发调用使用
UnreliableRemoteEvent
或累积增量时间