关于 roblox-toolchain
roblox-toolchain 是面向 Roblox 游戏开发的 Agent 技能,介绍如何以文件系统为项目源,通过 Rojo、Rokit 和 Wally 或 Pesde 管理构建与依赖。内容涵盖项目配置、文件映射、工具版本固定、Luau LSP 配置及工作流规范。
主要功能
- 01Rojo 文件系统开发指南
- 02使用 Rokit 固定工具版本
- 03Wally 与 Pesde 依赖管理
- 04项目文件与 DataModel 映射
- 05配置 Luau LSP sourcemap
- 06提供代码库集成规范
适用场景
- 01从文件系统开发 Roblox 游戏
- 02为团队固定 Rojo 等工具版本
- 03配置 Wally 或 Pesde 依赖
- 04设置 Luau LSP 代码补全
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ afrxo/ roblox-agent-skills --skill roblox-toolchain安装后在 Claude Code 中输入/roblox-toolchain确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/.gitkeepci.ymldefault.project.jsonfile-tree.mdpesde.tomlrokit.tomlwally.tomlreferences/luau-lsp-setup.mdpesde-manifest.mdproject-format.mdsync-mapping.mdwally-manifest.md详细介绍
Roblox Rojo(文件系统工作流)
如何通过磁盘上的文件而非 Studio 内部来开发 Roblox 游戏。资料来源:rojo.space(Rojo 官方文档)、github.com/rojo-rbx/rojo(Rojo 源码)、github.com/rojo-rbx/rokit(Rokit 工具链管理器——请使用它,而非 Aftman/Foreman)、github.com/UpliftGames/wally(Wally 包管理器)、github.com/pesde-pkg/pesde(Pesde——较新的替代包管理器)。工具:stylua、selene、luau-lsp。
深入了解请参阅:
references/project-format.md— 完整的.project.json架构references/sync-mapping.md— Rojo 可识别的所有文件扩展名及其转换结果references/wally-manifest.md— 完整的wally.toml、锁文件和命令参考references/pesde-manifest.md— 完整的pesde.toml架构、环境和 Wally 桥接references/luau-lsp-setup.md— sourcemap 重新生成、LSP 配置
集成政策
本技能介绍的是推荐模式;不授权重写现有代码。将规则用于新代码,以及用户明确要求修改的内容。**遵循代码库现有的工具链。**如果项目使用 Aftman,不要主动迁移到 Rokit。如果使用 Wally,不要建议使用 Pesde。如果 default.project.json 遵循特定结构,就在此基础上扩展——不要重写。只指出一次冲突,并默认采用现有配置。
完整规则:../../shared/integration-policy.md。
原则
- **唯一可信来源:文件系统。**仓库是规范来源;Studio 是构建目标。
- 按项目固定工具链版本。
rokit.toml列出rojo、wally(或pesde)、stylua、selene、luau-lsp的确切版本。可复现构建胜过“在我的机器上能用”。 - **重新生成 sourcemap,而非将其提交。**它是生成的输出。依赖项变更时重新生成。
- 在 CI 中构建和检查代码。
main分支状态为绿色,意味着rojo build能生成有效的.rbxl,且检查全部通过。 - **
Packages/是生成目录。**由 Wally 写入;不要手动编辑;已加入.gitignore。
Rojo 基础知识
安装
使用 Rokit(工具链管理器,Foreman/Aftman 的后继者)按项目固定版本:
# rokit.toml — drop-in compatible with foreman.toml / aftman.toml format
[tools]
rojo = "rojo-rbx/[email protected]"
wally = "UpliftGames/[email protected]"
stylua = "JohnnyMorganz/[email protected]"
selene = "Kampfkarren/[email protected]"
然后在项目根目录运行 rokit install,即可安装这些确切版本。不要依赖全局安装的工具——不同机器上的版本可能不一致。
**为什么选 Rokit,而不是 Aftman:**Aftman 已无人维护(第三方作者已不再从事 Roblox 相关工作);Foreman 由 Roblox 维护,但主要面向内部使用。Rokit 由社区维护,可直接兼容这两种清单格式,而且安装速度快得多。迁移时,将 aftman.toml 重命名为 rokit.toml。
常见 Rokit 命令:rokit init、rokit add OWNER/REPO、rokit install、rokit update、rokit list、rokit self-update。
两种模式
# Live sync — Studio plugin connects to this server, applies changes as you save.
rojo serve [path/to/project.json]
# One-shot build — produces a .rbxl (place) or .rbxm (model).
rojo build [path/to/project.json] --output build/place.rbxl
rojo serve 用于开发。rojo build 用于 CI 和最终发布。Studio 插件(通过 Studio 的插件选项卡安装,版本需与 CLI 匹配)负责实际应用实时变更。
项目文件位置
Rojo 会在当前目录中查找 default.project.json。传入显式路径即可使用其他任何 .project.json 文件。包含多个构建目标(游戏、插件、库)的仓库通常会为每个目标配置一个项目。
default.project.json 结构
{
"name": "MyGame",
"tree": {
"$className": "DataModel",
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"$path": "src/ReplicatedStorage"
},
"ServerScriptService": {
"$className": "ServerScriptService",
"$path": "src/ServerScriptService"
},
"StarterPlayer": {
"$className": "StarterPlayer",
"StarterPlayerScripts": {
"$className": "StarterPlayerScripts",
"$path": "src/StarterPlayerScripts"
}
},
"Workspace": {
"$className": "Workspace",
"$properties": {
"Gravity": 196.2
}
}
}
}
name(必需)— 用作构建产物名称。tree(必需)— 根 Instance 描述。- 在
tree中:$className— Instance 类。如果$path能解析出类,或键是服务名称,则可省略。$path— 要挂载到此 Instance 下的磁盘文件夹/文件。$properties— 要设置的属性(例如Gravity、HttpEnabled)。$ignoreUnknownInstances— 如果为true,Rojo 会保留未同步的 Instance,而不是将其删除。- 其他任意键 = 子 Instance 描述。
其他顶层字段:servePort(默认值为 34872)、servePlaceIds(place ID 允许列表,可防止意外同步到错误的游戏中)、placeId、gameId、serveAddress、globIgnorePaths、emitLegacyScripts。
emitLegacyScripts 用于控制 *.server.luau/*.client.luau 是生成旧式 Script/LocalScript Instance,还是生成设置了 RunContext 的 Script Instance。文档所述的默认值为 true;请先根据已安装的 Rojo 版本(rojo --version + 发布说明)确认,再据此作出假设。新项目应设置为 false 以使用现代 RunContext — 参见 roblox-dev 的脚本类型部分。
完整架构:references/project-format.md。
文件系统 → DataModel 映射
这套规则会经常用到,请记住常见项。
| 磁盘路径 | 转换为 |
|---|---|
dir/(任意目录) | Folder |
foo.luau | 名为 foo 的 ModuleScript(Rojo 也接受旧式 .lua) |
foo.server.luau | 名为 foo 的 Script(服务器端) |
foo.client.luau | 名为 foo 的 LocalScript(客户端) |
dir/init.luau | 名为 dir 的 ModuleScript(目录会变为脚本) |
dir/init.server.luau | 名为 dir 的 Script(目录会变为服务器脚本) |
dir/init.client.luau | 名为 dir 的 LocalScript(目录会变为本地脚本) |
foo.rbxm / foo.rbxmx | 从二进制/XML 文件生成的模型 |
foo.csv | LocalizationTable |
foo.txt | StringValue |
foo.json(不是 .model.json / .project.json) | 返回该表的 ModuleScript |
foo.toml | 返回该表的 ModuleScript |
foo.model.json | JSON 描述的模型(手写,适用于 RemoteEvent 等) |
foo.project.json | 嵌套项目(合并到父项目中) |
foo.meta.json | 附加到匹配文件/目录的元数据侧文件 |
init.luau 语义
目录 + init.luau 会变为一个 ModuleScript,目录中的其他子项则会成为该脚本的子项。这样,脚本无需单独的文件夹也能包含子模块。
src/Inventory/
├── init.luau # ModuleScript "Inventory"
├── add.luau # ModuleScript "add" (child of Inventory)
├── remove.luau # ModuleScript "remove"
└── types.luau # ModuleScript "types"
每个目录中只能有一个 init.* 文件(init.luau 或 init.server.luau 或 init.client.luau)。
Meta 文件
foo.meta.json 会将 Rojo 专用元数据附加到 foo.*。常见用途:
// init.meta.json — turn the parent folder into a Tool with a Grip
{
"className": "Tool",
"properties": {
"Grip": [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]
}
}
// foo.meta.json — disable a script
{
"properties": { "Disabled": true }
}
// hello.meta.json — let Rojo leave Instances under hello.txt alone
{
"ignoreUnknownInstances": true
}
className 仅适用于 init.meta.json。properties 适用于任何非模型文件。
完整映射表:references/sync-mapping.md。
推荐的项目布局
my-game/
├── rokit.toml # toolchain pinning
├── default.project.json # Rojo project file
├── wally.toml # package manifest
├── wally.lock # lockfile (committed)
├── stylua.toml # formatter config
├── selene.toml # linter config
├── sourcemap.json # generated; .gitignore'd
├── Packages/ # wally output; .gitignore'd
├── ServerPackages/ # wally output; .gitignore'd
├── DevPackages/ # wally output; .gitignore'd
├── .github/workflows/ci.yml # CI
├── (editor config) # .vscode/settings.json, .nvim, etc. — editor-specific
├── .gitignore
├── README.md
└── src/
├── ReplicatedStorage/
│ ├── Shared/
│ │ ├── init.luau
│ │ └── ...
│ └── Client/ # (optional) client-only scripts via RunContext
├── ServerScriptService/
│ ├── init.server.luau
│ └── Systems/
└── StarterPlayer/
└── StarterPlayerScripts/
└── init.client.luau
让 src/ 目录名称与其 DataModel 挂载点一致。查看路径即可准确了解代码的运行位置。
Wally(包)
通过 Rokit 安装(不要从全局安装中获取)。添加到 rokit.toml:
[tools]
wally = "UpliftGames/[email protected]"
然后运行 rokit install。命令:
wally init # scaffold wally.toml
wally install # download deps into Packages/
wally install --locked # CI; fail if lockfile is stale
wally update [name...] # bump deps
wally publish # push your package to the registry
wally search <query> # find packages
wally.toml
[package]
name = "yourname/coolpkg" # SCOPE/NAME, lowercase, dashes ok
version = "1.0.0" # SemVer
license = "MIT" # SPDX
authors = ["Your Name <[email protected]>"]
realm = "shared" # "shared" or "server"
registry = "https://github.com/UpliftGames/wally-index"
[dependencies]
React = "jsdotlua/[email protected]"
Promise = "evaera/[email protected]"
[server-dependencies]
ProfileService = "loleris/[email protected]"
[dev-dependencies]
Jest = "jsdotlua/[email protected]"
Realm
shared— 同步到客户端。挂载到ReplicatedStorage下。server— 仅服务器端可用。挂载到ServerStorage或ServerScriptService下。- 不存在
clientrealm。仅客户端依赖项放在shared中,并在客户端通过导入运行。
在 Rojo 中挂载 Wally 输出
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"Packages": {
"$path": "Packages"
}
},
"ServerScriptService": {
"$className": "ServerScriptService",
"Packages": {
"$path": "ServerPackages"
}
}
包会按 realm 安装到不同文件夹中。Shared → Packages/,server → ServerPackages/,dev → DevPackages/。
锁文件
wally.lock 固定了每个依赖项(包括传递依赖项)的确切版本。请提交它。 在 CI 中运行 wally install --locked,可确保所有人构建出相同的内容。
完整的清单字段、锁文件格式和命令列表:references/wally-manifest.md。
Pesde(替代包管理器)
Pesde 是一款较新的 Luau 优先包管理器,旨在避免运行时锁定。它通过一个工具支持多个 Luau 运行时(luau、lune、roblox、roblox_server),还可通过桥接从现有的 Wally 注册表获取包。以下情况值得使用:
- 希望在纯 Luau、Lune 和 Roblox 项目中使用同一个包管理器。
- 需要在同一个项目中使用 Wally 包和原生 pesde 包。
- 相比 Wally 的 Cargo 风格,更偏好 npm/pnpm 风格的使用体验。
pesde.toml
name = "yourname/coolpkg"
version = "1.0.0"
license = "MIT"
[target]
environment = "roblox" # luau | lune | roblox | roblox_server
lib = "src/init.luau"
[indices]
default = "https://github.com/pesde-pkg/index"
[wally_indices]
default = "https://github.com/UpliftGames/wally-index"
[dependencies]
# Native pesde dep
hello = { name = "pesde/hello", version = "^1.0.0" }
# Wally dep via the bridge
React = { wally = "jsdotlua/react", version = "^17.1.0" }
[dev_dependencies]
Jest = { wally = "jsdotlua/jest", version = "^3.10.0" }
命令
pesde init # scaffold pesde.toml
pesde add SCOPE/NAME # add + install a dep
pesde install # install all deps; writes pesde.lock
pesde run # run [scripts] entries (uses Lune)
pesde publish
目录结构
- 原生 pesde 依赖项会安装到以目标命名的文件夹中(
roblox_packages/、luau_packages/等)。 - Wally 桥接依赖项仍通过 wally-index 仓库解析。
pesde.lock是锁文件,请提交它。- 对于 Roblox 项目,pesde 会通过脚本包生成与 Rojo 兼容的源映射;请配合
rojo sourcemap使用。
如何选择
| 需求 | 选择 |
|---|---|
| 成熟、仅面向 Roblox、拥有庞大现有生态 | Wally |
| 多运行时(Lune 命令行工具、Roblox、纯 Luau) | Pesde |
| Cargo 风格的使用体验 | Wally |
| npm/pnpm 风格的使用体验,加上 Wally 桥接 | Pesde |
| 现有 Wally 项目使用顺畅 | 继续使用 |
Wally 和 Pesde 并非可直接互换的工具,清单格式和解析语义各不相同。没有理由就不要在项目中途切换。
源映射(Luau LSP)
Luau LSP 需要知道文件在 DataModel 中的映射位置,才能正确提供自动补全和 require() 解析。Rojo 会生成该映射。
# Watch mode — regenerates on filesystem change
rojo sourcemap --watch --output sourcemap.json default.project.json
# One-shot
rojo sourcemap --output sourcemap.json default.project.json
然后将其路径提供给 luau-lsp。各编辑器的设置方式如下:
- VS Code(
luau-lsp扩展): 在.vscode/settings.json(或工作区级配置)中设置"luau-lsp.sourcemap.sourcemapFile": "sourcemap.json"。 - Neovim / Helix / Sublime: 将源映射路径作为 LSP 启动参数(
--definitions=.../--sourcemap=...)传入,或通过 LSP 客户端的设置传入;luau-lsp二进制文件的文档中列出了相关标志。 - 命令行模式:
luau-lsp analyze --sourcemap sourcemap.json ...。请通过已安装的luau-lsp --help确认具体标志。
在以下情况后重新生成:
- 在项目中添加或移除文件。
- 运行
wally install(新增包路径)。 - 更改
default.project.json挂载点。
一种常见工作流:在终端中运行 rojo sourcemap --watch,同时运行 rojo serve。两者都会在整个会话期间持续运行。
sourcemap.json 是生成文件,请将其加入 .gitignore。
LSP 设置详情:references/luau-lsp-setup.md。
代码检查与格式化
StyLua(格式化工具)
# stylua.toml
column_width = 100
line_endings = "Unix"
indent_type = "Tabs" # or "Spaces" with indent_width = 4
quote_style = "AutoPreferDouble"
call_parentheses = "Always"
[sort_requires]
enabled = true # alphabetize the require block; pairs with luau-expert guidance
通过 stylua src/ 运行。安装 StyLua 扩展后,大多数编辑器都能在保存时自动格式化。
selene(代码检查工具)
# selene.toml
std = "roblox" # roblox stdlib definitions
# Optional: per-rule tweaks
[config]
incorrect_standard_library_use = "warn"
std = "roblox" 是关键设置;没有它,selene 会对每个 Instance.new 和 task.wait 报错。通过 selene src/ 运行。
Luau LSP(类型检查器)
luau-lsp 可在编辑器中提供类型检查、悬停信息和自动补全。每个文件结合使用 --!strict(参见 luau-expert 技能),以实现最大覆盖率。
项目级配置(.luaurc):
{
"languageMode": "strict",
"lint": {
"*": true
}
}
请根据 luau.org/lint 验证架构;.luaurc 字段可能会有变化。
CI
最低可用的 CI 运行流程:
- 设置 Rokit(
rokit install),安装固定版本的工具。 wally install --locked——如果锁文件不一致则失败。stylua --check src/——如果格式不一致则失败。selene src/——执行 lint。rojo sourcemap --output sourcemap.json default.project.json——生成映射文件。- 使用 sourcemap 运行
luau-lsp analyze——进行类型检查。具体标志取决于已安装的版本(--sourcemap或--definitions);请查看luau-lsp --help。 rojo build default.project.json --output build/place.rbxl——构建产物。- **(可选)**将
place.rbxl上传为产物/发布。
每一步失败都会中止后续步骤。CI 运行通过意味着:工具版本固定、依赖已锁定、格式正确、lint 无问题、类型检查通过且构建成功。
GitHub Actions 示例见 examples/ci.yml。
验证规则
- 不要凭记忆引用
.project.json字段名,以下字段除外:name、tree、$className、$path、$properties。完整架构很简短——提交前请查看references/project-format.md,确认不常用字段。 - 不要凭记忆引用 Wally 版本语义。 Wally 遵循 Cargo 风格的 SemVer,默认使用
^;推荐使用非默认版本说明符前,应根据 Wally 文档确认~、=、*的具体含义。 - 不同版本中
emitLegacyScripts的默认值有所变化。 在假定Script与Script(RunContext=Server)的区别前,请确认项目使用的 Rojo 版本(rojo --version)。 - 编辑器配置各不相同。 本技能涵盖 Luau LSP 层——参考资料中的
.vscode/settings.json示例仅供说明;Neovim/Helix/Sublime 用户需配置对应字段。 - 不要混淆 Rojo 与引擎。
RunContext的位置/复制/DataModel 语义属于roblox-dev。本技能只说明“文件最终位于此 DataModel 路径”。
反模式
| ❌ 不要 | ✅ 应该 |
|---|---|
全局安装 rojo/wally/stylua | 通过 rokit.toml 为每个项目固定版本 |
| 新项目使用 Aftman 或 Foreman | 使用 Rokit(可直接替代;由社区维护,速度更快) |
手动编辑 Packages/ | 将其视为生成内容;wally install 会重新生成 |
提交 Packages//ServerPackages//DevPackages/ | 将它们加入 .gitignore |
提交 sourcemap.json | 将其加入 .gitignore;按需重新生成 |
忘记将 wally.lock 纳入 git | 提交它;CI 使用 --locked |
在同一文件夹中放置多个 init.* | 每个目录只放一个 init 脚本;改用子模块 |
将 init.client.luau 放在 ServerScriptService 挂载点 | 让脚本类型与容器匹配 |
使用未设置 std = "roblox" 的 selene | 始终设置;否则每个 Roblox API 都会触发 lint |
| 同时编辑 Studio 和文件系统 | 文件系统是唯一标准;不运行 rojo serve 时请关闭 Studio |
将 rojo build 生成的 place 文件提交到仓库 | 仅作为 CI 产物;二进制 place 文件无法进行 diff |
仅在 ReplicatedStorage 下挂载 Packages,却使用服务器依赖 | 在服务器容器下单独挂载 ServerPackages |
在 StarterPlayerScripts 挂载点使用普通 *.luau,并期待客户端行为 | 使用 *.client.luau(旧版方式)或 RunContext=Client 的 Script |
每次保存后手动重新运行 rojo sourcemap | 在长期运行的终端中使用 --watch |
从工作树有未提交更改时运行 wally publish | 为干净的提交打标签;从 CI 发布 |
不使用 sourcemap 运行 luau-lsp | 生成 sourcemap;LSP 需要它来解析 require() |
示例
examples/default.project.json— 涵盖所有标准挂载的典型 place 项目。examples/wally.toml— 带注释的 Wally 清单,包含[dependencies]、[server-dependencies]、[dev-dependencies]。examples/pesde.toml— 带注释的 Pesde 清单,包含[target]、[indices]、[wally_indices]、[dependencies]。examples/rokit.toml— 工具链固定配置。examples/file-tree.md— 完整的仓库布局,展示文件系统如何映射到 DataModel。examples/ci.yml— GitHub Actions 工作流:安装 Rokit → wally install --locked → stylua check → selene → rojo build。