mcpField
P
playcal

playcal

本地服务
pypi.org开发工具01 天前更新

关于 playcal

playcal 是面向使用 Unity Editor 构建游戏的 AI Agent 的工具包,将编辑、等待编辑器就绪、验证改动和检查差异整合为循环。它还提供游戏事件分析、日志整理及机器人测试与玩家表现的校准功能,目前处于 pre-alpha。

主要功能

  1. 01等待 Unity Editor 稳定并验证改动
  2. 02检查场景差异与断言结果
  3. 03汇总 Unity 日志中的错误
  4. 04分析关卡通过率及置信区间
  5. 05根据玩家数据校准机器人测试

适用场景

  1. 01确认编辑器写入是否实际生效
  2. 02从庞大的层级结果中筛选对象
  3. 03定位 Unity 构建日志的首个错误
  4. 04估算玩家在各关卡的通过率

MCP 相关

`playcal mcp` 启动 stdio MCP 服务,将各模块作为工具公开;工具参数定义与 CLI 共用。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"rizgarozan-playcal": {
"command": "uvx",
"args": [
"playcal"
]
}
}
}
启动命令uvx playcal

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypiplaycal0.2.2

工具

15

收录的工具列表 · 更新于 1 天前

参数类型必填说明
dirstring否基线的存放位置(默认值:.playcal/baselines)
namestring是基线名称,例如 level4-android
floorarray否忽略小于此绝对值的性能下降,例如 frame_ms=0.5。可重复指定
actionenum是save NAME FILE:将一次运行保存为基线。check NAME FILE:比较新运行结果。show NAMEsave | check | show
metricarray否仅使用这些指标(默认值:两者均为全部)
sourcestring否样本 JSON 文件,或使用 - 表示标准输入(默认值)
tolerancenumber否允许的性能下降百分比(默认值:10)
percentilenumber否用于比较的百分位数(默认值:95)

详细介绍

playcal

test License: Apache-2.0 PyPI Python 3.10+ good first issues

为构建游戏的 AI Agent 提供的一站式开源工具包。 它为 Agent 提供了可靠的循环流程(编辑 → 等待编辑器真正稳定 → 检查更改是否确实生效 → 只查看更改内容 → 断言),还能根据真实玩家校准机器人游戏测试:“机器人觉得第 12 关很简单,但大约 41% 的玩家能通过(置信区间为 30–53%)。”

首先支持 Unity,基于 Unity 官方 CLI 构建。Godot 和 Unreal 之后也会接入同一核心。

AI Agent:请先阅读 llms.txt。

状态:预 Alpha(v0.1.0)。 目前,事件架构、ingest、report、calibrate、logs、loop、query、snap、observe、replay、baseline(性能和内存)、visual、tests、affected、lock、checkpoint、doctor、MCP 服务和 Unity SDK 均已可用;loop、query、snap、observe、replay 以及 SDK 帧记录器已在实时运行的 Unity 6000.3 Editor 上通过检查。下方标注为 计划中 的内容尚未构建。我宁愿展示一份简短而诚实的清单,也不愿列出一长串虚假的功能。进展请见路线图。

脚本从空预制件生成硬币:playcal observe 运行游戏,报告异常及其文件和行号,并以状态码 1 退出;修复后以状态码 0 退出

在实时运行的 Unity 6000.3 Editor 上执行的真实运行:文本是 playcal 输出的内容,等待时间经过了加速(括号中显示实际时长)。

为什么选择 playcal

在游戏编辑器中工作的 Agent 经常以一些乏味且反复出现的方式失败:

  • 编译或域重载完成前就运行工具,接着基于过时状态进行推理;
  • 编辑器未处于焦点状态,导致 Play Mode 停在第 1 帧,却显示“正在运行”;
  • 模态对话框阻止所有操作,却无人察觉;
  • 预制件从未保存,却声称“已完成”;
  • 单次读取控制台或转储层级结构就会消耗 10k+ 个令牌。

Unity 官方 CLI 和社区 MCP 服务提供了基础构件:运行、测试、捕获、控制台。但没有任何工具能将它们串联成一个可以处理这些故障的循环流程。playcal 正是这样的循环流程,还提供可在编辑器重启后保留的场景差异、性能和截图门禁、不稳定测试隔离,以及基于真人玩家的校准。有关该循环流程在实时编辑器上能做什么、不能做什么,请参阅基准测试。

