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. 01为新 Roblox 项目搭建架构骨架
  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 架构(玩法程序员与架构师)

目标:在 macOS 上使用 Roblox Studio(2026 年 10 月版本),通过 scenario-roblox-expert 工具包进行操作。

在 Roblox 上进行专家级架构设计,意味着由放置位置和 RunContext 决定代码在哪里运行;每一端都由一个入口脚本分两个阶段启动所有模块;服务端模块掌握真实状态,属性只是其镜像;标签将行为绑定到实例,并为每个连接指定所有者。这项技能将这些规则从资料中提炼出来,并通过 Studio 0.743 中的实时测试进行修正,同时还包含经过测试的竞技场核心(回合循环、队伍、地图、HUD、商店、剑)以及用于构建和审查它的工具。如果这里提到的同级技能不在你可用的技能中,请让用户安装它(npx skills add scenario-labs/skills --skill <name>);若无人值守,请根据工具架构继续操作,并标明缺失之处。

必备背景: scenario-roblox-expert(通道、审查流程、工具包、当前 Roblox 的常见陷阱)。

状态(2026-10-19): Studio 0.743 中 14 项实时测试通过;8 项离线检查通过(18 项 Lune 单元测试、luau-lsp 在代码、夹具和代码片段上使用两种类型求解器检查、StyLua、selene)。

立场(专家实践要点)

  1. 固定放置位置和 RunContext:每一端各一个入口 Script。 在 ServerScriptService 中使用一个 RunContext 为 Server 的 Script,在 ReplicatedStorage 中使用一个 RunContext 为 Client 的 Script(Roblox 文档:Script locations;Ludius,n8IDTRW1EfM [00:01:34];Leif,F3ASTJuO82A [00:03:18])。其他所有内容都应是 ModuleScript。

    • 在 StarterPlayerScripts 中放置一个 Client Script,实时测试中它运行了两次,且 Output 中出现“non-legacy RunContext”警告。
    • Rojo 7.7.1 会将 .client.luau 转换为 LocalScript,除非项目设置了 "emitLegacyScripts": false(已验证)。ReplicatedStorage 中的 LocalScript 永远不会运行。
  2. 通过一个加载器分两个阶段启动。 GetDescendants 的顺序未定义,因此模块加载时不得互相调用(Suphi Kaner,YsXijBUUmmM [00:04:22])。Init 设置自身状态且绝不让出执行;Start 可以让出执行并调用其他模块(Leif [00:02:25];文档要求对每个模块调用 start())。Loader.luau 补充了资料中遗漏的内容 [新增]:

    • 先按显式顺序加载,再按路径排序加载其余模块;名称重复时失败(Suphi [00:01:50]);
    • 每个模块分别使用一个 pcall,因此一个服务出错不会阻止其他服务运行;
    • 如果 Init 让出了执行,则报告错误;
    • 冻结注册表,并将启动报告写入属性。
  3. 服务端模块掌握真实状态;属性是其镜像;UI 不拥有任何状态。 状态服务绝不 require 自己的 GUI;GUI 调用服务并监听其变化(MrAsync [00:25:46 至 00:31:11])。将共享状态发布为 Configuration 上的属性,供客户端观察(Crusherfire,6Q_p2ObUXSo [00:07:06])。镜像服务端的绝对时间(EndsAt),让客户端无需计时器远程调用即可倒计时 [新增]。属性或特性变更与远程调用之间没有顺序保证(文档:Attributes;Server Authority):将状态放入远程调用的负载中,或者只通过属性发布,并在每次变更时重新渲染。

  4. 按标签绑定行为,并通过祖先对象进行筛选。 由一个模块驱动所有带标签的实例,而不是为每个实例编写一个脚本(B Ricey,Vi2FckNJv9w [00:02:44];GnomeCode,UsPYgIW3rv8 [00:01:38];文档)。资料中遗漏了两个常见陷阱 [新增]:

    • ServerStorage 中带标签的模板也会出现在 GetTagged 的结果中:只在选定的祖先对象下绑定。
    • 将带标签的部件从 ServerStorage 移回 Workspace 时,不会触发 InstanceAdded(实时测试):监听 AncestryChanged。

    Binder.luau 同时处理这两种情况:先连接信号,再调用 GetTagged,并执行 setup 返回的清理函数。

  5. 每个连接都有一个与其生命周期一致的所有者。 将清理操作交给生命周期结束时会一并销毁的所有者, 例如使用每个 tycoon 自己的 Trove,而不是玩家的 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])。实际运行时,处理程序在等待之前还没有运行,之后读取到的 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 中,这会减少玩家加入时的流量(文档,数据模型)。每回合将其中一个克隆到 Workspace, 结束后销毁(GnomeCode [00:11:03 to 00:13:51],其做法是将它们放在 ReplicatedStorage 中)。Crusherfire 会在启动时将服务端模块从系统文件夹移到 ServerScriptService(6Q_p2ObUXSo [00:09:47])。通过相对路径引用同级模块,这样系统就可以移动(Crusherfire [00:23:46])。

  7. 由客户端发起请求;通过代码创建远程对象。 绝不要在 Explorer 中手动创建远程对象(Leif, F3ASTJuO82A [00:04:28])。客户端询问“我可以在这里建造吗?”,由服务端决定(Leif [00:05:39])。先在客户端检查以节省带宽,再在服务端检查一次(MrAsync [00:16:37])。

  8. 根据团队选择框架,而非个人喜好。 Knit 风格的框架会替你做决定,而且所有内容都依赖一个服务 (B Ricey,MyK4o2pIpQw [00:17:58]);2025 年的资料采用小型加载器 (Ludius、Leif、Crusherfire)。单人或小团队:使用此加载器、Signal 和一个清理模块;已有 Knit 代码库:保留现有代码,并补上审查 [新增]。仅在需要多个有状态实例时使用类(B Ricey [00:07:10]);服务是一个单例表(Leif [00:02:00])。

