mcpField
R
roblox-networking

roblox-networking

github.com/el4cteoDevelopment61589 installsUpdated 2w ago
Repository

About roblox-networking

roblox-networking is a technical guide for Roblox developers choosing and implementing communication between clients and servers. It explains remote selection, validation, serialization, bandwidth management, and latency patterns.

Key features

  1. 01Choosing remote and replication channels
  2. 02Typed remote setup
  3. 03Server-side request validation
  4. 04Network serialization rules
  5. 05Bandwidth and CPU practices
  6. 06Time and latency handling

Use cases

  1. 01Selecting a channel for game events
  2. 02Validating client requests
  3. 03Reducing network traffic
  4. 04Handling timers and latency-sensitive features

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/el4cteo/roblox-skills --skill roblox-networking

After installing, type/roblox-networkingin Claude Code to confirm it loaded.

Bundled filesreferences/patterns.md

Details

From the project README

Roblox networking

Pick the right channel

ChannelDeliveryUse for
RemoteEventReliable, orderedRequests (client→server), game events, UI updates. The default.
UnreliableRemoteEventMay drop or reorder; payload ≤ 1000 bytes (larger payloads are dropped)Continuous, superseded-by-next data: aim direction, cosmetic VFX, positions of non-physics objects.
RemoteFunctionRequest/response, yields callerClient→server queries that need an answer (e.g. "can I buy X?").
Attributes / propertiesAutomatic replication server→clientPersistent state clients read (health, team, round state).
Physics replicationAutomatic for owned assembliesMoving 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 nil holes in arrays.
  • Non-string dictionary keys (Instances, numbers in sparse tables) are converted to strings — send arrays of pairs instead.
  • Instances arrive as nil if the receiver can't see them (e.g. ServerStorage items, 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 TweenService replicates 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.

Comments

Markdown supported