安装

需要 Python 3.10+。

pipx install playcal
playcal modules

要安装最新的 main 版本,请使用 pipx install git+https://github.com/RizgarOzan/playcal。Unity SDK 通过 git URL 安装,详见 Unity SDK(现已可用)。

与 AI Agent 一起安装

将以下内容粘贴到 Claude Code、Cursor、Codex 或任何带有 shell 的 Agent 中:

Install playcal for this project:
1. Make sure pipx exists (python -m pip install --user pipx && python -m pipx ensurepath).
2. Run: pipx install playcal
3. Run: playcal modules --json  and show me the module list.
4. Read https://github.com/RizgarOzan/playcal#usage and tell me which commands fit this project.
Do not change project files yet.
MCP 服务和技能

playcal mcp 是一个 stdio MCP 服务。每个模块都会成为一个工具(playcal_ingest、playcal_logs、playcal_loop 等),其构建所用的参数定义与 CLI 相同,因此两者不会出现偏差。

服务的 initialize 回复会通过 MCP instructions 附带一份简短的“何时使用哪个命令”指南,内容与技能文件开头的说明相同,因此 Agent 知道应先运行 doctor,并通过 loop 执行对 Editor 的写入操作。

# or pick it from the MCP Registry as io.github.RizgarOzan/playcal
claude mcp add --transport stdio playcal -- playcal mcp
code --add-mcp '{"name":"playcal","type":"stdio","command":"playcal","args":["mcp"]}'

如果你的 Agent 可以使用 shell,技能文件比 MCP 更省资源(每个提示词中都不必包含工具列表):

mkdir -p .claude/skills/playcal && playcal skill > .claude/skills/playcal/SKILL.md

用法

每个命令都可以输出 --json,让 Agent 无需解析文本即可读取结果。

playcal ingest(目前可用)

此命令会根据 schema/event-v0.json 验证游戏事件。它接受 JSONL 文件,或包含 PC0: 数据块的纯文本。游戏内的“发送结果”按钮会生成这些数据块,玩家可以将其粘贴到任意聊天中。

playcal ingest sessions.jsonl --json
playcal ingest whatsapp-export.txt --paste

有效事件会写入 accepted.jsonl。每条验证失败的事件都会连同失败原因写入 rejected.jsonl。

此架构不接受未知字段,因此姓名、电子邮件、设备 ID、位置和 IP 地址都不会混入数据。请参阅 PRIVACY.md。

playcal report(目前可用)

按关卡统计通关率,分别按玩家和尝试次数计算。每个通关率都附带 95% 的 Wilson 区间,因为只有 10 名玩家时,单看“60%”几乎没有意义。

playcal report accepted.jsonl
playcal report accepted.jsonl --json

不同的 game_version 会分别统计,关卡按自然顺序排序(1、2、10)。

playcal calibrate(目前可用)

机器人可以在每个版本中游玩所有关卡,但你想知道玩家会表现如何。calibrate 会接收机器人运行数据,以及一组游玩过部分关卡的人类玩家数据,学习机器人通关率与人类通关率之间的映射,并预测机器人游玩过的每个关卡的人类通关率,每项预测都附带 95% 区间。

playcal calibrate --human panel.jsonl --bot bots.jsonl
# version    level      bot      predicted human   human seen
# 1          03         50%        35% [21%-52%]     36% n=25  *
# 1          06         67%        50% [33%-66%]            -
# leave-one-out: 5/5 human rates inside their interval, median error 4 points.

两个文件都使用相同的事件架构。该工具通过随机效应回归,按关卡拟合人类 logits 与机器人 logits,因此,如果机器人以自己的方式玩错某些关卡,每个区间都会变宽,而不会悄悄拉动拟合线。至少需要 3 个关卡的人类玩家数据(每个关卡至少 5 次尝试,使用 --min-attempts)。从 4 个关卡开始,工具还会依次留出每个关卡,并检查该关卡的实际通关率是否落在预测区间内;这行结果能告诉你其他预测有多可信。关卡数量少时,区间会很宽,这才是诚实的答案。

