dsh-unity-vision
Verifieddsh-unity-vision · v0.1.2 · MIT
Unity eyes and hands for DSH agents: see the live Game View (on-demand single frame plus a rate-limited continuous preview) and drive it with a visible virtual mouse, then verify by reading game state back. Talks to Unity through the Unity CLI's warm `uni
Install
dsh plugin add dsh-unity-vision Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
- github/dvcode982/dsh-unity-vision 0 0 archived
Tags
Creators
Readme
dsh-unity-vision
给 AI agent 的 Unity 眼睛和手:看见实时画面(按需单帧 + 限频持续预览)、用虚拟鼠标操作用,再用游戏状态回读校验。 不是"截个图给你看",而是一条可断言、可复现、带负向对照的验证闭环。
1. 它解决什么问题
DSH 里的 agent 底座是纯文本模型,看不见 Unity 画面,也不能点按钮。本插件补上这条链路:
DSH agent
│ unity_* 工具(本插件,host half)
▼
dsh-unity-vision ── 串行化的保温通道:unity shell --protocol ndjson
│ (常驻进程,一行 JSON 请求 → 一行 JSON 信封)
▼
Unity CLI 1.0.0-beta.6 → com.unity.pipeline 0.8.0-exp.1(工程内)
│
├── 眼睛:官方 capture_game_view(source=screen,含 Screen Space Overlay 的 HUD)
├── 手 :工程内包 com.mistyruins.agentvision 的 agentvision_* 命令
└── 校验:agentvision_probe 的状态回读(场景名 / 帧号 / UI 读数)
两条硬约束决定了这个设计(都是实测结论,不是猜的):
- 眼睛用官方命令:
capture_game_view --source screen是唯一能拍到 Screen Space Overlay(HUD、按钮)的路径;source=camera会漏掉它们。 - 手必须编译进工程:用
eval_file注入每次都要跑 Roslyn 编译(约 4 秒/次),三步注入窗口长达 13 秒以上,期间真实鼠标的任何动作都会复位设备状态 —— 成功率只有约 2/3。编译好的[CliCommand]由游戏自己的帧循环保证时序,一次命令完成点击。
2. 安装(三步)
第 1 步:Unity 工程侧 —— 装上 com.mistyruins.agentvision
把 unity-package/com.mistyruins.agentvision/ 整个目录复制到工程的 Packages/ 下,并在 Packages/manifest.json 的 dependencies 里加一行:
"com.mistyruins.agentvision": "file:com.mistyruins.agentvision",
然后让 Unity 解析并编译(编辑器运行中时不会自动重扫 Packages/,必须加这一行 + 触发一次 resolve)。
该包依赖
com.unity.pipeline(Unity CLI 的编辑器侧组件:unity pipeline install --project-path <工程>)。 包内 14 个命令:agentvision_env / env_restore / inject / click / drag / key / vinput / capture / ui_list / ui_click / play_seq / play_seq_result / probe / overlay。
第 2 步:DSH 侧 —— 装本插件
包名 dsh-unity-vision 已发布到 npm(v0.1.1),所以「插件包名」字段 / dsh plugin add / pnpm add 之后直接填:
dsh-unity-vision
等价的手工写法 —— 在 profile 的 package.json 里加依赖与 bundles 条目:
"dependencies": { "dsh-unity-vision": "^0.1.1" },
"dsh": { "profile": { "bundles": [ "...", "dsh-unity-vision" ] } }
备选(想跟仓库最新提交而不是 npm 版本时):
// git 子目录规格
"dsh-unity-vision": "github:Dvcode982/dsh-unity-vision#path:/plugin"
// 或本地已 clone 的目录
"dsh-unity-vision": "link:<本仓库路径>/plugin"
⚠️ 包在仓库的
plugin/子目录、不在仓库根:不要用github:Dvcode982/dsh-unity-vision(不带#path:/plugin),那会装到仓库根,没有index.js与cordis.patch.yml,加载不起来。 必须完整重启 DSH:宿主条目在启动时组合投放,刷新浏览器不够。 bundle 条目是必需的——只加dependencies不会让插件被加载。
第 3 步:验证
unity_vision_status
它采用「先探后调」:会明确告诉你连上的是编辑器还是播放器、是哪个工程/平台、以及 14 个 agentvision_* 命令是否注册成功。装机不对时秒级给出原因,而不是让后续调用干等到超时。
配置
优先级:插件 config > 环境变量 > 自动探测。 三项都不设插件也能启动,只是"连哪个工程、用哪个 CLI"改由自动探测决定。
| 配置项 | 环境变量 | 不设时的行为 |
|---|---|---|
| 工程路径 | DSH_UNITY_PROJECT |
自动探测:采用 Unity CLI 报告的第一个实例(unity status 的 instances[0])。同时开着多个工程实例时请显式指定。 |
| Unity CLI 可执行文件 | DSH_UNITY_BIN(兼容 UNITY_BIN、DSH_UNITY_CLI) |
走 PATH:按 unity 查找。若解析到的文件不存在或起不来,unity_vision_status 会额外返回 UNITY_BIN_UNRESOLVED,并在 message 里附上当前解析到的值与来源(option / 环境变量名 / PATH)。 |
| 播放器进程名 | DSH_UNITY_RUNTIME |
非空才走播放器目标(所有 command 带 --runtime,unity_see 改走 agentvision_capture);否则连编辑器。 |
插件源码里没有任何作者机器的绝对路径兜底,路径只来自上面三个来源。
插件 config(写在 profile 的插件配置里,优先级最高):
{ "projectPath": "<Unity 工程绝对路径>", "unityBin": "<Unity CLI 绝对路径>", "runtime": "YourGame.exe" }
环境变量 —— Windows:
set DSH_UNITY_PROJECT=D:\path\to\YourUnityProject
set DSH_UNITY_BIN=C:\Program Files\Unity\Hub\Editor\<version>\Editor\Unity.exe
set DSH_UNITY_RUNTIME=YourGame.exe
环境变量 —— bash:
export DSH_UNITY_PROJECT=/path/to/YourUnityProject
export DSH_UNITY_BIN="/Applications/Unity/Hub/Editor/<version>/Unity.app/Contents/MacOS/Unity"
export DSH_UNITY_RUNTIME=YourGame.exe
测试/验收脚本(§6.1)必须先设
DSH_UNITY_PROJECT(播放器类脚本另需DSH_UNITY_RUNTIME), 否则启动时即以退出码 2 报出可操作的错误。进度与截图默认落在系统临时目录<tmp>/dsh-unity-vision/(m0/与evidence/两个子目录),不再写进仓库;可用DSH_UV_OUT改根目录。
3. 工具清单
| 工具 | 作用 | 关键点 |
|---|---|---|
unity_vision_status |
先探后调 | 按目标分别探测:编辑器走 status,播放器走 runtime_status(不再要求存在编辑器实例) |
unity_see |
取一帧,返回文件路径 | 编辑器用官方 capture_game_view(默认 source=screen,含 HUD);播放器自动改用 agentvision_capture;默认把 PNG 写到工程外,不污染工程 |
unity_watch |
按需的限频持续预览 | start/stop/status;这是轮询预览(每 tick 一次取帧),不是视频流 |
unity_probe |
判定用的状态回读 | 场景名 / 帧号 / Screen 尺寸 / 鼠标设备状态 / uGUI point·click 读数 |
unity_see |
取图(两种画面) | view="game"(默认)取 Game 视图 = 玩家 display(含 Overlay HUD);view="scene" 取编辑器 Scene 视图(含网格/辅助图标、没有 HUD)。两者尺寸可能相同但内容完全不同,用 sha256 与画面内容区分。注意:capture_scene_view 不接受 --source/--width/--height(传了会报 INVALID_COMMAND_ARGS) |
unity_env |
环境自检与复原 | fix(默认)清暂停、保证帧推进、把输入路由指向 Game View;restore 把工程改回原样(插件卸载时也会自动 restore)。unity_ui 不需要它 |
unity_ui |
确定性 UI 交互(首选) | action="list" 列出可交互控件(名称/文字/屏幕中心/是否可点);action="click" 按文字或名称点击按钮:Unity 侧用 EventSystem + ExecuteEvents 派发真实指针事件,不经过 Input System,因此不受物理指针干扰,并回报它到底点了哪个对象(编辑器与独立播放器均已实测,见 §7.2) |
unity_mouse |
虚拟鼠标:move / click / drag / look | 默认吃"图内像素"(左上原点)并自动换算;参数严格校验(非法 action/coord/button 一律拒绝,绝不退化成点击);expectScene 才产生断言,且目标场景已经是它时会被拒绝(无法归因)。按钮/菜单请优先用 unity_ui,unity_mouse 留给需要真实设备状态的操作(第一人称视角、拖拽) |
unity_key |
键盘 | Input System 键名(Enter/Space/W…) |
unity_overlay |
虚拟光标可见化 | 在游戏内画绿十字光标 + 黄圈涟漪,source=screen 能拍到 —— 截图因此能证明"我的手在哪" |
可靠性契约(重要,请照此使用)
unity_mouse 的返回把三件事分开报告,不再用一个 ok 混淆:
| 字段 | 含义 |
|---|---|
status |
verified / sent-unverified / assertion-failed / action-failed / precondition-failed / invalid-args / not-playing / unresolved-coords / probe-failed |
verified |
是否真的被状态回读证实(只有 expectScene 通过才为 true) |
ok |
命令被接受 且(若请求了断言)断言通过 |
✅ 正确用法:unity_mouse { action:"click", x, y, expectScene:"<目标场景>", maxAttempts:3 }
→ 动作 → 等待 → unity_probe 回读 → 不匹配则重试;返回 status / verified / attempts / sceneBefore / sceneAfter
⚠️ 没有 expectScene 时返回 status="sent-unverified"、verified=false —— 这只代表"已发送",不代表成功
❌ 若目标场景已经是 expectScene,会直接返回 precondition-failed 并**拒绝执行**,避免"点了没变也算成功"的假阳性
可靠性现状(实测,别当成承诺):UI 点击一律走 unity_ui(EventSystem 直发),编辑器与独立播放器都已实测;
unity_mouse 的指针注入在播放器下稳定(位置回读 positionMatched=true、uiPoint 跟随),
在编辑器下受共享物理指针干扰而不可靠(受控对照:直接发命令与走本工具各失败,mouse 回读仍是 (0.00,0.00)、uiPoint 恒为 (0.00,0.00),T3 同类)。
要做可信的端到端验收,优先打播放器目标(播放器侧的完整实测见 §7.2)。
以上结论只覆盖指针/视角(Value 类控件);Button 类动作在任何环境下都不可注入(引擎级位域限制,见 §5 第 3 条)。
4. 插件替你处理的 5 个"静默失败"陷阱
这些坑都会让注入看起来成功但毫无效果、且不报错。本插件在每次动作前调用 agentvision_env 自动处置:
| # | 陷阱 | 症状 | 处置 |
|---|---|---|---|
| T1 | Play Mode 被静默暂停(EditorApplication.isPaused) |
deltaTime=0、帧号完全不动、一切注入无效 |
清除暂停 |
| T2 | editorInputBehaviorInPlayMode 默认要求 Game View 有焦点 |
焦点在别的窗口时输入被改道给编辑器 | 运行时改为"总是送给 Game View" |
| T3 | 默认 updateMode 下原生鼠标每帧覆盖注入值 |
注入坐标读回永远是原生值 | 按需切换时序 |
| T4 | Application.runInBackground 默认 false |
编辑器失焦时帧号卡死,而 deltaTime 显示陈旧值骗人 |
强制打开 |
| T5 | 厂商 simulate_pointer 排队后自己调 InputSystem.Update() |
事件在错误的更新阶段被消费,uGUI 永远看不到 | 改用自研注入:只排队、不 Update |
| T6 | 保温 shell 会破坏非 ASCII 参数 | 中文参数变成乱码(开始游戏→寮€濮嬫父鎴),还会把紧随其后的参数吞进同一个值 |
只在宿主侧解析文字:unity_ui 先用 list 取回控件表(输出中文正常),本地匹配后只把 ASCII 下标发给 Unity |
判断"游戏在不在跑"只能看
frameCount是否递增 ——deltaTime/isPaused/timeScale/isFocused都可能在"帧不动"时显示正常。这是本项目踩过的最贵的坑。
5. 已知限制(诚实清单)
- 设备级注入在编辑器里不可靠(已被替代方案绕开)。受控对照:同一次 Play 会话里,直接发
agentvision_click与走unity_mouse各失败,注入后mouse回读仍是(0.00,0.00)、uiPoint恒为(0.00,0.00),属共享物理指针干扰(T3 同类)。结论:按钮/菜单一律走unity_ui(EventSystem 直发,编辑器与播放器都确定生效,实测MainMenu → VerticalSlice_Castle且 status=verified);unity_mouse只用于必须读真实设备状态的操作(第一人称视角、拖拽)。原先写的"75% / 重试 3 次 ≈98.4%"是 4 次样本外推,已撤回。 - 单条命令约 4 秒(实测,非文档所说的 ~100ms)。瓶颈在管线命令往返本身,不在进程启动;因此"连续预览"的间隔不可能很低(
unity_watch默认 1500ms)。 - 虚拟设备的按键(Button 类动作)读不到——引擎级限制,非可修 bug。
InputState.Change、QueueStateEvent都试过;键盘/鼠标按键是位域控件,引擎明确拒绝控件级事件(实测报错Cannot send delta state events against bitfield controls),而整设备写入又驱动不了 Button 类动作(已排除"用户键鼠抢占"与"读首个控件"两种解释)。影响面:玩法移动/按键交互(MoveUp/Attack等)不可注入,需在游戏侧加适配器;Value 类控件不受影响——鼠标位置与delta可注入(视角实测 14.4°/秒),UI 则改走unity_ui的 EventSystem 直发(完全不依赖 Input System)。 - overlay 的涟漪是静态的(无自定义 MonoBehaviour 做动画);光标位置是精确的(像素级验证误差 ≤1px)。
capabilities里没有视频流:agent 是"逐张读图",实时视频流只对人有用。
完整的缺陷史(F01–F16)与逐项整改记录保存在开发过程的
复核报告.md/项目体检报告.md中;这两份不随公开仓库分发,本 README 只保留使用相关的结论。
Unity.InputSystem的 legacy 输入(UnityEngine.Input)只读、不可注入;若工程的 Active Input Handling 设成旧版独占,注入不会生效。另注:本工程PlayerInputReader会同时读旧后端并取较大值,因此仅凭"视角转了"不能证明是 Input System 注入生效——要证明必须像agentvision_play_seq那样直接读游戏自己的动作值(实测gameLook=(2,0)即为注入生效的直接证据)。
6.1 可复跑的验证脚本
| 脚本 | 作用 | 最近结果 |
|---|---|---|
node host-test.mjs |
宿主面集成测试:模拟 cordis 上下文跑 apply(),校验 9 个工具的形状 / 挂载日志 / 清理 effect,不需要改 profile |
7/7 检查(注册 9 个工具) |
node contract-test.mjs |
宿主契约回归(0.1.0「9 个工具全灭」的拦截器):注册契约 + output.render 必须返回内容块数组 + 恒等回退下 schema 的 required 提升 |
全绿 |
node contract-test.mjs --live |
同上,且真跑 9 个工具的离线分支并校验返回值符合 output.schema |
全绿(要求本机没有 Unity 在跑;CI 跑这一档) |
node contract-test-adversarial.mjs |
对抗式契约测试:拿宿主真校验器(拿得到时)与逐行移植版交叉比对 + 变异测试 | 见脚本输出 |
node e2e-agent-session.mjs |
真人级端到端(需要 Unity 在跑):走 apply() 注册出的真工具,按真实会话顺序跑「状态 → 探针 → 两个视图取帧 → 帧号 → uGUI → 环境只读」,并要求两张图真的落盘且可分辨;含一条负向对照(点不存在的控件必须显式失败)。默认只读,--with-input 才跑 Play Mode 点击闭环 |
真 Unity 上 18/18 |
node scripts/verify.mjs |
门禁的唯一定义(CI 跑的就是这一条)。全绿时最后一行是 ✅ 门禁全绿,可以提交了。 |
见 scripts/verify.mjs |
node smoke.mjs --json |
只读冒烟:连接 → 命令注册 → 环境自检 → 状态回读 → 取帧(落到工程外)→ 坐标换算 → overlay 门禁 | 7/7 |
node smoke.mjs --click --json |
追加闭环:进 Play Mode → 点击并断言场景切换 → 负向对照 → AC-5 光标合成校验 | 曾 10/10(编辑器点击不可靠,见 §5.1) |
node player-test.mjs |
播放器目标(直连底层客户端):probe / see / inject 并回读 / status | 4/4 |
node soak.mjs 30 30 player |
AC-8 长跑:每 30 秒采样帧号、场景、输入漂移(注入已知坐标再回读)、内存;每 5 个点走一次取帧 | 严格判据未通过(见 §5) |
soak.mjs默认打播放器:编辑器里 T3(默认ProcessEventsInDynamicUpdate下原生鼠标每帧覆盖注入位置) 会让"位置回读"永远读回原生坐标,那是既有的环境行为、不是桥接缺陷,所以漂移项只在播放器目标下参与判定。
这些脚本都从环境变量取路径(见上面的「配置」一节):跑之前先设
DSH_UNITY_PROJECT,播放器类脚本 (player-test.mjs/soak.mjs … player)另需DSH_UNITY_RUNTIME。 未设置时脚本立刻以退出码 2 报错,插件源码里不再有任何本机路径兜底。 进度与截图默认写到<tmp>/dsh-unity-vision/{m0,evidence}/(可用DSH_UV_OUT覆盖),不再污染仓库。
6.5 玩法输入(虚拟设备):注入层可用,但游戏不消费 —— 如实记录的负结果
已实现:agentvision_vinput(虚拟 Keyboard/Mouse 设备,整设备 InputState.Change 写按键、逐控件写 delta 并逐帧重写)、agentvision_play_seq / agentvision_play_seq_result(在游戏循环内部执行输入脚本并逐帧采样相机位姿/按键/设备状态/游戏动作读数)。
为什么要有 play_seq:命令行往返一次约 4 秒,用"发命令 → 外部 sleep → 再探测"根本量不准一个 1.5 秒的保持动作(第一次探测就已经是 4 秒后)。采样必须放进游戏帧循环。
实测结论(每一步都有数据):
| 观察 | 数据 |
|---|---|
| 我们的虚拟键盘控件确实被写入并保持 | vkW=1.0(全程) |
| 游戏的动作只有在一局真正在跑时才启用 | 主菜单 acts=12;经「开始游戏」进入后 acts=27,seenBy 出现 MoveUp/MoveDown/MoveLeft/MoveRight/Sprint/Crouch/Interact/Reload/Drop/UseSanityItem |
| 但游戏自己的动作读不到我们的值 | 同一采样点:vkW=1.0 而 MoveUp=0.0,moved=0(连续 13 秒) |
| 所有"看起来在动"的位移都与我方输入无关 | 配对对照(同一稳定态,按 W vs 不按)均为 moved=0;真正的 0.6~0.9 单位位移发生在出生后与结算后,此时按键状态不变 |
闸门位置:PlayerInputBridge 只在 GameEvents.RunStarted 时调用 PlayerInputReader.Enable(),RunEnded 时 Disable()——所以必须经主菜单开始一局,直接打开关卡场景测出来的都是"No"。
未解决的真实缺口:设备状态写对了、动作也绑上了我们的设备,但 InputAction.ReadValue 仍返回 0(怀疑与 Button 类动作的"actuating control"解析有关,尚未证实)。因此本插件目前不能驱动该工程的玩法移动/视角;能确定驱动的是 UI(unity_ui)。
两次自我纠错(记在这里以免重犯):我曾两次把 0.6 单位位移当成"按键生效"——第一次靠"负向对照在另一个状态下测"侥幸发现,第二次靠配对对照 + 逐帧采样彻底证伪。教训:任何"输入生效"的结论,必须在同一稳定态下做 A/B,并同时读设备侧与游戏侧的值。
6.5.1 硬约束:必须与"正在使用本机的用户"共存
用户明确要求:注入方案必须与他的实际操作共存,不能靠停用/独占物理键鼠,也不能要求他停手。
takeover_mouse/takeover_keyboard默认 false,参数说明标注 DANGEROUS on a machine someone is using;仅限独占的 Player 进程。- 逐帧采样加入
phys=(物理鼠标)与physKb=(物理键盘):"隔离是否生效"只认这两个字段,不采信命令自报。 - 安全界定(此前表述夸大了,此处更正):
InputSystem.DisableDevice只影响该 Unity 进程内部的 Input System,不触碰操作系统层;用户其他程序从不受影响,最坏情况仅为"在那个 Unity 窗口里点不动"。编辑器进程退出后该状态随之消失。
按推荐度的共存路线:① 独立 Player 进程 + 窗口不聚焦(用户在其他窗口工作时系统不会把按键投递给 player 窗口;注入在 player 进程内进行,无需停用任何设备);② 游戏侧适配器(agent 输入直接喂 PlayerInputReader,完全不碰 Input System 设备,零冲突);③ 申请独占时间窗(最差,默认不采用)。
范式对照:UI 层一直稳定可靠,正是因为 unity_ui 走 EventSystem + ExecuteEvents——零设备、零抢占。玩法层应向它看齐,而不是去和用户的键鼠抢设备。
6.4 本轮新增:确定性 UI 路径(把"编辑器点不动"这件事解决掉)
问题:设备级注入在编辑器里受共享物理指针干扰,点按钮时好时坏(复核 §8.3 的受控对照证明是环境问题、不是工具问题)。
做法(GitHub 一侧没找到可直接用的现成方案;官方 VirtualMouseInput 的配方已读并记录,但设备路线仍卡在"让 uGUI 回调被求值"):
在 Unity 包里新增两条命令,走 EventSystem + ExecuteEvents,完全不碰 Input System:
| 命令 | 作用 |
|---|---|
agentvision_ui_list |
按根对象递归扫描可交互控件(不用 FindObjectOfType,遵守工程规范),输出 名称 / 可见文字 / 屏幕中心 / 是否可点 |
agentvision_ui_click |
按 index(ASCII 安全)/ label / name / 屏幕坐标定位,派发 pointerDown → pointerUp → pointerClick,并回报它点了哪个对象、由谁处理 |
宿主侧 unity_ui 把"按文字找按钮"放在本地完成(因为保温 shell 破坏非 ASCII 参数,见陷阱 T6),只发 ASCII 下标。
实测成果:
主菜单 ui_list → 3 个控件:StartButton「开始游戏 [Enter]」/ BuyButton「购买理智道具(50 金币)」(不可点,金币为 0) / QuitButton「退出」
unity_ui click label="开始游戏" expectScene="VerticalSlice_Castle"
→ status=verified clicked=StartButton MainMenu → VerticalSlice_Castle
进游戏后 ui_list 仍可用 → 认出结算页两个按钮「再次开始 [Enter]」「返回主菜单」
截图存档于开发过程的 evidence/m4/outcome-castle-1024.png(该目录不随公开仓库分发;画面与 ui_list 读到的文字一致)
7. 播放器路径(AC-7,已验证)
插件不只能连编辑器,也能连独立播放器。配置一个运行时目标即可:
// 插件配置
{ "runtime": "YourGame.exe" } // 或环境变量 / 每会话指定
设了它以后:
- 所有
command ...会带上--runtime <进程名>; unity_see自动改走agentvision_capture(我们包里的ScreenCapture命令),因为官方的capture_game_view是编辑器专有命令(它反射 Game View,播放器里不存在)。
7.1 让播放器带上网关(工程侧,需要时开启、验证后复原)
管线的运行时支持是opt-in的,要过两道门(PipelineRuntimeBuildProcessor 源码级确认):
- 配置:
ProjectSettings/Packages/com.unity.pipeline/RuntimePipelineConfig.json里enableInBuilds: true(JSON 编写;也可用unity command set_runtime_pipeline_settings --settings {…} --confirm true)。 未创建时构建日志会出现 "Pipeline will be disabled in Player builds"。 - 符号:非 Development 构建还需要把
ENABLE_RUNTIME_PIPELINE加进 Player Settings 的 Scripting Define Symbols (官方注释称它是"让管线进入非 Development 构建"的 opt-in 符号)。
⚠️ 安全:开了之后播放器里会跑一个 HTTP 服务器(默认 7900 起)。只对开发/QA 构建开启,验证完请复原这两项。 已排除的弯路:只开
enableInBuilds会被第二道门拦下;unity build --args "-development"实测仍不带Unity.Pipeline.dll。
7.2 播放器侧的实测结果(2026-10-11 重打播放器后重测)
| 能力 | 结果 |
|---|---|
| 发现与连接 | 播放器启动后写出 .unity-pipeline-runtime-port(含 pid/port/platform/unityVersion/buildGuid/startedAt/lastHeartbeat);CLI 用顶层 --runtime <进程名> 路由到它。注意:CLI 没有 runtime_status 命令(F15),状态探测改走 --runtime 通道 |
| 状态探测 | ✅ unity_vision_status → connected=true, target=player, scene=VerticalSlice_Castle, frame=1446, isPlaying=true, 14 条命令 |
| 取帧 | ✅ unity_see → mode=player,1024×576 / 73 KB。命令把 PNG 写在播放器侧(persistentDataPath),宿主再复制到工程外临时目录(F16 修复:不要把宿主路径当参数传进播放器,会因路径含空格/引号报 ArgumentException: Name has invalid chars) |
| UI 列举 | ✅ agentvision_ui_list → RestartButton「再次开始 [Enter]」(513,269) / MenuButton「返回主菜单」(767,269),interactable=True |
| UI 点击 | ✅ agentvision_ui_click --name MenuButton → 面板消失(ui_list 2 → 0)。A/B 对照:不点击时 11 秒后仍为 2,故变化确由点击造成。该按钮在目标工程里按设计只 Hide() + 弹提示、不切场景(见 ResultPresenter.ReturnToMainMenu),所以场景名不变是正确的 |
| 校验 | agentvision_probe → scene / frame / screen / camera / mouse / overUI / uiPoint |
| 编辑器专有命令 | ❌ 播放器里不可用:capture_game_view(反射 Game View)、eval / eval_file("Runtime code compilation only supported on Desktop development builds")——所以播放器的眼睛必须靠编译进包的 agentvision_capture |
播放器构建的工程侧前置(验证完请复原):ProjectSettings/Packages/com.unity.pipeline/RuntimePipelineConfig.json 里 enableInBuilds: true(该文件默认不存在,需新建)+ Standalone 的 Scripting Define Symbols 加 ENABLE_RUNTIME_PIPELINE。缺任何一项,非 Development 构建都不会带运行时管线。
构建方式提示:
unity build会另起 batchmode 编辑器,在部分授权环境下会失败(Access token is unavailable)。可行替代:在已运行的编辑器里用BuildPipeline.BuildPlayer构建(本轮的 112 MB 播放器即如此产出,耗时约 24 秒)。
8. 工程侧改动与回滚
装上本插件对 Unity 工程的改动只有一处:
| 改动 | 内容 |
|---|---|
Packages/manifest.json |
加一行 "com.mistyruins.agentvision": "file:com.mistyruins.agentvision" |
Packages/com.mistyruins.agentvision/ |
新增自研包(4 个文件) |
AC-7 用到的两项工程设置不属于交付内容:
RuntimePipelineConfig.json(enableInBuilds)与ENABLE_RUNTIME_PIPELINE脚本宏都是临时开门用的,验证完必须复原 —— 否则你之后每个构建都会 在播放器里跑一个 HTTP 服务器。复原后Assets下可能残留空目录Assets/Settings/Pipeline/Resources/(构建处理器只清资产、不清目录骨架),记得一并删掉。
回滚:backup/<工程名>-src-<日期>/rollback.cmd(279 文件 / 1.04 MB 的装包前源码快照)。
⚠️ 回滚脚本必须带
robocopy /PURGE。2026-10-10 的沙箱演练证明:只用/E时 robocopy 只做"复制 + 覆盖",不会删除目标里多出来的文件 —— 回滚后Packages/com.mistyruins.agentvision/会残留成孤立目录(manifest 已回滚)。脚本已修正为/E /PURGE,实测Assets根与备份逐项一致。
9. 许可与致谢
- 本插件:MIT
- 注入手法参考 FunplayAI/funplay-unity-mcp(MIT,
InputSimulationFunctions.cs) - 截帧的降采样/Screen Space 取舍参考 CoplayDev/unity-mcp(MIT,
ScreenshotUtility.cs) - 工程规范与模板参考
dsh-gomoku(本工作区项目,未公开)与 dsh-ego-browser - 详见 THIRD-PARTY-NOTICES.md