원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.
Roblox networking
Pick the right channel
| Channel | Delivery | Use for |
|---|---|---|
RemoteEvent | Reliable, ordered | Requests (client→server), game events, UI updates. The default. |
UnreliableRemoteEvent | May drop or reorder; payload ≤ 1000 bytes (larger payloads are dropped) | Continuous, superseded-by-next data: aim direction, cosmetic VFX, positions of non-physics objects. |
RemoteFunction | Request/response, yields caller | Client→server queries that need an answer (e.g. "can I buy X?"). |
| Attributes / properties | Automatic replication server→client | Persistent state clients read (health, team, round state). |
| Physics replication | Automatic for owned assemblies | Moving parts; see roblox-physics. |
Never use RemoteFunction:InvokeClient: if the client errors, disconnects, or never returns, the server throws or yields forever. Use a RemoteEvent to the client and another back if you need a reply.
Defining remotes once (typed)
Create remotes on the server; clients wait for them. Centralize names so typos can't happen.
--!strict
-- ReplicatedStorage/Shared/Net.luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local FOLDER_NAME = "Remotes"
local IS_SERVER = RunService:IsServer()
local folder: Instance
if IS_SERVER then
folder = ReplicatedStorage:FindFirstChild(FOLDER_NAME) or Instance.new("Folder")
folder.Name = FOLDER_NAME
folder.Parent = ReplicatedStorage
else
folder = ReplicatedStorage:WaitForChild(FOLDER_NAME)
end
local function get<T>(className: string, name: string): T
if IS_SERVER then
local existing = folder:FindFirstChild(name)
if existing then
return existing :: any
end
local remote = Instance.new(className)
remote.Name = name
remote.Parent = folder
return remote :: any
end
return folder:WaitForChild(name) :: any
end
return {
RequestPurchase = get("RemoteEvent", "RequestPurchase") :: RemoteEvent,
InventoryChanged = get("RemoteEvent", "InventoryChanged") :: RemoteEvent,
AimDirection = get("UnreliableRemoteEvent", "AimDirection") :: UnreliableRemoteEvent,
GetShopStock = get("RemoteFunction", "GetShopStock") :: RemoteFunction,
}Server handler template
Every OnServerEvent/OnServerInvoke handler: rate limit → type-check every argument → check game rules → act. The first parameter is always the real Player (Roblox fills it in; it can't be forged).
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Net = require(ReplicatedStorage.Shared.Net)
local ITEM_PRICES: { [string]: number } = { Sword = 100, Shield = 150 }
local MIN_INTERVAL = 0.25
local lastRequest: { [Player]: number } = {}
Net.RequestPurchase.OnServerEvent:Connect(function(player: Player, itemId: unknown)
local now = os.clock()
if now - (lastRequest[player] or 0) < MIN_INTERVAL then
return -- rate limited
end
lastRequest[player] = now
if typeof(itemId) ~= "string" or #itemId > 64 then
return -- malformed: never trust argument types
end
local price = ITEM_PRICES[itemId]
if not price then
return -- unknown item
end
-- check the player's server-side balance, deduct, grant, then notify:
Net.InventoryChanged:FireClient(player, { added = itemId })
end)
game:GetService("Players").PlayerRemoving:Connect(function(player)
lastRequest[player] = nil -- avoid leaking Player keys
end)Full validation toolkit (NaN/inf checks, instance ownership, distance and cooldown checks): roblox-security.
What survives the wire
- Tables are copied: identity and metatables are lost; functions become
nil. - Don't mix array and dictionary keys in one table; don't put
nilholes in arrays. - Non-string dictionary keys (Instances, numbers in sparse tables) are converted to strings — send arrays of pairs instead.
- Instances arrive as
nilif the receiver can't see them (e.g.ServerStorageitems, or parts not
yet streamed in on the client).
- Supported:
nil, booleans, numbers, strings, tables,buffer, Roblox datatypes (Vector3,CFrame,
Color3, EnumItem, ...), and replicated Instance references.
- Server→client ordering vs. property replication is not guaranteed: a remote may arrive before or
after a property change it relates to.
Bandwidth and CPU
- Send changes, not full state (item added, not the whole inventory). Send on change, not per frame.
- Throttle input-driven sends on the client (e.g. aim at most 20 Hz) and still rate-limit on the server.
- Send IDs/enums as small numbers; quantize floats; pack hot, high-frequency data into a
buffer. - Batch many small messages into one per frame (queue in a table, flush on
Heartbeat). - Server-side
TweenServicereplicates every frame and looks jittery: tween on clients instead. - VFX: the server decides the outcome and fires a minimal event; each client spawns effects locally.
- Profile with the MicroProfiler (
ProcessPackets,Allocate Bandwidth and Run Senders) and the
Developer Console Network tab.
--!strict
-- Pack a hit notification into 11 bytes instead of a table with string keys.
local function encodeHit(targetId: number, damage: number, position: Vector3): buffer
local b = buffer.create(11)
buffer.writeu32(b, 0, targetId)
buffer.writeu16(b, 4, math.clamp(math.round(damage), 0, 65535))
buffer.writei16(b, 6, math.clamp(math.round(position.X), -32768, 32767))
buffer.writei16(b, 8, math.clamp(math.round(position.Z), -32768, 32767))
buffer.writeu8(b, 10, math.clamp(math.round(position.Y / 4), 0, 255))
return b
end
local function decodeHit(b: buffer): (number, number, Vector3)
return buffer.readu32(b, 0),
buffer.readu16(b, 4),
Vector3.new(buffer.readi16(b, 6), buffer.readu8(b, 10) * 4, buffer.readi16(b, 8))
end
print(decodeHit(encodeHit(42, 25, Vector3.new(10, 20, 30))))For many remotes with hot paths, schema-driven generators (Zap, Blink) produce typed, validated, buffer-packed remotes automatically. Adopt them when profiling shows network cost.
Time and latency
workspace:GetServerTimeNow()is a synchronized clock on server and clients: send a start timestamp
and let clients compute progress locally (round timers, cooldown bars, projectiles).
player:GetNetworkPing()returns round-trip network latency in seconds (clients may only query
LocalPlayer); use it to size lag-compensation windows.
- Don't send countdown ticks every second; send the end time once.
More patterns (request/response with timeouts, state snapshots + deltas, per-frame batching, client prediction): references/patterns.md.
Related skills
roblox-security (validation, anti-exploit), roblox-architecture (where remotes live), roblox-physics (network ownership, server authority), roblox-performance.

