mcpField
R
roblox-networking

roblox-networking

github.com/el4cteo開発61589 回インストール2 週間前に更新
リポジトリ

roblox-networking について

roblox-networkingは、Roblox開発者向けにクライアントとサーバー間の通信方式を選び、実装するためのガイドです。リモートの検証、シリアライズ、帯域管理、遅延への対処を説明します。

主な機能

  1. 01リモートと複製チャネルの選択
  2. 02型付きリモートの設定
  3. 03サーバー側でのリクエスト検証
  4. 04ネットワークのシリアライズ規則
  5. 05帯域とCPUの運用方法
  6. 06時刻と遅延への対処

活用シーン

  1. 01ゲームイベントの通信チャネル選択
  2. 02クライアントリクエストの検証
  3. 03ネットワーク通信量の削減
  4. 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。

コメント