scenario-roblox-architecture について
Roblox のゲームプログラマーやアーキテクト向けのアーキテクチャスキルです。スクリプト配置、モジュール起動、サーバー状態管理、インスタンスのライフサイクルを扱い、テスト済みのアリーナコアと監査ツールも提供します。
主な機能
- 01サーバーとクライアントの配置設計
- 02二段階ローダーによるモジュール起動
- 03サーバー状態のクライアントへの反映
- 04タグ付きインスタンスへの動作設定
- 05接続とオブジェクトのライフサイクル管理
- 06アリーナコアの構築と監査
活用シーン
- 01Roblox プロジェクトの構成を作成
- 02ラウンド制ゲームのサービスとコントローラーを整理
- 03モジュール順序や依存関係の問題を診断
- 04リスポーン、マップ交代、接続解除を処理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenario-roblox-architectureインストール後、Claude Code で/scenario-roblox-architectureを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/critique.mdexpert-notes.mdgui-paths.mdprocedures.mdproject-setup.mdsources.mdscripts/rbx_architecture.pyscripts/agentkit/architecture/ArchAudit.luauBinder.luauLoader.luaulune_arch_audit.luaulune_place_facts.luauscripts/templates/arena/fixtures/FixtureSecondEntry.server.luauFixtureTwiceProbe.client.luauscripts/templates/arena/fixtures/first/FixtureLoadingModule.luauscripts/templates/arena/fixtures/FixtureDoor1/DoorScript.model.jsoninit.meta.jsonPanel.model.jsonscripts/templates/arena/fixtures/FixtureDoor2/DoorScript.model.jsoninit.meta.jsonPanel.model.jsonscripts/templates/arena/fixtures/gui/FixturePerLife.model.jsonscripts/templates/arena/fixtures/gui/FixtureScreens/FixtureNested.model.jsonscripts/templates/arena/fixtures/replicated/CharacterCache.luauCycleA.luauCycleB.luauFixtureRemote.model.jsonGlobalRegistry.luauLeakyClass.luauLeakyServerModule.luau詳細
Roblox のアーキテクチャ(ゲームプレイプログラマーおよびアーキテクト)
対象:scenario-roblox-expert ツールキットを通じて操作する、macOS 上の Roblox Studio(2026 年 10 月ビルド)。
Roblox におけるエキスパート向けアーキテクチャでは、配置場所と RunContext によって実行場所を決め、サイドごとに 1 つのエントリースクリプトですべてのモジュールを 2 段階で起動し、サーバーモジュールが真の状態を所有して属性に反映し、タグでインスタンスに動作をバインドして、すべての接続に所有者を設定します。このスキルでは、Studio 0.743 でのライブテストで修正したソースのルールに加え、テスト済みのアリーナのコア機能(ラウンドループ、チーム、マップ、HUD、ショップ、剣)と、それらを構築・監査するツールを扱います。ここで名前が挙がっている関連スキルが利用可能なスキルにない場合は、ユーザーにインストールを依頼してください(npx skills add scenario-labs/skills --skill <name>)。無人で進める場合は、ツールのスキーマに従い、不足している点を明示してください。
必須の背景知識: scenario-roblox-expert(チャンネル、レビューループ、ツールキット、最新の Roblox に関する落とし穴)。
ステータス(2026-10-19): Studio 0.743 でライブテスト 14 件が合格し、オフラインチェック 8 件も合格しました(Lune のユニットテスト 18 件、コード・フィクスチャ・スニペットに対する両方の型ソルバーでの luau-lsp、StyLua、selene)。
方針(エキスパート向けの追加事項)
-
配置場所と RunContext を固定します。サイドごとにエントリー Script を 1 つにします。 ServerScriptService には RunContext が Server の Script を 1 つ、ReplicatedStorage には RunContext が Client の Script を 1 つ使います(Roblox ドキュメント「Script locations」、Ludius, n8IDTRW1EfM [00:01:34]、Leif, F3ASTJuO82A [00:03:18])。それ以外はすべて ModuleScript にします。
- StarterPlayerScripts の Client Script はライブ環境で 2 回実行され、Output に「non-legacy RunContext」警告が表示されました。
- Rojo 7.7.1 は、プロジェクトで
"emitLegacyScripts": falseを設定しない限り、.client.luauを LocalScript にします(検証済み)。ReplicatedStorage 内の LocalScript は実行されません。
-
1 つのローダーを通して 2 段階で起動します。 GetDescendants の順序は未定義なので、ロード中に別のモジュールを呼び出してはいけません(Suphi Kaner, YsXijBUUmmM [00:04:22])。Init は自身の状態を設定し、yield しません。Start は yield でき、ほかのモジュールを呼び出せます(Leif [00:02:25]、ドキュメントのすべてのモジュールに対する
start())。Loader.luauは、ソースで扱われていない次の点を追加します[追加]。- 明示された順序に続けて、残りをパス順にソートします。名前が重複すると失敗します(Suphi [00:01:50])。
- モジュールごとに pcall を 1 回実行するため、1 つのサービスに問題があってもほかのサービスは停止しません。
- yield する Init はエラーとして報告されます。
- 凍結したレジストリと起動レポートを属性として書き込みます。
-
サーバーモジュールが真の状態を所有し、属性に反映します。UI は何も所有しません。 状態サービスが GUI を require することはありません。GUI はサービスを呼び出し、変更をリッスンします(MrAsync [00:25:46 to 00:31:11])。共有状態は、クライアントが監視する Configuration の属性として公開します(Crusherfire, 6Q_p2ObUXSo [00:07:06])。クライアントがリモートを使わずにカウントダウンできるよう、サーバーの絶対時刻(
EndsAt)を反映します[追加]。プロパティまたは属性の変更とリモートの間には順序の保証がありません(ドキュメント「Attributes」「Server Authority」)。そのため、状態をリモートのペイロードに含めるか、属性だけで公開して変更のたびに再描画します。 -
祖先でフィルタリングし、タグで動作をバインドします。 インスタンスごとにスクリプトを置く代わりに、1 つのモジュールでタグ付けされたすべてのインスタンスを処理します(B Ricey, Vi2FckNJv9w [00:02:44]、GnomeCode, UsPYgIW3rv8 [00:01:38]、ドキュメント)。ソースで見落とされている落とし穴は 2 つあります[追加]。
- ServerStorage 内のタグ付きテンプレートも GetTagged に含まれます。選択した祖先の下にある場合のみバインドしてください。
- タグ付きパーツを ServerStorage から Workspace に戻しても、InstanceAdded は発火しませんでした(ライブ環境)。AncestryChanged を監視してください。
Binder.luauはこの両方に対応し、GetTagged より前にシグナルを接続して、セットアップから返されるクリーンアップ処理を実行します。 -
すべての接続には、それとともに破棄される所有者を持たせます。 接続を破棄するオブジェクトにクリーンアップをひも付けます。たとえば、プレイヤーの Trove ではなく、個々の tycoon 用の Trove を使います(Crusherfire、0moy04A9vFg [00:16:38])。作成時にクリーンアップオブジェクトへ追加します(B Ricey、2Msrw5w5cTs [00:09:27])。Workspace.PlayerCharacterDestroyBehavior を Enabled に設定します(Crusherfire、YWyJHwynM1M [00:12:34])。Deferred シグナルでは Destroying ハンドラーは破棄の後に実行されます(Crusherfire、xz_G2n1Q8-U [00:12:45])。実行中の検証では、yield の前にハンドラーが実行されず、その後 Parent が nil になった状態で読み取られていました。削除前に状態を取得し、削除ハンドラーではテーブルのクリアだけを行います。別のハンドラー内で接続されたリスナーは、すでにキューに入っている発火を受け取れません。また、入れ子の発火は幅優先で実行されます(Crusherfire [00:03:19 to 00:08:52]; 実行中の検証)。すべてのリスナーを Init で接続します。
- 画面にもライフタイムがあります。スポーンのたびに StarterGui が PlayerGui に再コピーされますが、ResetOnSpawn が false の直接の子は除きます。Folder を挟むと false の設定は無効になります(ドキュメント、オンスクリーン UI コンテナー)。プレイヤーは毎ラウンド死亡するため、HUD とショップでは false を使い(GnomeCode、oEWoyT-GBZY [00:14:38])、一度だけ実行するコントローラーでは有効な参照を保持します(実行中の検証)。
-
サーバーコードとラウンドのコンテンツを、レプリケーションされるコンテナーに置いてはいけません。 エクスプロイターは ReplicatedStorage を読み取れます(Leif [00:01:00]; Crusherfire、6Q_p2ObUXSo [00:09:13])。マップは必要になるまで ServerStorage に置き、参加時の通信量を抑えます(ドキュメント、データモデル)。ラウンドごとにマップを 1 つ Workspace に複製し、終了後に破棄します(GnomeCode [00:11:03 to 00:13:51]。同氏はマップを ReplicatedStorage に保持しています)。Crusherfire は開始時に、サーバーモジュールをシステムフォルダーから ServerScriptService に移します(6Q_p2ObUXSo [00:09:47])。システムを移動できるよう、相対パスで兄弟モジュールを Require します(Crusherfire [00:23:46])。
-
クライアントが要求し、リモートはコードで作成します。 Explorer でリモートを手動で作成してはいけません(Leif、F3ASTJuO82A [00:04:28])。クライアントは「ここで建築してもよいですか」と要求し、サーバーが判断します(Leif [00:05:39])。帯域幅を節約するためクライアントでチェックし、さらにサーバーでもチェックします(MrAsync [00:16:37])。
-
好みではなく、チームに合わせてフレームワークを選びます。 Knit 形式のフレームワークでは選択肢が決められ、すべてが 1 つのサービスに依存します(B Ricey、MyK4o2pIpQw [00:17:58])。一方、2025 年のソースでは小規模なローダーが作られています(Ludius、Leif、Crusherfire)。個人または小規模チームの場合は、このローダー、Signal、クリーンアップモジュールを使います。既存の Knit コードベースがある場合は、そのまま使い、監査を追加します[追加]。クラスは、状態を持つインスタンスが複数ある場合にのみ使います(B Ricey [00:07:10])。サービスはシングルトンのテーブルです(Leif [00:02:00])。
最初に確認すること
- セッションの構成:ラウンド制、永続ワールド、ハブのいずれか。プレイヤー数、チーム。
- 新規プレースか既存コードか:Rojo または Script Sync のリポジトリ、Studio のみ、または既存のフレームワークを維持するか。
- チームの規模、プレース間でシステムを共有するかどうか(自作システムには Roblox Packages、サードパーティーコードには Wally)。
- サーバー権限(
AuthorityMode = Server):Deferred シグナルを強制し、インスタンスごとに最初の 64 個の属性のみをレプリケーションします。 - StreamingEnabled:新規プレースでは有効です(これらのテストでは無効)。クライアントはストリーミングされるパーツを待ちます。
デフォルト:emitLegacyScripts を false にした Rojo プロジェクト、以下のアリーナの骨組み、Deferred シグナル、PlayerCharacterDestroyBehavior Enabled、--!strict。
手順
セットアップ:import rbx_architecture as A(リード担当者のスクリプトを sys.path に追加します)。コード全文と記録済みの結果は references/procedures.md にあります。
-
スキャフォールドを作成します。
A.scaffold(dest, name, fixtures=False)はアリーナのスケルトンに Loader と Binder を追加してコピーし、Rojo プロジェクトを書き込み、rbx_env.new_placeを通じてプレースをビルドします。ゲート:A.place_facts(place)で Server と Client の RunContexts、SignalBehavior Deferred、PlayerCharacterDestroyBehavior Enabled が確認できること。A.check(dest)が両方の型ソルバーでエラー 0 件を返すこと。 -
モジュールをマッピングします。 コードを書く前にモジュールマップを作成します。依存関係はサービスから共有モジュールへ向け、サービスから UI へ、またはレプリケートされたコードからサーバーへ向けてはいけません。
モジュール 側 所有するもの PlayerService サーバー プレイヤーごと、キャラクターごとのテーブル。退出時にクリアします TeamService サーバー チーム割り当てと生存中のチーム RoundService サーバー RoundMachine の状態、 StateChanged、GameState ミラーWeaponService サーバー タグで管理する剣:付与、回収、リスポーン時の再付与 MapService サーバー ラウンドごとに 1 つのマップクローン、ローテーション、パッド、タグで管理するハザード DamageService, ShopService サーバー scenario-roblox-gameplay と scenario-roblox-data に引き継ぎます RoundMachine, Config, Signal 共有 純粋なロジック、定数、シグナル(Lune で実行) RoundController, ShopController クライアント HUD とショップ。一度だけ接続し、ResetOnSpawn false にします ゲート:
A.audit_file(place)がエラー 0 件、警告 0 件、require の循環なし、未解決の require 0 件を報告すること。 -
起動します。 各エントリースクリプトは
Loader.boot(Loader.ordered(folder, order), {publish = script})を呼び出します。ゲート: プレイテストでA.boot_report(pt)が、両側で BootOk true、BootErrors 0、BootCount 1 を示し、すべてのinit:がすべてのstart:より前に実行されること。コンソールに "required recursively" や[Loader]の行がないこと。 -
状態とミラーを確認します。 RoundService は
EndsAt、Winner、続いてRoundStateを書き込みます。コントローラーは属性が変わるたびに再描画します。GameState のTest_*属性は Studio でのみループを短縮します。ゲート: サーバーの状態、クライアントのミラー、HUD のテキストが一致すること。HUD をキャプチャし、image_checksを実行して画像を開くこと。 -
タグとライフタイムを管理します。
Binder.bind(tag, setup, {ancestors = ...})でタグ付きインスタンスをバインドします。PlayerRemoving 時にプレイヤーテーブルを、CharacterRemoving 時にキャラクターテーブルをクリアします。リスポーンすると Backpack が空になるため、CharacterAdded 時にラウンドアイテムを再付与します。MapService は Intermission 時にマップをクローンし、Lobby へ移る際に破棄します。ゲート: ラウンド中に 5 回リスポーンしても、追跡対象のキャラクターが 1 体、マップ上にバインドされた剣が 1 本だけ残ること。リスポーン後も HUD とショップが同じインスタンスであること。Lobby では Workspace 内のマップが 0 個、バインドされたハザードが 0 個で、インスタンス数が 10 以内まで戻り、マップの読み込みをまたいでもフレームがゲートを満たすこと。 -
ライブで観察して反復します。
rbx_env.place_slot()内でrbx_env.open_placeを使い、プレースを一度だけ開きます。編集のたびに開き直すのではなく、A.sync_sources(studio, dest)でソースをプッシュします。ゲート:A.run(s, "return ArchAudit.all(game)", kit=["ArchAudit"])の結果がオフラインの集計と一致し、tests/code/scenario-roblox-architecture/run_live.pyが終了コード 0 で終了すること。
数値
| 値 | 数値 | 比較対象 |
|---|---|---|
| プレイヤーのレイテンシー | 100~300 ms | docs、クライアントサーバーランタイム |
| Studio のプレイテスト時のレイテンシー | 0 ms。片道 50~150 ms をシミュレート | docs、Studio Settings、Network、または settings().Network |
| モジュールサイズの目安 | 500 行以上 | MrAsync [00:20:54]、ArchAudit module-size |
| Server Authority で複製される属性 | インスタンスごとに先頭の 64 個。名前と文字列は最大 50 文字 | docs、Server Authority |
| 起動時間の実測 | サーバー:5 個のサービスで 1.56~2.52 ms。クライアント:2 個のコントローラーで 0.40~1.08 ms | 実環境、Studio 0.743 |
| クライアントの実行順序の実測 | ReplicatedFirst はクライアントの Main より 35~36 ms 先に実行。IsLoaded は false の後 true。キャラクターはまだなし | 実環境 |
| ラウンド中に 5 回リスポーン | インスタンス数の増減は +3 または -3(約 46,150)。キャラクター 1 体、バインドされた剣 1 本 | 実環境、6 回実行 |
| アリーナのデフォルト値 | プレイヤー 2 人、インターミッション 15 秒、ラウンド 120 秒、終了 5 秒、ティック 0.25 秒 | [追加] |
| フレームゲート | 60 fps で p95 は最大 20 ms、最悪値は最大 33.3 ms(予算の 1.2 倍、2 倍) | scenario-roblox-expert |
| マップサイクルの実測 | サーバーの p95 は 17.3 ms、クローン全体の最悪値は 17.5~20.0 ms。Lobby のインスタンス差分は 0 | 実環境、3 回実行 |
品質ゲート
- 測定可能:
- ArchAudit:フィクスチャ外でエラー 0 件、警告 0 件。AgentKit の
Audit.all(game)はエラー 0 件です。 - 両側の起動レポート:BootOk、BootErrors 0、BootCount 1。
- コンソール:"required recursively"、"not a valid member"、
[Loader]、"non-legacy RunContext" の行がありません。 - ライフタイム:リスポーン後の
Charactersはプレイヤー数と一致します。Lobby ではマップ 0 個、バインドされたハザード 0 個で、インスタンス数は 10 以内に戻ります。サーバーとクライアントのフレームはゲートを満たします。 run_live.pyは 0 で終了し、Lune のユニットテストは合格します。luau-lsp は両方のソルバーでエラー 0 件(A.check)です。StyLua と selene に問題はありません。
- ArchAudit:フィクスチャ外でエラー 0 件、警告 0 件。AgentKit の
- **視覚的:**ラウンド中のキャプチャに、ミラーされた状態を示す HUD が表示されます。
image_checksは警告を出さず、画像を開いて確認します。人間のレビュアーが Explorer のキャプチャを取得します。
よくある間違い
| 間違い | 症状 | 修正 |
|---|---|---|
| StarterPlayerScripts 内のクライアントスクリプト | 2 回実行される。Studio の Output に警告が表示される | エントリ Script を ReplicatedStorage に移動するか、StarterPlayerScripts で LocalScript を使用します |
Rojo のデフォルトの emitLegacyScripts | ReplicatedStorage 内のクライアントエントリが実行されない | "emitLegacyScripts": false に設定します |
execute_luau から稼働中のサービスを require する | 新しいコピーではフィールドが nil になり、状態が空になる | モジュールの Studio 専用属性を読み取ります |
| ServerStorage 内のタグ付きテンプレート | テンプレートに動作が付与される | ancestors でフィルタリングします |
| DataModel 内での親の変更 | 戻した後に動作しなくなる | AncestryChanged を監視します(Binder) |
| ラウンドアイテムを一度だけ付与する | リスポーン後に剣がなくなる | ラウンド中、CharacterAdded で再付与します |
| Destroying または CharacterRemoving 内で Parent を読み取る | Deferred では nil になる | 破棄する前に状態を取得します |
| ハンドラー内でリスナーを接続する | すでにキューに入った発火を取り逃がす | 何かが発火する前に Init で接続します |
SetAttribute の後に FireAllClients を呼び出す。ハンドラーは属性を読み取る | ライブサーバーでは古い値を読み取る場合がある | ペイロードで状態を送信します |
| ReplicatedStorage または Workspace 内のマップ | 参加時にすべてのクライアントがすべてのマップをダウンロードする | ServerStorage を使用し、ラウンドごとに 1 つ複製します(maps-replicated) |
| ResetOnSpawn が true の ScreenGui を保持する一度だけ実行されるコントローラー | 最初の死亡後に HUD が消える | ResetOnSpawn を false にします |
| Folder 内の ScreenGui で ResetOnSpawn を false にする | それでもリスポーンのたびに再構築される | StarterGui の直下の子にします(reset-ignored-nested) |
_G レジストリ、重複したモジュール名 | 警告なしに上書きされる | Loader レジストリを使用します。ordered は重複時にエラーになります |
| AutoUpdate がオフのパッケージ | コピーが更新されない | AutoUpdate をオンにします(Roblox Learn、AzKZy2BqIh8 [00:09:55]) |
引き継ぎ
- 受け取るもの: scenario-roblox-expert(概要、ツールキット、place スロット)、scenario-3d および scenario-game-assets(ServerStorage にタグ付きテンプレートとして届くモデル)。
- 引き渡すもの: scenario-roblox-networking(サービス構成、remote モジュールスロット)、 scenario-roblox-data(DataService の起動スロット、PlayerRemoving フック)、scenario-roblox-gameplay(Binder のセットアップ内のヒットとダメージ)、scenario-roblox-ui(GameState 上のコントローラー、ショップ画面)、 scenario-roblox-luau(型、Signal、遅延イベント)、scenario-roblox-tooling(Rojo プロジェクト、CI)、 scenario-roblox-performance(リーク、ストリーミング)、scenario-roblox-animation(キャラクタースクリプト)。
- 形式: Rojo の
src/ツリー、モジュールマップ、ArchAudit レポート、results.json。
現在の Roblox に関するメモ
- Rojo 7.7.1 は Part の
"Position"を終了コード 0 で無視し、原点に配置したままにします。12 個の数値を含む"CFrame"を記述してください(検証済み)。 - Rojo 7.7.1 は Script Sync の
.local.luauと.legacy.luauを、X.localとX.legacyという名前の ModuleScript としてビルドします(検証済み)。2 種類のサフィックス方式を混在させないでください。 - 文字列による require:
./、../、@self、@game。Initモジュールの特別な処理は廃止されました (バージョン差分の23行目)。 - SignalBehavior:新しいテンプレートと Server Authority では Deferred が使われます。古い place では
Defaultが引き続き Immediate に対応します。execute_luauからworkspace.SignalBehaviorを参照すると「not a valid member」エラーが発生しました (実機確認済み)。Properties で確認するか、Lune を使ってファイルから読み取ってください。 - PlayerCharacterDestroyBehavior は NotScriptable です。Properties または Rojo プロジェクトで設定してください。
CollectionService:CreateCollectionはドキュメントに記載されていますが、Studio 0.743 では「Collections are not enabled」というエラーが発生しました(実機確認済み)。タグと Binder を使用してください。- Next Gen Studio UI がデフォルトです。メニューの場所については
references/gui-paths.mdを参照してください。 - Creator Store のアセットは
require(assetId)で読み込めません。コードをプロジェクトに含めてください。
参考資料
references/expert-notes.md:専門家による原則、タイムスタンプ、 および実機での修正内容。references/procedures.md:すべての手順と完全なコード、テスト、 記録された結果。references/critique.md:自己レビュー用ルーブリック。references/project-setup.md:クライアントの起動、StreamingEnabled、 Packages とチーム用ツール、2 クライアントでのテスト。references/gui-paths.md:同じ手順で使用する Studio のメニューとパネル。references/sources.md:すべての情報源と URL、推奨タイムスタンプ。scripts/rbx_architecture.py:スキャフォールド、監査、place の情報、同期、 起動レポート。scripts/agentkit/architecture/:Loader、Binder、ArchAudit、Lune ランナー。scripts/templates/arena/:テスト済みのアリーナコアと監査用フィクスチャ。