关于 roblox-studio-emulation
roblox-studio-emulation 是面向 Roblox Studio 开发者的测试技能,用于验证移动设备、多人交互和边界条件。它介绍菜单自动化、远程事件测试,以及如何说明验证结果的范围。
主要功能
- 01模拟触控和设备条件
- 02用 AppleScript 操作 Studio 测试菜单
- 03通过真实远程事件测试多人流程
- 04用客户端属性设置测试场景
- 05区分实测、逻辑验证与未测试
适用场景
- 01检查仅触控界面的行为
- 02无需第二客户端测试派对状态更新
- 03用合成输入测试逻辑分支
MCP 相关
该技能配合 Roblox Studio MCP 工具使用,并提及在切换模拟器后通过 `list_roblox_studios`、`set_active_studio` 和 `start_stop_play` 重新连接。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ flatbedj/ roblox-skills --skill roblox-studio-emulation安装后在 Claude Code 中输入/roblox-studio-emulation确认已加载。
详细介绍
模拟单人试玩无法触及的条件
仅靠 MCP 工具进行测试时,有两大类测试看起来无法完成,因此很容易想把它们推给用户。但这两类测试都可以自行完成。声称需要用户测试某项内容前,请先查看这里。
常规开发循环请参阅 roblox-studio-workflow;MCP 工具箱请参阅 roblox-studio-mcp。
1. 设备模拟(手机/平板/触控)——通过 Studio 菜单栏
Studio 不会向 macOS 辅助功能开放任何窗口(count windows → 0),因此你无法调整其窗口大小。
但它会开放整个菜单栏,足以操作 Test 菜单。
# one-time: the user must grant Terminal (or your host app) Accessibility permission
# System Settings → Privacy & Security → Accessibility
# symptom if missing: "osascript is not allowed assistive access. (-1719)"
osascript -e 'tell application "Roblox Studio" to activate'
osascript -e 'tell application "System Events" to tell process "RobloxStudio" \
to click menu item "Device Emulator" of menu 1 of menu bar item "Test" of menu bar 1'
进程名称是 RobloxStudio(不是 “Roblox Studio”,也不是 “RobloxStudioBeta”)。
确认设置确实生效,不要想当然——启动 Play 并读取实际值:
local cam = workspace.CurrentCamera
local UIS = game:GetService("UserInputService")
-- desktop: ~1920x1080, TouchEnabled=false
-- emulated: device-sized viewport, TouchEnabled=TRUE
return cam.ViewportSize, UIS.TouchEnabled
TouchEnabled = true 表示仅触控代码路径(摇杆间距、触控布局分支、POS_SHOWN 偏移)现已启用。
完整的 Test 菜单(实用项目):
| 项目 | 说明 |
|---|---|
Device Emulator | 切换模拟状态 |
Controller Emulator / Player Emulator | 模拟游戏手柄/玩家状态 |
Start Test Session | 必须先启动,才能使用 Add Clients |
Add Clients | 在 Play Solo 中不可用;在 Test Session 中可用 |
Toggle Client View、Pause Client、End Session | 控制多客户端会话 |
点击前先枚举项目并检查其启用状态:
osascript -e 'tell application "System Events" to tell process "RobloxStudio" \
to get name of every menu item of menu 1 of menu bar item "Test" of menu bar 1'
osascript -e 'tell application "System Events" to tell process "RobloxStudio" \
to get enabled of menu item "Add Clients" of menu 1 of menu bar item "Test" of menu bar 1'
限制
- 选择特定设备(iPhone、iPad 等)需要使用窗口内的下拉菜单,而该菜单不会通过 AX 暴露。你只能使用当前选中的设备。如果需要特定宽度,请让用户设置一次——狭窄的手机屏幕最容易导致布局出错。
- 切换模拟器会结束当前 Play 会话,并可能导致 MCP 链接断开。通过
list_roblox_studios→set_active_studio重新连接,然后运行start_stop_play。 - 完成后请将模拟器切回关闭状态,否则之后的每张截图都会显示为触控设备。
2. 无需第二个客户端即可测试多人游戏功能——触发真实的远程事件
**这通常比启动多客户端会话更快、更精确。**服务器 VM 和客户端 VM 共享 DataModel,因此从服务器 VM 调用 FireClient 会到达真实的客户端模块,并触发其下游所有逻辑——信号处理程序、缓存状态、UI 刷新。
-- datamodel_type = "Server"
local Remotes = require(game:GetService("ReplicatedStorage"):WaitForChild("Shared").Remotes)
local plr = game.Players:GetPlayers()[1]
-- pretend the server told us we joined someone's party
Remotes.event("PartyUpdated"):FireClient(plr, {
ownerUserId = 99999999, ownerName = "TestHost",
members = { { userId = plr.UserId, name = plr.DisplayName } },
})
然后在客户端 VM 中断言可观测的实例状态(绝不要检查模块内部状态——VM 相互隔离,require 得到的是一个虚拟副本):
-- datamodel_type = "Client"
local on = 0
for _, p in game:GetService("CollectionService"):GetTagged("Plot") do
for _, d in p:GetDescendants() do
if d:IsA("ProximityPrompt") and d.Name == "PartyPrompt" and d.Enabled then on += 1 end
end
end
return on
务必测试往返状态——触发“已加入”状态并进行断言,然后触发“已离开”状态(FireClient(plr, nil))并断言状态已恢复。单向测试会掩盖卡住的状态,这是更棘手的 bug(提示词一直隐藏 = 该玩家无法使用此功能)。
模拟前置条件
在客户端写入属性足以设置测试场景,因为客户端代码读取的是相同属性,而且 GetAttributeChangedSignal 会在本地触发:
-- datamodel_type = "Client" — pretend another player owns this plot
workspace.Map.Plots.Plot1:SetAttribute("OwnerUserId", 99999999)
-- ... run the test ...
workspace.Map.Plots.Plot1:SetAttribute("OwnerUserId", nil) -- ALWAYS clean up
不适用的情况
如果代码要求被模拟的用户是真实在线的 Player,就无法用这种方法模拟。例如:某个轮询循环执行 Players:GetPlayerByUserId(owner),并清理任何未通过此检查的对象,那么你的设置会立即被撤销。表现为:创建的对象会在一秒内消失。
改用以下任一方法:
Test ▸ Start Test Session ▸ Add Clients,使用真实的第二个玩家;或- 在客户端 VM 中用合成输入对纯逻辑进行单元测试——逐字照搬已发布代码中的守卫条件/分支,并使用表格测试:
local cases = {
{ "nil payload", nil, true },
{ "empty string", "", true },
{ "garbage base64", "!!!not-base64!!!", true },
{ "short buffer", Base64.encode(short),true },
{ "valid buffer", Base64.encode(full), false },
}
-- assert blocked == expected for each; report N/N
这是有效且可报告的结果——但请明确说明这验证的是逻辑,而非完整的渲染路径。
如实报告
每次都要区分以下三种情况:
- 实时验证——实际驱动功能并观察到了效果。
- 逻辑验证——使用合成输入对分支进行了表格测试;完整路径未经测试。
- 未经测试——如实说明,并明确指出测试还需要什么。
绝不要把“能编译,看起来也没问题”报告为已验证。