先明确

  • 会话形式:基于回合、持久化世界或大厅;玩家人数;队伍。
  • 新 place 还是现有代码:Rojo 或 Script Sync 仓库、仅使用 Studio,或保留现有框架。
  • 团队规模,以及系统是否会在多个 place 间共享(自有系统使用 Roblox Packages,第三方代码使用 Wally)。
  • Server Authority(AuthorityMode = Server):它会强制使用 Deferred 信号,并且每个实例只复制前 64 个属性。
  • StreamingEnabled:新 place 中默认开启(这些测试是在关闭时进行的);客户端需要等待部件流式传入。

默认设置:Rojo 项目中 emitLegacyScripts 为 false,使用下方的竞技场骨架、Deferred 信号、 PlayerCharacterDestroyBehavior Enabled、--!strict。

流程

设置:import rbx_architecture as A(它会将 lead 的脚本加入 sys.path)。完整代码和 记录的结果见 references/procedures.md。

  1. 搭建骨架。 A.scaffold(dest, name, fixtures=False) 会复制竞技场骨架以及 Loader 和 Binder,写入 Rojo 项目,并通过 rbx_env.new_place 构建 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服务器每轮克隆一张地图、轮换地图、管理按标签标记的垫子和危险物
    DamageService, ShopService服务器交由 scenario-roblox-gameplay 和 scenario-roblox-data 处理
    RoundMachine, Config, Signal共享纯逻辑、常量、信号(在 Lune 中运行)
    RoundController, ShopController客户端HUD 和商店,仅连接一次,ResetOnSpawn false

    门槛: A.audit_file(place) 报告 0 个错误和 0 个警告、没有 require 循环,且有 0 个未解析的 require。

  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 时销毁地图。**门槛:**在一轮中途重生五次后,仍只跟踪一个角色,地图上也只有一把已绑定的剑。 重生后,HUD 和商店仍是相同的实例。在 Lobby 中,Workspace 中有 0 张地图和 0 个已绑定的危险物,实例数量恢复至 10 以内,并且跨地图加载时帧数达到门槛。

  6. 实时观察并迭代。 使用 rbx_env.place_slot() 在 rbx_env.place_slot() 中通过 rbx_env.open_place 打开一次 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文档、客户端-服务端运行时
