roblox-luau について
Roblox のゲームスクリプトを Luau で書くためのリファレンススキルです。エンジン API、実行コンテキスト、クライアントとサーバー間の通信を扱い、Studio プロジェクトの実践パターンとチェック方法を示します。
主な機能
- 01Roblox サービスと Instance
- 02Script、LocalScript、ModuleScript の役割
- 03イベント処理と接続のクリーンアップ
- 04クライアント・サーバー間の Remotes と検証
- 05Studio テストと静的解析
活用シーン
- 01Roblox オブジェクトの作成と設定
- 02サーバーまたはクライアントの配置先選択
- 03イベント駆動のゲームロジック実装
- 04サーバーでのクライアント要求の検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ gamedev-skills/ awesome-gamedev-agent-skills --skill roblox-luauインストール後、Claude Code で/roblox-luauを入力して読み込まれたか確認します。
同梱ファイルreferences/client-server.md
詳細
Roblox Luau スクリプティング
Luau で Roblox のエクスペリエンスをスクリプト化します。サービス、Instance、イベント、サーバーとクライアントの分割、境界を越えた安全な通信を扱います。現在の Roblox エンジンと Studio を対象としています。
使用する場面
- Roblox スクリプトを作成するときに使用します。サービスの取得、インスタンスの作成と親への設定、イベントの接続、サーバーとクライアントのどちらで実行するかの判断、
RemoteEvent/RemoteFunction通信の配線などが該当します。 - プロジェクトに
Script/LocalScript/ModuleScriptオブジェクト、.rbxl(x)のプレース、または Rojo の*.project.jsonがあり、コードでgame:GetService(...)を呼び出している場合に使用します。
使用しない場面: セッションをまたぐデータの永続化 → roblox-datastores。リモートプロトコルのアーキテクチャ、不正行為への対策、レート制限、高頻度のレプリケーション、複数クライアントによる不正利用のテスト → roblox-networking。Roblox API に関係しない一般的な Lua の質問。エンジンに依存しない入力/セーブのアーキテクチャ → input-systems/save-systems。
基本的なワークフロー
game:GetService("Name")でサービスを取得します。 よく使うもの:Players、Workspace、ReplicatedStorage(クライアントとサーバーで共有)、ServerScriptService(サーバー専用コード)、ServerStorage、RunService、UserInputService(クライアント)。- コードがどこで実行されるかを把握します。
Scriptはサーバーで実行され、LocalScriptはクライアント(StarterPlayerScripts、StarterGui、またはプレイヤーのキャラクター内)で実行されます。ModuleScriptはrequireして使う共有コードです。 - 意図を持ってインスタンスを作成します。
local p = Instance.new("Part")を使い、プロパティを設定してから、p.Parentを最後に設定します(親に設定するとレプリケーションが発生します)。 - イベントに応じて処理します。
Players.PlayerAdded、part.Touched、RunService.Heartbeatなどのシグナルに:Connectで接続します。リークを防ぐため、不要になったら接続を解除します。 - Remotes でクライアントとサーバーの境界を越えます。ただし、クライアントを決して信用しないでください。 クライアントは
RemoteEvent:FireServer(...)でリクエストし、サーバーが検証して適用します。すべてのゲーム状態の正当な管理者はサーバーです。 - Studio でテストします。 Play/Play Here/サーバー+クライアントの Start を使い、Output ウィンドウとサーバー/クライアントの表示切り替えでコードの実行場所を確認します。
- プロジェクトにツールがすでにある場合は、Studio で確認する前に静的チェックを実行します。 ルートの
selene.tomlにstd = "roblox"を追加してから、selene .を実行します。 Rojo プロジェクトでは、次のコマンドでソースマップを一度書き出してから、rojo sourcemap default.project.json --output sourcemap.jsonを実行し、luau-lsp analyze --sourcemap=sourcemap.json srcを実行します。Roblox の型定義を--definitionsで渡してください(フラグの一覧はluau-lsp analyze --helpで確認できます)。Studio Script Sync プロジェクトにはソースマップがない場合があります。
パターン
1. サーバースクリプト:プレイヤーの参加に応じて処理する(leaderstats)
-- ServerScriptService/Leaderboard.server.luau (a Script = runs on the server)
local Players = game:GetService("Players")
local function onPlayerAdded(player: Player)
local stats = Instance.new("Folder")
stats.Name = "leaderstats" -- this name makes it show on the leaderboard
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = 0
coins.Parent = stats
stats.Parent = player -- parent LAST
end
Players.PlayerAdded:Connect(onPlayerAdded)
2. インスタンスを作成して設定する
local Workspace = game:GetService("Workspace")
local part = Instance.new("Part")
part.Size = Vector3.new(4, 1, 4)
part.Position = Vector3.new(0, 10, 0)
part.Anchored = true -- won't fall under gravity
part.BrickColor = BrickColor.new("Bright blue")
part.Parent = Workspace -- set Parent last so it replicates once, fully
3. イベントを接続する(リークを防ぐために接続を解除する)
local debounce = false
local connection
connection = part.Touched:Connect(function(hit: BasePart)
local character = hit.Parent
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not humanoid or debounce then return end
debounce = true
humanoid.Health -= 10
task.wait(1) -- task.wait, NOT the deprecated wait()
debounce = false
end)
-- Later, when the part is removed or the round ends:
-- connection:Disconnect()
4. RemoteEvent でクライアントからサーバーへ通信する(サーバーで検証する!)
-- ReplicatedStorage: create a RemoteEvent named "BuyItem" (in Studio or via code).
-- CLIENT (LocalScript): request a purchase. The client can lie — this is only a request.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local buyItem = ReplicatedStorage:WaitForChild("BuyItem") -- wait: may not have replicated yet
buyButton.MouseButton1Click:Connect(function()
buyItem:FireServer("sword") -- send the item id only; never the price/result
end)
-- SERVER (Script): the ONLY place the transaction is decided.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local buyItem = ReplicatedStorage:WaitForChild("BuyItem")
local PRICES = { sword = 100, shield = 75 }
buyItem.OnServerEvent:Connect(function(player: Player, itemId)
-- TRUST NOTHING from the client. Validate types and values.
if type(itemId) ~= "string" then return end
local price = PRICES[itemId]
if not price then return end -- unknown item
local coins = player.leaderstats.Coins
if coins.Value < price then return end -- can't afford
coins.Value -= price -- server applies the change
grantItem(player, itemId)
end)
5. RunService を使ったフレームごとのループ
local RunService = game:GetService("RunService")
-- Heartbeat fires every frame AFTER physics; dt is seconds since the last step.
RunService.Heartbeat:Connect(function(dt)
spinner.CFrame *= CFrame.Angles(0, math.rad(90) * dt, 0) -- 90deg/sec, frame-independent
end)
6. ModuleScript で共有コードを使う
-- ReplicatedStorage/GameConfig (a ModuleScript) — usable by server and client.
local GameConfig = {}
GameConfig.MaxHealth = 100
function GameConfig.damageFor(weapon: string): number
return ({ sword = 25, bow = 15 })[weapon] or 0
end
return GameConfig
local GameConfig = require(game:GetService("ReplicatedStorage"):WaitForChild("GameConfig"))
print(GameConfig.MaxHealth)
落とし穴
- クライアントを信頼することは悪用につながります → クライアントは
RemoteEvent/RemoteFunctionに任意の引数を送信できます。サーバーで引数の型と範囲をすべて検証し、体力、通貨、インベントリはサーバー側で権威を持って管理してください。 LocalScriptは配置した場所では実行されません → LocalScriptが実行されるのはStarterPlayerScripts、StarterCharacterScripts、StarterGui、またはツール内です。WorkspaceやServerScriptServiceでは実行されません。サーバーのScriptはServerScriptService/Workspaceに配置します。- 非推奨のグローバル関数 → 古い
wait()/spawn()/delay()(スケジューリングが劣り、スロットリングも発生します)ではなく、task.wait/task.spawn/task.delayを使ってください。 - プロパティを設定する前に親を設定する → インスタンスが最終状態で一度だけレプリケーションされるよう、プロパティを先に設定し、最後に
Parentを設定してください。 - 参加直後にクライアント側で
nilになる → オブジェクトは時間をかけてストリーミング/レプリケーションされます。クライアントで直接インデックスを参照するのではなく、parent:WaitForChild("Name")を使ってください。 - 接続を切断しないままにする → 長期間存続する
:Connectハンドラーはリークを引き起こし、破棄されたオブジェクトに対しても実行される可能性があります。接続を保存して:Disconnect()するか、適切な場合はInstance:GetAttributeChangedSignal/:Onceを使ってください。 RemoteEventで足りる場面でRemoteFunctionを使う →RemoteFunctionは戻り値を待つ間ブロックされるため、悪意のあるクライアントや応答の遅いクライアントによってサーバーが停滞する可能性があります。本当に応答が必要な場合を除き、一方向のRemoteEventを優先してください。- 古い型チェッカーを前提にする -> Luauの新しい型ソルバーは、2025年11月に一般公開(Studio Betaを終了)されました。
nocheck/非厳格モードの場所では自動的に有効になり、厳格モードの場所ではワークスペースプロパティUseNewLuauTypeSolverでオプトインします。また、LuauTypeCheckModeで場所のデフォルトのチェックモードを設定します。編集時にのみ実行され(実行時のコストはありません)、型の警告を振り分ける際は、古い動作ではなく、その場所で実際に有効になっているソルバーを基準にしてください。
参考資料
- クライアント/サーバーモデル全体(レプリケーション、
RemoteFunctionとRemoteEventの違い、:WaitForChildのタイミング、同じコンテキスト内でのメッセージングに使うBindableEvent、属性、CollectionServiceタグ、:Once/接続のクリーンアップ)については、references/client-server.mdを参照してください。
関連スキル
roblox-datastores— プレイヤーデータをセッション間で永続化します(サーバー専用)。roblox-networking— 本番環境向けのリモートコントラクト、サーバー検証、レート制限、レプリケーション、ストリーミング、予測、マルチクライアントテスト。save-systems— エンジンに依存しない永続化の概念。game-ai/input-systems— Luauで実装できる、移植性の高いAIと入力のパターン。
コメント
Markdown 対応