playcal logs(目前可用)

将 Unity 的 Editor.log(或 Player.log,或粘贴的控制台文本)提炼成 Agent 所需的信息。同一错误重复一千次会合并为一条记录,包含 count: 1000;此外还会单独提取最初的原因,因为后续错误通常都是连带问题。

playcal logs --json                         # this machine's Editor.log
playcal logs Editor.log --since "Reloading assemblies"
cat Player.log | playcal logs -
adb logcat -d | playcal logs -             # Android: only the Unity tag is read

退出代码 1 表示发现错误。在 CI 中,它也会以相同方式读取批处理模式构建日志,因此任务会因首个原因而失败,而不是被大量连带错误淹没:

- run: unity -batchmode -quit -projectPath . -executeMethod Build.Run -logFile build.log || true
- run: pipx run playcal logs build.log --json
playcal loop(可在运行中的 Editor 上使用)

loop settle 会等待 Editor 确实就绪:连续两次轮询都显示“就绪”,因为域重载可能会短暂闪断。当 Unity 正在编译或重载时,unity status 完全看不到 Editor,因此 settle 会通过 unity pipeline list 查明原因:安全模式、Unity 正在运行但卡在对话框上(editor_blocked),或者确实没有 Editor。若有两个 Editor 正在打开,也会报告出来,而不是在它们全部超时后才报错。

loop run 会检查 Editor 是否就绪,运行一条 unity command,再次检查,并可验证其操作结果。每次检查都会调用一次 editor status,该命令会直接说明 Editor 是否正在编译或重载;对于繁忙或无响应的 Editor,以及会触发重载的写入操作,settle 的轮询会作为后备检查:

playcal loop run "gameobject create" --verify "scene get_hierarchy" --expect "result.roots.*.name=Probe" --diff --json -- --name Probe

--verify 会在写入后运行一条读取命令;--expect 会断言其 JSON(path=value、path!=value、path~text,或仅写 path 表示“存在”;* 匹配任意项);--diff 会显示前后两次读取之间的变化。摘要会在 Editor 或 CLI 出现故障时标记 fault: "infra",在命令本身有误时标记 fault: "task",让 Agent 知道应该重试还是重新考虑。报告成功但实际上未生效的写入会以 verify_failed(退出码 5)失败。

我们在运行中的 Editor(Unity 6000.3,Pipeline 0.10.0-exp.1)上遇到两个陷阱:

  • 有编译错误的 Editor 仍会显示“就绪”。--compile 还会运行 unity recompile,并以 compile_error(退出码 6)失败,同时返回错误的 file、line、code、message。
  • 某些命令会在返回几秒后才开始重载(package resolve 会显示 requiresRecompile)。loop 能检测到这种情况,并等待重载开始后才再次信任“就绪”状态;--reload-wait SECONDS 会强制其他操作也等待,例如将脚本写入磁盘。

针对该 Editor 的读取、写入和 --expect 均通过。安全模式分支尚未在实际运行中遇到。

playcal query(现已可用)

当 Agent 只需要三个名称时,完整的层级结构转储可能会产生数万个 token。query 只保留你需要的内容,并说明删减了多少:

playcal query --unity "scene get_hierarchy" --from "result.roots.**" --where "name~Enemy" --select name --select activeSelf --json
playcal query scene.json --from data.objects --where "tag=Enemy" --select name

--where 使用与 loop --expect 相同的语法。* 表示列表中的任意项,** 表示任意深度的内容。

playcal checkpoint(现已可用)

在 Agent 执行步骤前保存项目文件,并可恢复任何一次保存。保存内容会作为提交写入私有引用(refs/playcal/checkpoints),因此不会更改你的分支、HEAD 或暂存区,也不会推送任何内容。遵循 .gitignore 规则。

playcal checkpoint save "before level 4 tweak"
playcal checkpoint list
playcal checkpoint diff latest
playcal checkpoint restore ~1     # one save back; prints how to undo the restore

恢复时只会重写有差异的文件,因此 Unity 不会重新导入所有内容。使用 loop run --checkpoint 时,每次写入前都会先保存;若摘要失败,会附带一条 undo 命令。Unity 只有在场景和预制件保存后才会将其写入磁盘,因此 Editor 中未保存的更改不会包含在检查点中。

