Unity MCP 中 refresh_unity 工具详解:资产库刷新、脚本编译与就绪等待机制
2026/9/15 2:28:09 网站建设 项目流程

Unity MCP 中 refresh_unity 工具详解:资产库刷新、脚本编译与就绪等待机制

【免费下载链接】unity-mcpUnity MCP acts as a bridge between AI assistants and your Unity Editor. Give your LLM tools to manage assets, control scenes, edit scripts, and automate tasks within Unity.项目地址: https://gitcode.com/GitHub_Trending/un/unity-mcp

refresh_unity是 Unity MCP(MCPForUnity)核心工具组(core)中负责**显式触发 Unity 资源导入(Asset Database Refresh)与脚本编译(Script Compilation)**的关键能力,通常用于完成文件写入、资源落地、脚本编辑后的收尾动作。读完本文,你将掌握该工具的完整参数语义、从 Python 服务端到 Unity 编辑器的底层调用链路、断连/重载场景下的容错恢复策略,以及如何结合mcpforunity://editor/state资源做就绪等待,从而在 AI 工作流中安全可靠地使用刷新能力。

工具定位:显式触发刷新与编译的"副作用型"工具

Unity MCP 的绝大多数工具(如manage_assetmanage_script)只负责在编辑器中执行具体操作,而refresh_unity专门承担事后同步职责:请求 Unity 资产数据库重新导入,并可选地触发一次脚本编译,同时还可以阻塞等待编辑器恢复就绪状态。

在 Unity 侧实现 的注释中,它被明确标注为 "side-effectful and should be treated as a tool"(具有副作用、应按工具对待),并以[McpForUnityTool("refresh_unity", AutoRegister = false)]声明——这意味着它不会在 Unity 端自动注册进工具列表,而是由 Python 服务端在需要时通过 TCP 桥接显式下发refresh_unity命令。

典型使用场景包括:

  • 通过外部进程(如 Agent、CI 或脚本)向Assets/目录写入新资源后,强制 Unity 重新导入;
  • 修改脚本文件后,请求CompilationPipeline触发一次显式编译;
  • 在执行一系列变更后,等待编辑器回到ready_for_tools状态,再进行后续工具调用。

参数详解:mode / scope / compile / wait_for_ready

根据 官方参考文档(该文档由tools/generate_docs_reference.py从 Python 工具注册表自动生成),工具的四个参数如下:

参数类型必填说明
modeLiteral['if_dirty', 'force']刷新模式:仅在检测到变更时刷新,或强制刷新
scopeLiteral['assets', 'scripts', 'all']刷新范围:仅资产、仅脚本、全部
compileLiteral['none', 'request']是否请求编译:不请求、请求一次编译
wait_for_readybool若为 true,则等待mcpforunity://editor/state返回data.advice.ready_for_tools为 true

各参数的默认值与底层语义

从 服务端定义 可以看到实际默认值为:mode="if_dirty"scope="all"compile="none"wait_for_ready=True。而 Unity 端处理 读取参数时的兜底默认值为mode="if_dirty"scope="all"compile="none"wait_for_ready=false——两者不一致意味着:若客户端不传wait_for_ready,服务端仍会自行执行一次防御性的就绪等待轮询(详见下文"服务端容错与恢复")。

  • modeif_dirtyforce目前在实际执行上等价。Unity 侧代码注释明确指出 "Best-effort semantics: if_dirty currently behaves like force unless future dirty signals are added",即当前实现并未接入真正的脏标记信号,两种模式都会触发刷新,语义保留为向后兼容的扩展点。
  • scope
    • assets:执行AssetDatabase.Refresh(ForceUpdate | ForceSynchronousImport)
    • scripts:跳过重量级全量刷新("For scripts, requesting compilation is usually the meaningful action"),把动作交给compile参数;
    • all:先按上述逻辑处理,若未触发刷新则补一次轻量级AssetDatabase.Refresh(ForceSynchronousImport),确保调用返回前刷新完成、避免 Unity 后台化时卡住。
  • compilerequest时调用CompilationPipeline.RequestScriptCompilation(),触发一次脚本编译与可能的 Domain Reload(程序域重载)。
  • wait_for_ready:控制是否等待编辑器就绪。等待逻辑在 Unity 端与 Python 端各有一层实现,二者互为兜底。

从服务端到编辑器的调用链路

