mcpField
S
scenario-roblox-architecture

scenario-roblox-architecture

github.com/scenario-labs開発1.2k55 回インストール1 日前に更新
リポジトリ

scenario-roblox-architecture について

Roblox のゲームプログラマーやアーキテクト向けのアーキテクチャスキルです。スクリプト配置、モジュール起動、サーバー状態管理、インスタンスのライフサイクルを扱い、テスト済みのアリーナコアと監査ツールも提供します。

主な機能

  1. 01サーバーとクライアントの配置設計
  2. 02二段階ローダーによるモジュール起動
  3. 03サーバー状態のクライアントへの反映
  4. 04タグ付きインスタンスへの動作設定
  5. 05接続とオブジェクトのライフサイクル管理
  6. 06アリーナコアの構築と監査

活用シーン

  1. 01Roblox プロジェクトの構成を作成
  2. 02ラウンド制ゲームのサービスとコントローラーを整理
  3. 03モジュール順序や依存関係の問題を診断
  4. 04リスポーン、マップ交代、接続解除を処理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/scenario-labs/skills --skill scenario-roblox-architecture

インストール後、Claude Code で/scenario-roblox-architectureを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
critique.md
expert-notes.md
gui-paths.md
procedures.md
project-setup.md
sources.md
scripts/
rbx_architecture.py
scripts/agentkit/architecture/
ArchAudit.luau
Binder.luau
Loader.luau
lune_arch_audit.luau
lune_place_facts.luau
scripts/templates/arena/fixtures/
FixtureSecondEntry.server.luau
FixtureTwiceProbe.client.luau
scripts/templates/arena/fixtures/first/
FixtureLoadingModule.luau
scripts/templates/arena/fixtures/FixtureDoor1/
DoorScript.model.json
init.meta.json
Panel.model.json
scripts/templates/arena/fixtures/FixtureDoor2/
DoorScript.model.json
init.meta.json
Panel.model.json
scripts/templates/arena/fixtures/gui/
FixturePerLife.model.json
scripts/templates/arena/fixtures/gui/FixtureScreens/
FixtureNested.model.json
scripts/templates/arena/fixtures/replicated/
CharacterCache.luau
CycleA.luau
CycleB.luau
FixtureRemote.model.json
GlobalRegistry.luau
LeakyClass.luau
LeakyServerModule.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)。

方針(エキスパート向けの追加事項)

  1. 配置場所と 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 は実行されません。
  2. 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 はエラーとして報告されます。
    • 凍結したレジストリと起動レポートを属性として書き込みます。
  3. サーバーモジュールが真の状態を所有し、属性に反映します。UI は何も所有しません。 状態サービスが GUI を require することはありません。GUI はサービスを呼び出し、変更をリッスンします(MrAsync [00:25:46 to 00:31:11])。共有状態は、クライアントが監視する Configuration の属性として公開します(Crusherfire, 6Q_p2ObUXSo [00:07:06])。クライアントがリモートを使わずにカウントダウンできるよう、サーバーの絶対時刻(EndsAt)を反映します[追加]。プロパティまたは属性の変更とリモートの間には順序の保証がありません(ドキュメント「Attributes」「Server Authority」)。そのため、状態をリモートのペイロードに含めるか、属性だけで公開して変更のたびに再描画します。

  4. 祖先でフィルタリングし、タグで動作をバインドします。 インスタンスごとにスクリプトを置く代わりに、1 つのモジュールでタグ付けされたすべてのインスタンスを処理します(B Ricey, Vi2FckNJv9w [00:02:44]、GnomeCode, UsPYgIW3rv8 [00:01:38]、ドキュメント)。ソースで見落とされている落とし穴は 2 つあります[追加]。

    • ServerStorage 内のタグ付きテンプレートも GetTagged に含まれます。選択した祖先の下にある場合のみバインドしてください。
    • タグ付きパーツを ServerStorage から Workspace に戻しても、InstanceAdded は発火しませんでした(ライブ環境)。AncestryChanged を監視してください。

    Binder.luau はこの両方に対応し、GetTagged より前にシグナルを接続して、セットアップから返されるクリーンアップ処理を実行します。

  5. すべての接続には、それとともに破棄される所有者を持たせます。 接続を破棄するオブジェクトにクリーンアップをひも付けます。たとえば、プレイヤーの 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])、一度だけ実行するコントローラーでは有効な参照を保持します(実行中の検証)。
  6. サーバーコードとラウンドのコンテンツを、レプリケーションされるコンテナーに置いてはいけません。 エクスプロイターは 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])。

  7. クライアントが要求し、リモートはコードで作成します。 Explorer でリモートを手動で作成してはいけません(Leif、F3ASTJuO82A [00:04:28])。クライアントは「ここで建築してもよいですか」と要求し、サーバーが判断します(Leif [00:05:39])。帯域幅を節約するためクライアントでチェックし、さらにサーバーでもチェックします(MrAsync [00:16:37])。

  8. 好みではなく、チームに合わせてフレームワークを選びます。 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 にあります。

  1. スキャフォールドを作成します。 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 件を返すこと。

  2. モジュールをマッピングします。 コードを書く前にモジュールマップを作成します。依存関係はサービスから共有モジュールへ向け、サービスから 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 件を報告すること。

  3. 起動します。 各エントリースクリプトは Loader.boot(Loader.ordered(folder, order), {publish = script}) を呼び出します。ゲート: プレイテストで A.boot_report(pt) が、両側で BootOk true、BootErrors 0、BootCount 1 を示し、すべての init: がすべての start: より前に実行されること。コンソールに "required recursively" や [Loader] の行がないこと。

  4. 状態とミラーを確認します。 RoundService は EndsAt、Winner、続いて RoundState を書き込みます。コントローラーは属性が変わるたびに再描画します。GameState の Test_* 属性は Studio でのみループを短縮します。ゲート: サーバーの状態、クライアントのミラー、HUD のテキストが一致すること。HUD をキャプチャし、image_checks を実行して画像を開くこと。

  5. タグとライフタイムを管理します。 Binder.bind(tag, setup, {ancestors = ...}) でタグ付きインスタンスをバインドします。PlayerRemoving 時にプレイヤーテーブルを、CharacterRemoving 時にキャラクターテーブルをクリアします。リスポーンすると Backpack が空になるため、CharacterAdded 時にラウンドアイテムを再付与します。MapService は Intermission 時にマップをクローンし、Lobby へ移る際に破棄します。ゲート: ラウンド中に 5 回リスポーンしても、追跡対象のキャラクターが 1 体、マップ上にバインドされた剣が 1 本だけ残ること。リスポーン後も HUD とショップが同じインスタンスであること。Lobby では Workspace 内のマップが 0 個、バインドされたハザードが 0 個で、インスタンス数が 10 以内まで戻り、マップの読み込みをまたいでもフレームがゲートを満たすこと。

  6. ライブで観察して反復します。 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 msdocs、クライアントサーバーランタイム
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 に問題はありません。
  • **視覚的:**ラウンド中のキャプチャに、ミラーされた状態を示す HUD が表示されます。image_checks は警告を出さず、画像を開いて確認します。人間のレビュアーが Explorer のキャプチャを取得します。

よくある間違い

間違い症状修正
StarterPlayerScripts 内のクライアントスクリプト2 回実行される。Studio の Output に警告が表示されるエントリ Script を ReplicatedStorage に移動するか、StarterPlayerScripts で LocalScript を使用します
Rojo のデフォルトの emitLegacyScriptsReplicatedStorage 内のクライアントエントリが実行されない"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) で読み込めません。コードをプロジェクトに含めてください。

参考資料

コメント