playcal snap(现已可用)

按名称保存一次读取,之后即可查看发生了哪些变化。这样,“这次运行对场景做了什么?”只需几行就能说明,而不必对比两份转储:

playcal snap save before --unity "scene get_hierarchy"
# ... the agent works, or you play a round ...
playcal snap diff before --unity "scene get_hierarchy"     # exit 1 when something changed
playcal snap diff run-a run-b --json

Unity 的 instanceIds 只在同一个 Editor 会话中有效,因此跨重启或进入播放模式进行普通差异比较时,会显示所有对象都被移除并重新添加。snap 会排除 instanceId,并依次通过 hierarchyPath、path、guid、name 匹配对象;两个同名的同级对象会分别标记为 Enemy 和 Enemy#2。isDirty 也会被排除,因为它每次编辑都会变化。任何 JSON 文件都可用,不限于 Unity 读取结果。保存内容写入 .playcal/snaps/。

在运行中的 Editor 上,我们保存了一个场景、添加了一个对象,差异结果显示 added: result.roots[hierarchyPath=/SnapProbe]。之后我们再次保存、重启 Unity 并重新比较:没有变化,尽管所有 instanceId 都已更新,而且 Unity 还把一个对象的旧 ID 分配给了另一个对象。

playcal observe(在运行中的 Editor 上工作)

按下 Play,让游戏运行几秒,然后用一份摘要说明发生了什么:运行期间记录的控制台错误(折叠显示,并附上首个原因,类似 logs)、场景中的变化(遵循 snap 的规则),以及游戏运行期间针对场景执行的任何 --expect 检查。只有运行结果为红色时才会截取屏幕截图。

playcal observe --seconds 5 --expect "result.roots.*.name=Player"
# errors (task)
#   LogError: boss has no target  at Assets/Boss.cs:13
#   scene: 1 added, 0 removed, 0 changed
#   played: 5.0-19.4 s before the second read
#   shot: C:\MyGame\.playcal\observe\observe.png

退出码 0 表示正常,1 表示异常(控制台错误、检查失败、读取失败、Play Mode 未能启动),3 或 4 表示 Editor 本身出错(与 loop 的代码相同)。退出码 3 且原因是 console_unread,表示无法读取控制台,或控制台在运行期间被清空或溢出,因此错误可能被漏掉;observe 不会将这种情况判定为正常。控制台标记在 Play 启动前获取,因此更早的错误不计入。场景差异只会报告出来,因为游戏运行时场景本来就应该发生变化;如需据此拦截,请使用 --expect。--read CMD 会比较另一个读取结果,而不是 scene get_hierarchy;--no-play 会在相隔 --seconds 的两次读取之间进行比较,而不触碰 Play Mode;如果 Editor 已处于播放或暂停状态,则会监视当前状态并保持原样。observe 启动的 Play Mode 总会再次停止,即使运行在中途失败也是如此。played 表示游戏在第二次读取前运行了多久:至少达到第一个数值(从 Editor 表示正在播放时开始计时),最多达到第二个数值(从启动请求发出时开始计时)。

在运行中的 Editor(Unity 6000.3,Pipeline 0.10.0-exp.1)上,正常执行 observe --seconds 3 大约需要 33 秒。耗时并非来自重新加载:editor.playmode start 只有在启动完成后才会返回。真正耗时的是 CLI 本身,每次 unity 调用都需要 3–5 秒,不论执行什么操作。因此,observe 现在只调用 7 次,而之前需要 16 次:Play 前后的读取分别通过一次 job batch 调用完成;它会询问 editor status,后者会立即报告“正在播放”,而不是轮询 unity status,后者在此之后的几秒内可能不会列出任何 Editor。第一个版本大约耗时 70 秒,而在要求运行 3 秒时,游戏实际运行了约 26 秒。之后我添加了一个从缺失的 prefab 生成对象的脚本:observe 报告了带文件名和行号的 ArgumentException、场景中的 1 added,并在 45 秒内保存了一张真实的 Game 视图截图。还没有在实际运行中遇到的情况:项目将“Enter Play Mode Options”设置为跳过重新加载,以及控制台在运行期间溢出(发生时,摘要会显示 dropped)。