refresh_unity的完整执行链路分为 Python 服务端与 Unity 编辑器两端,核心流程如下:

  1. 路由定位:服务端通过get_unity_instance_from_context(ctx)从中间件状态中取出unity_instance(由set_active_instance设置、UnityInstanceMiddleware注入),确定命令要发往哪个 Unity 实例(多实例场景支持)。
  2. 下发命令:调用unity_transport.send_with_unity_instance(..., "refresh_unity", params, retry_on_reload=False)。注意这里显式传入retry_on_reload=False——因为refresh_unity本身就会触发编译/重载,若在重载时重试会引发多次重载(代码注释引用 issue #577)。
  3. Unity 端执行:编辑器收到命令后在主线程执行刷新与编译(见 RefreshUnity.HandleCommand)。
  4. 就绪等待:若请求了等待,Unity 端通过EditorApplication.update轮询EditorStateCache.GetActualIsCompiling()EditorApplication.isUpdatingTestRunStatus.IsRunningEditorApplication.isPlayingOrWillChangePlaymode四个条件,全部满足才判定就绪;Python 端则轮询mcpforunity://editor/state资源作为第二层保障。
  5. 清理状态:就绪恢复后,服务端会调用external_changes_scanner.clear_dirty(inst)清除该实例的外部变更脏标记。

调用关系示意

MCP Client (LLM) │ tools/call refresh_unity ▼ Python Server: services/tools/refresh_unity.py │ TCP Bridge (send_with_unity_instance, retry_on_reload=False) ▼ Unity Editor: RefreshUnity.HandleCommand ├─ AssetDatabase.Refresh(...) ├─ CompilationPipeline.RequestScriptCompilation() └─ WaitForUnityReadyAsync (EditorApplication.update 轮询) ▼ 返回响应: refresh_triggered / compile_requested / resulting_state / hint

Unity 端源码级行为细节

测试运行时的安全保护

进入处理逻辑前,Unity 端会先检查TestRunStatus.IsRunning(见 RefreshUnity.cs#L28-L35)。若测试正在运行,直接返回ErrorResponse("tests_running", { retry_after_ms = 5000 }),避免刷新/编译打断测试执行。

刷新与编译的执行分支

// MCPForUnity/Editor/Tools/RefreshUnity.cs(节选) bool shouldRefresh = mode is "force" or "if_dirty"; if (shouldRefresh) { if (scope == "scripts") { // 脚本范围:跳过全量刷新,编译才是关键动作 } else { AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport); refreshTriggered = true; } } if (compile == "request") { CompilationPipeline.RequestScriptCompilation(); compileRequested = true; } // scope == "all" 且尚未刷新时,补一次同步刷新 if (scope == "all" && !refreshTriggered) { AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); refreshTriggered = true; }

关键点在于ForceSynchronousImport:它保证刷新在命令返回前同步完成,避免 Unity 后台化(backgrounded)时刷新被延迟导致调用方误判失败。

Unity 6+ 的特殊处理

Unity 端针对 Unity 6 及以上版本做了一处专门修复(RefreshUnity.cs#L80-L89):当compile="request"跳过wait_for_ready等待。原因是 Unity 6+ 中EditorApplication.update轮询在 Domain Reload 后无法正确存活,等待会引发无限编译循环。该场景下改为立即返回,由客户端自行轮询editor_state。此行为仅通过#if UNITY_6000_0_OR_NEWER编译指令启用,旧版本 Unity 保留原等待行为。

等待就绪的实现

WaitForUnityReadyAsync使用TaskCompletionSource<bool>配合EditorApplication.update事件驱动(而非阻塞式Thread.Sleep),每个帧 tick 检查四个条件:

  1. !EditorStateCache.GetActualIsCompiling()—— 使用"真实编译中"标记而非 Unity 的isCompiling,避免误报(源码注释引用 issue #549、#1276);
  2. !EditorApplication.isUpdating—— 资产导入未在进行;
  3. !TestRunStatus.IsRunning—— 测试未在运行;
  4. !EditorApplication.isPlayingOrWillChangePlaymode—— 未处于播放模式切换。

超时上限为DefaultWaitTimeoutSeconds = 60秒,超时抛出TimeoutException,由外层转换为ErrorResponse("refresh_timeout_waiting_for_ready", ...)

返回结构与错误码

成功后返回SuccessResponse("Refresh requested.", ...)data字段如下:

字段说明
refresh_triggered是否实际触发了资产刷新
compile_requested是否请求了编译
resulting_state返回瞬间的编辑器状态:compiling/asset_import/idle
hint后续操作指引(如"轮询 editor_state 直到 ready_for_tools")

可能的错误码包括:tests_running(测试运行中,建议 5 秒后重试)、refresh_failed: <异常信息>refresh_timeout_waiting_for_ready(等待就绪超时)、refresh_wait_failed: <异常信息>

服务端容错与恢复:把"断连"当作成功

refresh_unity最大的工程难点在于:触发编译会引发 Domain Reload,导致 TCP 连接在命令执行中途断开。如果服务端把这种情况当作失败并让客户端重试,就会造成多次重载。因此 refresh_unity.py 专门实现了断连恢复逻辑:

  • 编译断连 = 预期成功:当compile="request"且响应中出现connection closed/disconnected/aborted(WinError 10053)/timeout,或reason == "reloading"时,服务端将其视为"刷新已成功触发",置recovered_from_disconnect = True,不再向客户端返回错误,防止 Claude Code 等客户端盲目重试(issue #577)。
  • 可重试错误hint == "retry"could not connect时,若wait_for_ready为 true 则进入等待循环;否则原样返回。
  • 不可恢复错误:与 Domain Reload 无关的连接错误,直接返回原始错误响应。

随后,若wait_for_ready=True,服务端调用wait_for_editor_ready(ctx, timeout_s=60.0)(refresh_unity.py#L34-L61),以 0.25 秒为间隔轮询editor_state.get_editor_state(ctx),直至data.advice.ready_for_tools为 true,或 60 秒超时返回失败({"timeout": True, "wait_seconds": 60.0})。轮询期间任何读取异常都被吞掉继续轮询,以对抗 Domain Reload 期间的瞬时连接错误。

就绪恢复后还会执行最后一步清理:调用external_changes_scanner.clear_dirty(inst)(refresh_unity.py#L258-L264),清除该实例的外部变更脏标记,使后续工具能够干净地继续执行。该扫描器(external_changes_scanner.py)通过比较Assets/ProjectSettings/Packages/Packages/manifest.jsonfile:本地依赖目录的最大 mtime 来感知外部变更。

就绪判定标准:editor_state 的 advice 机制

wait_for_ready的判定依据来自mcpforunity://editor/state资源(editor_state.py)。服务端在组装状态快照时计算advice

  • ready_for_toolsblocking_reasons为空时为 true;
  • blocking_reasons:可能包含compilingdomain_reloadrunning_testsasset_refreshstale_status(状态超过 2 秒判定为过期);
  • recommended_retry_after_ms:未就绪时为 500,就绪时为 0。

Python 端的wait_for_editor_ready使用_REAL_BLOCKING_REASONS = {"compiling", "domain_reload", "running_tests", "asset_import"}(refresh_unity.py#L26)过滤"真正忙碌"的原因——该集合与 EditorStateCache.cs 中activityPhase的取值(running_testscompilingdomain_reloadasset_importplaymode_transitionidle)一一对应,避免把stale_status这类陈旧状态误判为阻塞。

典型调用示例

以下为通过 MCP 客户端向服务端发起调用的 JSON-RPC 请求示例:

{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "refresh_unity", "arguments": { "mode": "force", "scope": "all", "compile": "none", "wait_for_ready": true } } }

成功响应示例(resulting_state为返回瞬间的编辑器状态):

{ "success": true, "message": "Refresh requested.", "data": { "refresh_triggered": true, "compile_requested": false, "resulting_state": "idle", "hint": "Unity refresh completed; editor should be ready." } }

compile="request"且处于 Unity 6+,hint会提示客户端:"If Unity enters compilation/domain reload, poll the mcpforunity://editor/state resource until data.advice.ready_for_tools is true."

推荐实践:修改脚本后使用{"compile": "request", "wait_for_ready": true};外部写入资源文件后使用{"mode": "force", "scope": "assets"};需要等待编辑器的场景务必开启wait_for_ready,并在 Unity 6+ 下做好客户端侧轮询兜底。

测试验证与注册机制

仓库为refresh_unity提供了两层集成测试:

  • test_refresh_unity_registration.py:验证@mcp_for_unity_tool装饰器正确注册了名为refresh_unity的工具(red test 风格,要求显式刷新工具存在)。
  • test_refresh_unity_retry_recovery.py:模拟 Unity 断开连接且传输层返回hint="retry"的场景,验证refresh_unity(wait_for_ready=True)会轮询就绪、返回success=Truedata.recovered_from_disconnect == True,并清除了external_changes_scanner中的脏标记。

工具注册走services.tools.__init__.pyregister_all_tools自动发现机制:任何位于tools/目录下、带@mcp_for_unity_tool装饰器的模块都会被自动扫描注册(Server/src/services/tools/init.py),随后套上log_executiontelemetry_tool装饰器后注册进 FastMCP。

总结

refresh_unity是 Unity MCP 中连接"变更操作"与"编辑器同步"的收尾工具,其工程价值体现在三层设计上:

  1. 语义分层mode(刷不刷)、scope(刷什么)、compile(编不编)、wait_for_ready(等不等)四个正交维度,覆盖从轻量资源刷新到完整"刷新+编译+等待就绪"的全部需求;
  2. 容错设计:把 Domain Reload 断连视为预期成功、禁止重载期重试、双端就绪等待互为兜底,保证 AI 工作流不会因连接中断而误判失败或重复触发编译;
  3. 状态联动:与mcpforunity://editor/state资源的advice.ready_for_tools机制深度耦合,为客户端提供可轮询、可验证的编辑器就绪判定标准。

理解这些细节后,你便能在自己的 Unity MCP 自动化流程中准确编排资源导入、脚本编译与就绪等待,避免常见的"刷新后立刻操作导致失败"或"编译触发后反复重载"等问题。

【免费下载链接】unity-mcpUnity MCP acts as a bridge between AI assistants and your Unity Editor. Give your LLM tools to manage assets, control scenes, edit scripts, and automate tasks within Unity.项目地址: https://gitcode.com/GitHub_Trending/un/unity-mcp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询