Studio 游戏测试延迟0 ms;模拟每个方向 50 至 150 ms文档;Studio 设置、Network 或 settings().Network
模块大小异味500+ 行MrAsync [00:20:54];ArchAudit module-size
Server Authority 下复制的属性每个实例前 64 个;名称和字符串最长 50 个字符文档、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;此时还没有角色实机
回合中复活五次实例数 +3 或 -3(约 46,150);1 个角色、1 把绑定的剑实机,运行 6 次
竞技场默认值2 名玩家、15 s 中场休息、120 s 回合、5 s 结束时间、0.25 s 间隔[已添加]
帧率门槛60 fps 下 p95 最多 20 ms,最差最多 33.3 ms(分别为预算的 1.2 倍和 2 倍)scenario-roblox-expert
地图轮换,实测服务端 p95 为 17.3 ms,跨一个克隆的最差耗时为 17.5 至 20.0 ms;大厅实例数变化为 0实机,运行 3 次

质量门槛

  • 可测量:
    • ArchAudit:fixture 之外有 0 个错误和 0 条警告;AgentKit Audit.all(game) 有 0 个错误。
    • 两端的启动报告:BootOk,BootErrors 为 0,BootCount 为 1。
    • 控制台:没有 "required recursively"、"not a valid member"、[Loader] 或 "non-legacy RunContext" 行。
    • 生命周期:复活后 Characters 等于玩家人数;在大厅中,地图数和绑定的危险物数量均为 0,实例数恢复到相差不超过 10;服务端和客户端帧率均达到门槛。
    • run_live.py 退出码为 0;Lune 单元测试通过;luau-lsp 在两种求解器上均有 0 个错误 (A.check);StyLua 和 selene 均无问题。
  • **视觉:**回合期间的截图显示带有镜像状态的 HUD;image_checks 未标记任何问题,并且你打开图像查看。人工审核者会获取一张 Explorer 截图。

常见错误

错误症状修复
StarterPlayerScripts 中的客户端脚本运行两次;Studio 在 Output 中发出警告将入口 Script 移至 ReplicatedStorage,或在 StarterPlayerScripts 中使用 LocalScript
Rojo 默认启用 emitLegacyScriptsReplicatedStorage 中的客户端入口永不运行设置 "emitLegacyScripts": false
在 execute_luau 中 require 正在运行的服务新副本:字段为 nil,状态为空读取模块仅供 Studio 使用的属性
ServerStorage 中带标签的模板模板会获得行为使用 ancestors 筛选
在 DataModel 中重新设置父级移回后行为缺失监视 AncestryChanged(Binder)
回合物品只发放一次重生后剑消失在回合期间的 CharacterAdded 时重新发放
在 Destroying 或 CharacterRemoving 中读取 ParentDeferred 下为 nil在销毁前捕获状态
在处理程序内连接监听器错过已排队的触发事件在 Init 中连接,在任何事件触发之前
SetAttribute 后调用 FireAllClients;处理程序读取该属性在运行中的服务上可能读到旧值在负载中发送状态
ReplicatedStorage 或 Workspace 中的地图每个客户端加入时都会下载所有地图使用 ServerStorage,每回合克隆一次(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(简介、工具包、场景槽位);scenario-3d 和 scenario-game-assets(以带标签模板形式放入 ServerStorage 的模型)。
  • 交付给: scenario-roblox-networking(服务布局、远程模块槽位), 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 会以退出码 0 丢弃 Part 上的 "Position",并使其停留在原点:请写入 12 个数字的 "CFrame"(已验证)。
  • Rojo 7.7.1 会将 Script Sync 的 .local.luau 和 .legacy.luau 构建为名为 X.local 和 X.legacy 的 ModuleScript(已验证):不要混用这两种后缀方案。
  • 按字符串 require:./、../、@self 和 @game;对 Init 模块的特殊处理已移除 (版本差异第 23 行)。
  • SignalBehavior:新模板和 Server Authority 使用 Deferred;较旧的场景中,Default 仍映射到 Immediate。实时测试中,workspace.SignalBehavior 在 execute_luau 中报错“不是有效成员”:请在 Properties 中读取,或使用 Lune 从文件读取。
  • PlayerCharacterDestroyBehavior 不可由脚本设置:请在 Properties 或 Rojo 项目中设置。
  • 文档说明了 CollectionService:CreateCollection,但在 Studio 0.743 中报错“未启用 Collections”(实时测试)。请使用标签和 Binder。
  • Next Gen Studio UI 是默认界面:菜单路径见 references/gui-paths.md。
  • Creator Store 资源无法通过 require(assetId) 加载:请将代码纳入项目。

参考资料

评论

支持 Markdown