playcal replay(可在运行中的 Editor 上使用)

录制一次会话的输入,然后在每次更改后回放,查看游戏是否仍有相同表现。游戏通过 SDK 读取输入,并标记应保持一致的输出:

float move = PlaycalInput.Axis("move.x", Input.GetAxis("Horizontal"));  // live value, or the recorded one during a replay
if (PlaycalInput.Button("jump", Input.GetButton("Jump"))) Jump();
PlaycalReplay.Mark("score", score);                                     // compared frame by frame
playcal replay record jump-test --frames 600     # press Play, record 600 frames, leave Play
playcal replay run jump-test                      # replay with the same frame rate and seed, compare the marks
# diverged
#   frame 150: expected x=0.181579992 at 150, got x=0.150087655 at 150
playcal replay run jump-test --stop-at 120        # pause on frame 120 and stay in Play Mode
playcal replay step --frames 5                    # advance the paused Editor 5 frames
playcal replay check jump-test                    # compare the last result again, e.g. with --tolerance

Play 启动时,SDK 会从 .playcal/replays/request 读取请求,因此无需更改场景。录制或回放时,它会将 Time.captureFramerate 设为录制时的 fps,这样每帧的 deltaTime 都相同,并为 UnityEngine.Random 设置种子(你自行创建的 System.Random 不受影响)。退出码 0 表示匹配;1 表示结果不一致、回放提前结束、运行期间出现控制台错误,或 no_marks(没有标记,因此没有可比较的内容);3 表示 console_unread,与 observe 中相同。整数标记必须完全匹配;--tolerance 只会放宽小数标记的要求。应在 Update 中读取输入,而不是在 FixedUpdate 中,因为录制的是帧。

在运行中的 Editor(Unity 6000.3,Pipeline 0.10.0-exp.1)中,我使用一个探测脚本录制了 300 帧;该脚本的“实时”输入来自系统时钟,因此只有录制结果能够重现它。它的标记包括一个经过移动的值、Random.value,以及在盒子上滚动的 Rigidbody 小球的位置。两次回放都与录制结果完全匹配,即使设置了 --tolerance 0,物理模拟也完全一致。将帧 150 的一个输入改动后,运行结果显示帧 150 出现 diverged。--stop-at 120 会让 Editor 在回放第 120 帧时暂停,step --frames 5 则会将其推进到第 125 帧。与 observe 一样,录制 120 帧约需 42 秒,运行约需 37 秒,其中大部分时间花在每次 unity 调用所需的 3–5 秒上。尚未验证的情况包括:玩家使用键盘操作、在 Editor 重启后或另一台机器上回放、Player 构建版本、包含大量刚体或关节的场景,以及新的 Input System(以同样方式通过 PlaycalInput 传递其值)。由于 deltaTime 固定,如果机器无法跟上,录制内容会以慢动作运行,而不是丢帧。

playcal baseline(目前支持 JSON)

将性能运行结果保存为基线,之后只有当运行速度比基线慢且差异超过噪声范围时才会失败。输入逐帧样本,格式为 {"frame_ms": [...], "gc_alloc_bytes": [...]},或直接输入帧时间列表:

playcal baseline save level4 run-main.json
playcal baseline check level4 run-branch.json                 # exit 1 only when slower
playcal baseline check level4 run.json --percentile 99 --tolerance 5 --floor frame_ms=0.5

默认情况下,它会比较 p95,并对差值进行自助抽样。只有当 95% 区间的下限仍超过容差时,检查才会失败(默认值为 10%)。短时间运行中偶尔出现卡顿时,结果会是 inconclusive,这样一台噪声较大的 CI 机器就不会导致构建失败。--floor 会忽略绝对值小到无关紧要的性能下降。基线存放在 .playcal/baselines/ 中。Unity SDK 的 PlaycalFrames.Begin("level4") 会记录样本,而 Save() 会将其写入 baseline 可读取的位置。它的 used_memory_bytes 序列也会经过相同检查,因此需要更多内存的运行会以同样方式失败;添加 --floor used_memory_bytes=1048576 可忽略低于 1 MB 的增长。视觉门禁命令是下文的 playcal visual。

playcal tests(目前可用)

