roblox-networking について
roblox-networkingは、Roblox開発者向けにクライアントとサーバー間の通信方式を選び、実装するためのガイドです。リモートの検証、シリアライズ、帯域管理、遅延への対処を説明します。
主な機能
- 01リモートと複製チャネルの選択
- 02型付きリモートの設定
- 03サーバー側でのリクエスト検証
- 04ネットワークのシリアライズ規則
- 05帯域とCPUの運用方法
- 06時刻と遅延への対処
活用シーン
- 01ゲームイベントの通信チャネル選択
- 02クライアントリクエストの検証
- 03ネットワーク通信量の削減
- 04タイマーや遅延に敏感な機能の処理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ el4cteo/ roblox-skills --skill roblox-networkingインストール後、Claude Code で/roblox-networkingを入力して読み込まれたか確認します。
同梱ファイルreferences/patterns.md
詳細
Roblox ネットワーキング
適切なチャネルを選ぶ
| チャネル | 配信 | 用途 |
|---|---|---|
RemoteEvent | 信頼性あり、順序保証 | リクエスト(クライアント→サーバー)、ゲームイベント、UI 更新。デフォルトの選択肢です。 |
UnreliableRemoteEvent | ドロップや順序入れ替えの可能性あり、ペイロード ≤ 1000 バイト(それより大きいペイロードはドロップされます) | 連続的で、次のデータによって上書きされるもの: エイム方向、装飾用 VFX、非物理オブジェクトの位置。 |
RemoteFunction | リクエスト/レスポンス、呼び出し元を中断 | 回答が必要なクライアント→サーバーの問い合わせ(例:「X を購入できますか?」)。 |
| アトリビュート / プロパティ | サーバー→クライアントへ自動レプリケーション | クライアントが読み取る永続的な状態(体力、チーム、ラウンド状態)。 |
| 物理レプリケーション | 所有権を持つアセンブリに対して自動 | 可動パーツ。roblox-physics を参照してください。 |
RemoteFunction:InvokeClient は決して使用しないでください。クライアントがエラーを起こすか、切断するか、あるいは永遠に戻らない場合、サーバーは
例外を投げるか、永遠に中断します。返信が必要な場合は、クライアントへの RemoteEvent と、返信用のもう一つの RemoteEvent を使用してください。
リモートを一度だけ(型付きで)定義する
リモートはサーバー上で作成し、クライアントはそれを待ちます。名前を一元化すれば、タイプミスは起こりません。
--!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,
}
サーバーハンドラーのテンプレート
すべての OnServerEvent/OnServerInvoke ハンドラーは、レート制限 → すべての引数の型チェック →
ゲームルールの確認 → 実行 の順で行います。最初のパラメーターは常に実際の Player です(Roblox が設定するため、偽造できません)。
--!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)
完全なバリデーションツールキット(NaN/inf チェック、インスタンスの所有権、距離とクールダウンのチェック): roblox-security。
通信路を生き残るもの
- テーブルはコピーされます。同一性とメタテーブルは失われ、関数は
nilになります。 - 1 つのテーブルに配列キーと辞書キーを混在させないでください。配列に
nilの穴を入れないでください。 - 文字列以外の辞書キー(インスタンス、スパーステーブル内の数値)は文字列に変換されます。代わりにペアの配列を送信してください。
- インスタンスは、受信側から見えない場合(例:
ServerStorageのアイテム、またはクライアント上でまだストリーミングされていないパーツ)、nilとして届きます。 - サポート対象:
nil、ブール値、数値、文字列、テーブル、buffer、Roblox のデータ型(Vector3、CFrame、Color3、EnumItemなど)、およびレプリケートされたInstance参照。 - サーバー→クライアントの順序とプロパティのレプリケーションの順序は保証されません。リモートは、関連するプロパティ変更の前後に届く可能性があります。
帯域幅と CPU
- 完全な状態ではなく、変更を送信してください(インベントリ全体ではなく、追加されたアイテム)。毎フレームではなく、変更時に送信してください。
- クライアントでは入力駆動の送信をスロットルし(例: エイムは最大 20 Hz)、サーバー側でもレート制限を行ってください。
- ID/列挙型は小さな数値として送信し、浮動小数点数は量子化し、ホットで高頻度なデータは
bufferにパックしてください。 - 多数の小さなメッセージを 1 フレームに 1 つにまとめてください(テーブルにキューイングし、
Heartbeatでフラッシュします)。 - サーバー側の
TweenServiceは毎フレームレプリケートされ、ぎこちなく見えます。代わりにクライアントでトゥイーンしてください。 - VFX: サーバーが結果を決定して最小限のイベントを発火し、各クライアントがローカルでエフェクトを生成します。
- MicroProfiler(
ProcessPackets、Allocate Bandwidth and Run Senders)と Developer Console の Network タブでプロファイリングしてください。
--!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))))
ホットパスを持つ多数のリモートには、スキーマ駆動のジェネレーター(Zap、Blink)が型付きで検証済みの、バッファにパックされたリモートを自動生成します。プロファイリングでネットワークコストが示された場合に採用してください。
時間とレイテンシ
workspace:GetServerTimeNow()はサーバーとクライアントで同期されたクロックです。開始タイムスタンプを送信し、クライアントに進行状況をローカルで計算させてください(ラウンドタイマー、クールダウンバー、発射物)。player:GetNetworkPing()は往復のネットワークレイテンシを秒単位で返します(クライアントはLocalPlayerのみ照会できます)。ラグ補償のウィンドウのサイズ決定に使用してください。- カウントダウンのティックを毎秒送信しないでください。終了時刻を一度だけ送信してください。
その他のパターン(タイムアウト付きリクエスト/レスポンス、状態スナップショット + デルタ、フレームごとのバッチ処理、クライアント予測): references/patterns.md。
関連スキル
roblox-security(バリデーション、アンチエクスプロイト)、roblox-architecture(リモートの配置場所)、
roblox-physics(ネットワーク所有権、サーバー権限)、roblox-performance。