关于 scenario-roblox-architecture
面向 Roblox 游戏程序员与架构师的架构技能,涵盖脚本放置、模块启动、服务器状态管理和实例生命周期,并提供可测试的竞技场核心与审计工具。
主要功能
- 01规划服务器与客户端脚本位置
- 02通过两阶段加载器启动模块
- 03以服务器状态驱动客户端镜像
- 04按标签绑定实例行为
- 05管理连接与对象生命周期
- 06搭建并审计竞技场核心
适用场景
- 01为新 Roblox 项目搭建架构骨架
- 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 架构(玩法程序员与架构师)
目标:在 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)。
立场(专家实践要点)
-
固定放置位置和 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 永远不会运行。
-
通过一个加载器分两个阶段启动。 GetDescendants 的顺序未定义,因此模块加载时不得互相调用(Suphi Kaner,YsXijBUUmmM [00:04:22])。Init 设置自身状态且绝不让出执行;Start 可以让出执行并调用其他模块(Leif [00:02:25];文档要求对每个模块调用
start())。Loader.luau补充了资料中遗漏的内容 [新增]:- 先按显式顺序加载,再按路径排序加载其余模块;名称重复时失败(Suphi [00:01:50]);
- 每个模块分别使用一个 pcall,因此一个服务出错不会阻止其他服务运行;
- 如果 Init 让出了执行,则报告错误;
- 冻结注册表,并将启动报告写入属性。
-
服务端模块掌握真实状态;属性是其镜像;UI 不拥有任何状态。 状态服务绝不 require 自己的 GUI;GUI 调用服务并监听其变化(MrAsync [00:25:46 至 00:31:11])。将共享状态发布为 Configuration 上的属性,供客户端观察(Crusherfire,6Q_p2ObUXSo [00:07:06])。镜像服务端的绝对时间(
EndsAt),让客户端无需计时器远程调用即可倒计时 [新增]。属性或特性变更与远程调用之间没有顺序保证(文档:Attributes;Server Authority):将状态放入远程调用的负载中,或者只通过属性发布,并在每次变更时重新渲染。 -
按标签绑定行为,并通过祖先对象进行筛选。 由一个模块驱动所有带标签的实例,而不是为每个实例编写一个脚本(B Ricey,Vi2FckNJv9w [00:02:44];GnomeCode,UsPYgIW3rv8 [00:01:38];文档)。资料中遗漏了两个常见陷阱 [新增]:
- ServerStorage 中带标签的模板也会出现在 GetTagged 的结果中:只在选定的祖先对象下绑定。
- 将带标签的部件从 ServerStorage 移回 Workspace 时,不会触发 InstanceAdded(实时测试):监听 AncestryChanged。
Binder.luau同时处理这两种情况:先连接信号,再调用 GetTagged,并执行 setup 返回的清理函数。 -
每个连接都有一个与其生命周期一致的所有者。 将清理操作交给生命周期结束时会一并销毁的所有者, 例如使用每个 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]),其 只运行一次的控制器会保留有效引用(实际运行时)。
-
服务端代码和回合内容绝不放在复制容器中。 利用漏洞的玩家可以读取 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])。
-
由客户端发起请求;通过代码创建远程对象。 绝不要在 Explorer 中手动创建远程对象(Leif, F3ASTJuO82A [00:04:28])。客户端询问“我可以在这里建造吗?”,由服务端决定(Leif [00:05:39])。先在客户端检查以节省带宽,再在服务端检查一次(MrAsync [00:16:37])。
-
根据团队选择框架,而非个人喜好。 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。
-
搭建骨架。
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 个错误。 -
规划模块。 编写代码前先写好模块映射。依赖应从服务指向共享模块,绝不能从服务指向 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。 -
启动。 每个入口脚本都调用
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 时销毁地图。**门槛:**在一轮中途重生五次后,仍只跟踪一个角色,地图上也只有一把已绑定的剑。 重生后,HUD 和商店仍是相同的实例。在 Lobby 中,Workspace 中有 0 张地图和 0 个已绑定的危险物,实例数量恢复至 10 以内,并且跨地图加载时帧数达到门槛。 -
实时观察并迭代。 使用
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 均无问题。
- ArchAudit:fixture 之外有 0 个错误和 0 条警告;AgentKit
- **视觉:**回合期间的截图显示带有镜像状态的 HUD;
image_checks未标记任何问题,并且你打开图像查看。人工审核者会获取一张 Explorer 截图。
常见错误
| 错误 | 症状 | 修复 |
|---|---|---|
| StarterPlayerScripts 中的客户端脚本 | 运行两次;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,每回合克隆一次(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)加载:请将代码纳入项目。
参考资料
references/expert-notes.md:按专家整理的原则及其时间戳, 以及实时更正。references/procedures.md:每个流程的完整代码、测试 和记录结果。references/critique.md:自我审查评分标准。references/project-setup.md:客户端启动、StreamingEnabled、 Packages 和团队工具、双客户端测试。references/gui-paths.md:执行相同步骤时使用的 Studio 菜单和面板。references/sources.md:所有来源及其 URL 和最佳时间戳。scripts/rbx_architecture.py:脚手架、审计、场景事实、同步、 启动报告。scripts/agentkit/architecture/:Loader、Binder、ArchAudit 和 Lune 运行器。scripts/templates/arena/:经过测试的竞技场核心及其审计 固定装置。