保存测试结果历史,并区分不稳定故障与真正的故障。它会读取 Unity 的 -testResults 写入的文件(NUnit3 XML)和 JUnit XML:

playcal tests check TestResults.xml        # exit 1 only on failures of tests that aren't flaky
playcal tests flaky                        # tests that passed and failed on the same commit

当某项测试在同一提交上既通过又失败时,它就会被视为不稳定测试,因此首次重跑并通过时就会被标记。之后,它的失败会显示为 quarantined,而不会让本次运行变红。它们仍会列出,以免被遗忘。check 会先根据历史记录判断本次运行,再将结果添加到历史中(--no-record 可跳过此操作)。历史记录位于 .playcal/tests/history.jsonl;请将其保存在 CI 缓存中,或提交到仓库。不稳定测试在后续提交中仍会处于隔离状态,直到你修复它并从历史记录中删除它的条目。

affected 会指出更改可能影响哪些测试程序集,因此小改动不必等待整个测试套件运行:

playcal tests affected                     # uncommitted work; --base main for a branch
# -runTests -assemblyNames "Game.UI.Tests"

它会读取项目的 .asmdef 和 .asmref 文件(名称和 GUID: 引用),将每个有更改的脚本归入其上方最近的 asmdef 所定义的程序集,并沿引用关系向外查找测试程序集。没有 asmdef 的脚本会归入 Assembly-CSharp 或 Assembly-CSharp-Editor。场景、预制件、设置或 Packages/manifest.json 的更改无法通过代码追踪,因此它会说明这一点并要求完整运行。不会扫描通过 file: 从项目外部引用的包。

playcal visual(目前支持 PNG 文件)

截图检查门控。将截图保存为基线,然后对比后续截图:

playcal visual save level4-start shot.png
playcal visual check level4-start shot.png     # exit 1 if more than 0.1% of pixels changed
# changed: 4096 pixels (0.1975%) differ, box [900, 300, 963, 363], see .playcal/visual/level4-start.diff.png

当一个像素的某个通道变化超过 --tolerance(默认值为 255 中的 8)时,该像素就会被视为已更改,因此抗锯齿和压缩噪声不会导致失败,而精灵缺失则会。--max-changed 设置允许变化的像素百分比上限。检查失败时,它会生成一张差异 PNG,将发生变化的像素以红色叠加在新截图的浅色副本上,并通过方框告知 Agent 应检查的位置。它仅使用标准库读取 8 位和 16 位 PNG(灰度、RGB、RGBA、不交错);比较 16 位截图时会按 8 位处理(取每个样本的高字节);一张 1920x1080 的截图大约需要 2 秒。请以固定分辨率截图,并暂停动画,否则门控会测量动画。对于时钟或 FPS 计数器等会自行变化的部分,可使用 --ignore x0,y0,x1,y1 忽略(可为多个方框重复此参数)。

playcal lock(目前可用)

当两个 Agent,或一个 Agent 和一个人,共同处理一个项目时,不应同时编辑 Level4.unity。lock 会在 .playcal/locks/ 下创建锁文件:

playcal lock take Assets/Scenes/Level4.unity --owner agent-a --ttl 30m   # exit 1 and who holds it, if taken
playcal lock check Assets/Scenes/Level4.unity --owner agent-b
playcal lock release Assets/Scenes/Level4.unity --owner agent-a
playcal lock list

同时锁定多个路径要么全部成功,要么全部失败。锁会过期,因此崩溃的 Agent 无法永久占用某个场景;重复获取自己的锁则会续期。设置 PLAYCAL_OWNER 可省去输入 --owner。只有所有 Agent 都在写入前请求锁定,锁才会生效;它无法阻止 Unity 或 git 写入文件。如果你使用 Git Bash,请传入不带前导 / 的 Unity 路径(或设置 MSYS_NO_PATHCONV=1),因为 Git Bash 会将 /SnapProbe 改写为 Windows 路径。

playcal doctor(目前可用)

遇到无法启动的情况时,请先运行此命令。它会检查 Python、git、Unity CLI、CLI 能检测到的每个 Editor(运行中、卡在对话框中或处于安全模式),并在使用 --project 时检查 SDK 是否位于 Packages/manifest.json 中。任一检查失败时,退出码为 1;--json 会以列表形式输出检查结果。

playcal doctor --project C:/Games/MyGame
# ok    unity cli: 1.0.0-beta.12
# fail  editor C:\Games\MyGame: running but not answering; usually a modal dialog is open in the Editor
Unity SDK(目前可用)

通过 Git URL https://github.com/RizgarOzan/playcal.git?path=/unity/com.playcal.sdk 安装,然后调用 PlaycalLog.Init()、log.LevelStart("04")、log.LevelFail(...)。“发送结果”按钮会调用 PlaycalLog.CopyResult()。用法见包 README。有一项测试会检查 C# 输出是否符合与 Python 端相同的架构。

运行性能测试时,PlaycalFrames.Begin("level4") 会记录帧时间、每帧分配的 GC 字节数和已使用内存总量(通过 ProfilerRecorder),并跳过前 30 帧。结束时调用 Save(),并将文件传给 playcal baseline。在 Editor 中测得的 GC 数值包含 Editor 自身的分配(在我们的测试场景中约为每帧 15 KB),因此请从播放器构建版本获取基线。

使用 playcal replay 时,通过 PlaycalInput.Axis / PlaycalInput.Button 读取输入,并用 PlaycalReplay.Mark 记录结果;详情请参阅 replay 部分。

对于视觉门禁,PlaycalShots.Capture("level4-start") 会将截图保存到 persistentDataPath/playcal/shots/。在批处理模式的测试运行中,它会改为渲染 Camera.main,因为屏幕截图捕获在此模式下不会触发。

路线图

模块功能状态
ingest事件架构、验证、粘贴数据块✅ 可用
report每关通过率、尝试次数、Wilson 置信区间✅ 可用
Unity SDK关卡事件、PC0“发送结果”行、删除我的数据、关闭未满 18 岁用户功能✅ 可用
loop编辑 → 稳定(编译/重新加载、焦点、模态框)→ 验证写入 → 差异比较 → 断言,生成一份 JSON 摘要✅ 已在运行中的 Unity 6000.3 Editor 上检查读取、写入和 --expect;尚未在实际运行中见过安全模式分支
calibrate / predict机器人指标 + 小规模真人面板 → 预测真人通过率及置信区间✅ 尝试通过率可用;尚未用真实面板验证
query从任意 JSON 结果(文件、stdin 或 Unity 读取命令)中选择、筛选和投影字段✅ 已在 JSON 和实时 scene get_hierarchy 读取中验证
checkpoint每个 Agent 步骤都是由 Git 支持、可还原的检查点✅ 可用(loop run --checkpoint 会在每次写入前保存)
logs控制台/Editor.log 摘要:去重、首个原因、错误类型✅ 可用
mcp, skill根据模块注册表生成 MCP 服务和 SKILL.md✅ 可用
snap保存读取结果,对比两次保存内容时排除会话 ID✅ 可用,已在 Editor 重启前后检查
observe整个场景的运行时差异,仅在出现错误时截图✅ 可用,已在运行中的 Unity 6000.3 Editor 上检查(错误、场景差异、截图)
baseline每次提交的视觉、性能(p95 帧时间、GC)和内存门禁✅ 性能和内存门禁、SDK 记录器,以及作为 playcal visual 的视觉门禁均可用
replay帧步进、录制输入、回放 → 回归测试✅ 可用,已在运行中的 Unity 6000.3 Editor 上检查(录制、匹配、偏差、停止和步进);尚未验证真人操作或跨重启运行
tests不稳定历史记录和隔离、受影响的测试✅ 历史记录、不稳定测试检测、隔离(NUnit3、JUnit),以及根据 .asmdef 图谱确定受影响的测试程序集
lock为多个 Agent 同时使用的场景/资源锁✅ 可用(带过期时间的建议性锁文件)
doctor在运行前检查设置✅ 可用
Godot, Unreal相同核心,全新后端以后推出

每一行对应一个模块。模块是 playcal.modules 组中的 Python 入口点,可选包含 Editor C#、独立测试和独立基准测试场景。新功能以新模块的形式提供,而不是新增需要安装的工具。

基准测试

bench/live.py 会在真实运行的 Editor 中,针对四种状态各执行一次写入,并用两种方式进行测试:一种是朴素 Agent,发送普通的 unity command 并相信返回的 success;另一种是运行 playcal loop run 并检查回读结果。之后会从 Editor 回读,确认写入是否真正生效,而不采信任何一方的说法。Unity 6000.3.9f1、Pipeline 0.10.0-exp.1、Windows 11,共运行 3 轮:

Editor 状态朴素方式生效次数playcal 生效次数错误成功次数(任一方式)朴素方式 / playcal 中位耗时
空闲3/33/302.2 秒 / 7.4 秒
域重载期间0/33/302.2 秒 / 33.6 秒(19–41 秒,重载本身耗时)
处于 Play Mode(拒绝写入)0/30/303.9 秒 / 28.0 秒
值无效(将字符串写入浮点数)0/30/304.3 秒 / 5.4 秒

简而言之,这说明:

  • Unity 重载期间发送的写入会被普通 CLI 丢弃,但通过 playcal 可以生效。6 次运行中,确实有 6 次命中了重载窗口。
  • Unity 的 CLI 比我预期的更严格。它明确拒绝了 Play Mode 下的写入和无效值,因此这两种方式都没有报告任何错误成功。playcal 的摘要还会标明失败类型(这两种情况均为 fault: task),让 Agent 知道不能只是一味重试。
  • 在空闲的 Editor 中,playcal 仍比直接调用慢约 3 倍。每次 unity 调用耗时约 3 秒,其中一部分时间用于 CLI 在 status 时发送使用情况分析数据。每次就绪检查只需调用一次 editor status,并将回读放在同一批次中,因此空闲状态下的写入调用次数从最初的 6 次降为 3 次:空闲状态耗时从 24 秒降至 15 秒,再降至 7 秒(#4)。重载期间的耗时主要来自重载本身;在这轮测试中,Pipeline 服务停止前耗时为 10 至 24 秒,在一次较空闲的测试中则为 14 秒。
  • 被拒绝和无效值这两种情况,耗时取决于 Editor 多久才会拒绝。Play Mode 下,写入请求等待约 25 秒后才被拒绝;无效值的结果在 5 秒后返回。
  • 读取几个对象的名称时:完整的 scene get_hierarchy 返回了 5216 字节,而 playcal query 返回了 682 字节。测试场景很小;场景越大,差距就越明显。

尚未测量的内容:作为第三组对照的 MCP 服务、Agent 完成整项任务的 Token 消耗,以及在实际运行的 Editor 中测试失去焦点的 Editor 和模态对话框两种情况。CI 中的 bench/sim.py 会用脚本模拟的 Editor 重放这些情况,此外还会测试一种写入已生效、但游戏在 Play 开始后抛出异常的情形(单独运行 loop 会将其判为成功;observe 则会发现问题)。这是一项回归防护测试,并非关于 Unity 的证据。

在自己的项目中运行(会创建并删除 BenchLive* 对象和一个临时脚本):

python bench/live.py --project-path C:/path/to/Project --repeat 3

使用 Agent 预测人类操作难度并非新想法。Roohi 等人(2021)曾针对一款拥有大量玩家的 Rovio 游戏进行这类研究。playcal 的新意在于使用小规模的面板(10–30 位朋友)、诚实的置信区间和开源代码来实现这一点。

参与贡献

最有帮助的是提供 Agent 与 Unity Editor 发生不同步的真实案例:Agent 运行了什么、它说了什么、实际情况又是什么。我们有相应的问题模板。除此之外,也欢迎贡献新模块;如果你熟悉 Godot 或 Unreal Editor 的 API,也欢迎开发相应的后端。

没时间提交 PR?在自己的项目中运行四条命令,并在 Testers wanted 中贴出输出结果,这比我自己再写一项测试更有帮助。

CONTRIBUTING.md 介绍了环境配置(pip install -e ".[dev]" 和 pytest;大多数内容无需 Unity)以及几条基本规则。也请阅读行为准则。安全问题请通过 SECURITY.md 反馈,不要发布到公开的问题区。

good first issue 标签下有适合新贡献者的小型任务。如果 playcal 帮你省下了一次调试,点个星标能让更多 Unity 用户发现它。

许可证

Apache-2.0。可用于商业游戏。专利授权旨在让工作室无需另行申请许可。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。