ET UnityBridge 深度解析:命令行桥接 Unity Editor 的架构、协议与 AI 自动化实操指南
2026/9/16 22:54:18 网站建设 项目流程

ET UnityBridge 深度解析:命令行桥接 Unity Editor 的架构、协议与 AI 自动化实操指南

【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET

导读

cn.etetet.unitybridge是 ET 框架中的Unity 本地文件桥接包:它通过一个纯命令行程序ET.UnityBridge.dll与 Unity Editor 内的文件宿主进程通信,让 AI Agent 或脚本以dotnet命令直接驱动 Unity——查询编译状态、进入/退出 PlayMode、操作资源、场景、GameObject、Inspector、Prefab,甚至截图和跑 Editor 测试。读完本文,你将掌握该桥接包的目录结构与通信协议、CLI 全部传输参数与命令发现方法、deferred 长时命令的底层机制,以及一套可直接上手的 AI 操作 Unity 的最小工作流。

包定位与三层结构

根据 AGENTS.md 的概述,本包围绕“Unity 本地文件桥接”提供三部分能力:

组成职责
DotNet~纯命令行程序ET.UnityBridge,对应工程 ET.UnityBridge.csproj
Scripts/EditorUnity Editor 文件宿主,汇入ET.Editor,负责在编辑器内轮询请求、分发执行、写回响应
Scripts/Model/Share桥接命令、错误码与共享文件协议,两端共用的数据结构

对应的核心目录速览(引自原文档):

路径说明
DotNet~ET.UnityBridge.csproj与命令行入口
Scripts/EditorUnity Editor 宿主、处理器与分发逻辑
Scripts/Model/Share桥接命令、错误码、路径与文件存储协议

这种“命令行客户端 + Editor 宿主 + 共享协议”的三层设计,使桥接完全不依赖网络端口或 HTTP 服务:所有数据通过本地磁盘文件交换,Unity 侧无需开启任何服务器监听,天然适配编辑器安全沙箱。

通信原理:本地文件存储协议

根目录解析优先级

桥接两端通过同一个根目录交换文件,路径解析逻辑在 UnityBridgeStorage.cs 的UnityBridgePathHelper.ResolveRoot中实现,优先级如下:

  1. 显式传入的--root <路径>
  2. 环境变量ET_UNITY_BRIDGE_ROOT
  3. 默认值Temp/UnityBridge(相对项目根目录)。

CLI 端在 Program.cs 中调用UnityBridgePathHelper.ResolveRoot(root)完成解析;Unity 侧 UnityBridgeEditorHost.cs 在静态构造时用同样的规则缓存根目录。两端必须解析到同一目录,否则就会出现wait unity bridge response timeout

目录布局与文件流转

UnityBridgeFileStore.EnsureDirectories(见 UnityBridgeStorage.cs)会创建以下子目录:

目录用途
requests/CLI 写入的待处理请求({rpcId}.json
processing/Editor 取出后正在处理的请求
responses/Editor 写回的响应({rpcId}.json),CLI 读取后删除
deadletter/无法解析的请求被移入的“死信”区
state/内部状态,含pending-command.json(deferred 命令)与idempotency/(幂等缓存)

一次完整请求的流转为:CLI 通过UnityBridgeFileStore.WriteRequest原子写入requests/{rpcId}.json(UnityBridgeStorage.cs)→ Editor 轮询到后用File.Move移入processing/(保证单次只被一个进程处理)→ 执行完成后WriteResponse写回responses/{rpcId}.json并删除 processing 文件 → CLI 读到响应即删除该响应文件。所有写入都走“临时文件 + 替换”的原子写路径(WriteTextAtomic),避免半写状态被另一端读到。

RPC 信封

CLI 与 Editor 交换的不只是命令本身,而是一个信封对象UnityBridgeRequestEnvelope(见 UnityBridgeCommands.cs),包含四个字段:

  • RpcId:本次调用的唯一标识,由 CLI 用进程 ID、随机数与时间戳异或生成(Program.cs);
  • IdempotencyKey:幂等键,配合响应缓存实现“同一命令重复发送只执行一次”;
  • TimeoutMs:超时上限(0 表示不设超时);
  • CommandJson:真正的命令 JSON,以_t字段标注命令类型。

CLI 使用入门

入口与最小流程

CLI 入口固定为:

dotnet ./Bin/ET.UnityBridge.dll

如果Bin/ET.UnityBridge.dll尚不存在,需要先用et-build编译确认工具已生成。不带参数直接运行会输出unity bridge command is empty并以退出码 2 结束(见 Program.cs)。

最小可用命令是Ping,用于探测宿主是否在线:

dotnet ./Bin/ET.UnityBridge.dll '{"_t":"Ping"}'

需要命令列表或详细状态时使用HostState

dotnet ./Bin/ET.UnityBridge.dll '{"_t":"HostState"}'

传输参数

CLI 支持四个传输参数(在 Program.cs 中以TransportOptions定义):

参数作用默认值
--root <路径>显式指定桥接根目录环境变量ET_UNITY_BRIDGE_ROOT,否则Temp/UnityBridge
--waitMs <毫秒>客户端等待最终响应的时间15000
--timeoutMs <毫秒>单条命令的服务端超时上限(写入信封)0(不设限),服务端另有分命令默认值
--idempotencyKey <字符串>幂等键,用于去重与结果缓存自动生成 GUID(N 格式)

组合示例:

dotnet ./Bin/ET.UnityBridge.dll '{"_t":"HostState"}' --root "Temp/UnityBridge" --waitMs 15000 --timeoutMs 10000 --idempotencyKey "host-state-check"

等待与自动延长机制

SendRequest(Program.cs)在写入请求后以 100ms 间隔轮询响应文件,直到超过waitMs截止时间。关键细节:当未显式指定--waitMs时,如果检测到state/pending-command.json中存在与当前 RpcId 匹配的 deferred 命令,CLI 会自动把等待期限延长到DefaultDeferredWaitMs(185 秒)。这意味着CompileEnterPlay这类耗时长命令默认最多能等到 185 秒,无需手工加大--waitMs

命令协议与常用命令

协议定义位置

全部命令与响应结构定义在 Proto 目录下:

  • UnityBridge_C_11100.proto:状态类命令、资源类命令、场景/选择集/测试/截图/GameView 命令,以及BridgeVector2/3BridgeQuaternionBridgeTransformInfoBridgeObjectInfoBridgeAssetInfo等共享消息结构;
  • UnityBridge_C_11400.proto:GameObject/Transform/菜单/Prefab/Inspector/Undo-Redo/BatchExecute 命令。

六个核心状态命令

命令作用响应特有字段
Ping连通性探测,返回编译/PlayMode/CodeMode/Unity 版本TimeIsCompilingIsPlayingIsPlayingOrWillChangePlaymodeCodeModeUnityVersion
HostState额外返回可用命令清单AvailableCommands
Compile触发一次脚本编译DurationMs
Refresh刷新资源数据库
RegenProject重新生成 VS/Rider 工程文件
EnterPlay/ExitPlay进入 / 退出 PlayModeIsPlaying
Reload热重载(要求已在 PlayMode)

命令发现

不要手工背诵全部命令名。两条路径:

  1. 运行时用HostStateAvailableCommands字段拿当前宿主支持的完整命令列表;
  2. 静态用rg在 Proto 目录检索命令消息:
rg -n "^message .*Request|^message (Ping|HostState|Compile|Refresh|RegenProject|EnterPlay|ExitPlay|Reload)\b" ./Packages/cn.etetet.unitybridge/Proto

需要行为细节时再按需打开对应的单个 handler(Scripts/Editor/Share/下每个命令一个UnityBridgeXxxHandler.cs),不要整包读取。

推荐执行顺序

skill 文档给出的状态变化类命令有严格的先后关系:

目标顺序
编译HostStateCompile
刷新HostStateRefresh
重建工程文件HostStateRegenProject
进入 PlayMode确认IsCompiling == falseIsPlayingOrWillChangePlaymode == falseEnterPlay
热重载确认IsPlaying == trueReload
退出 PlayModeHostStateExitPlay

状态检查:Ping 与 HostState 解读

Ping是每次操作前的必做动作,一个典型响应(对照 UnityBridge_C_11100.proto):

{"_t":"PingResponse","RpcId":1,"Error":0,"Time":1715000000000, "IsCompiling":false,"IsPlaying":false,"IsPlayingOrWillChangePlaymode":false, "CodeMode":"Code","UnityVersion":"2022.3.0f1"}

各字段含义:

  • Error:0 表示成功,非 0 表示失败;
  • IsCompiling:Unity 是否正在编译脚本;
  • IsPlaying:当前是否处于 PlayMode;
  • IsPlayingOrWillChangePlaymode:是否处于 PlayMode 或正在切换 PlayMode(EnterPlay的前置检查依据);
  • CodeMode:当前代码模式(从Resources/GlobalConfigCodeMode字段反射读取,见 UnityBridgeEditorStatus.cs);
  • UnityVersion:编辑器版本。

HostState在此基础上额外携带AvailableCommands,适合在任务开始前一次性确认宿主能力与命令拼写。

事件驱动的 Unity Editor 宿主

轮询循环

Editor 侧宿主UnityBridgeEditorHost使用[InitializeOnLoad]在编辑器启动时挂载到EditorApplication.update,以0.2 秒为周期轮询(UnityBridgeEditorHost.cs):

  1. 先尝试UnityBridgeDeferredRuntime.TryPump泵送已挂起的 deferred 命令;
  2. 若无挂起命令且距上次轮询超过 0.2 秒,则取一条新请求;
  3. ProcessOneRequestAsync中先TryTakeNextRequest(把requests/中排序后的第一个文件原子移动到processing/),再执行HandleRequest
  4. 执行结果若为普通响应,写回responses/并删除 processing 文件;若为 deferred 响应则交由 Deferred 运行时接管。

服务端超时

HandleRequest会根据命令类型给出服务端侧默认超时(UnityBridgeEditorHost.cs):

命令服务端默认超时
Compile180000 ms
Refresh/RegenProject/EnterPlay/ExitPlay60000 ms
其他命令10000 ms

若请求在processing/中停留超过TimeoutMs,宿主直接返回unity bridge request timeout

幂等缓存

每次请求携带IdempotencyKey,宿主在执行成功后把响应写入state/idempotency/{sha256(key)}.json;后续相同幂等键的请求直接命中缓存返回(并替换为新 RpcId),避免EnterPlayCompile这类命令被重复执行(见 UnityBridgeEditorHost.cs 与 UnityBridgeStorage.cs)。

命令分发与 Handler 体系

Dispatcher 注册机制

UnityBridgeEditorDispatcher在静态构造时通过TypeCache.GetTypesDerivedFrom<IUnityBridgeHandler>()反射收集所有 handler 实现,校验每个 handler 的RequestType/ResponseType合法且无重复后建立“请求类型 → handler”映射(UnityBridgeEditorDispatcher.cs)。扩展桥接命令只需新增一个继承AUnityBridgeHandler<TRequest, TResponse>的 handler 类,无需修改分发器。

命令 JSON 的_t判别

Dispatcher 的TryNormalizeCommandJson解析规则(UnityBridgeEditorDispatcher.cs):

  • 必须包含_t字段(支持字符串或 BSON 多态数组,取最后一个非空值);
  • 旧的CommandType/Payload封装格式已废弃,会直接报错提示改用_t
  • 命令名可以是短名(如Ping)或全名(ET.Ping),分发器自动归一化为全名后反序列化。

错误码定义

错误码集中在 ErrorCode.cs:

常量值(基于PackageType.UnityBridge推导)含义
Success0成功
InvalidCommandLine200000000 + 包号×1000 + 1命令行/命令 JSON 非法
Timeout200000000 + 包号×1000 + 2等待超时
NotInPlayMode200000000 + 包号×1000 + 3不在 PlayMode(ExitPlay/Reload前置不满足)
AlreadyInPlayMode200000000 + 包号×1000 + 4已处于 PlayMode(重复EnterPlay
Compiling200000000 + 包号×1000 + 5Unity 正在编译,暂不能开始新的延迟命令
HandlerFail100000000 + 包号×1000 + 1handler 执行失败

CLI 侧读到Error == 0时以退出码 0 结束,否则以退出码 1 结束(Program.cs),并原样打印响应 JSON 供调用方解读。

Deferred 命令:长时操作的实现原理

CompileRefreshEnterPlayAssetImportRequest等命令会改变 Unity 编辑器状态或耗时较长,被设计为deferred(延迟)命令。其机制分为两层:

Handler 层:AUnityBridgeDeferredHandler

AUnityBridgeDeferredHandler<TRequest, TResponse>(AUnityBridgeDeferredHandler.cs)在首次Handle时:

  1. 调用UnityBridgeDeferredRuntime.TryCreatePendingCurrent把命令快照(含 RpcId、幂等键、超时、开始时间)写入state/pending-command.json
  2. UnityBridgeDeferredContext.CreateStart()执行一次Run,此时 handler 内通过deferred.Started<TResponse>()主动抛出UnityBridgeDeferredStartedException
  3. 分发器捕获该异常后返回UnityBridgeDeferredResponse(UnityBridgeCommands.cs),宿主识别IsDeferredResponse后不写响应,直接返回等待后续泵送。

运行时层:轮询泵送与恢复

UnityBridgeDeferredRuntime.TryPump(UnityBridgeDeferredRuntime.cs)在每个 Editor update 帧先于普通请求执行:

  • 读到pending-command.json后,反序列化命令、查找 handler,调用deferredHandler.Deferred(command, startedAt)
  • handler 内部用deferred.NotReady<TResponse>()抛出UnityBridgeDeferredNotReadyException表示“还没就绪”,运行时收到null响应则什么都不做,下一帧再试
  • 条件满足(如编译完成、PlayMode 切换完成)后 handler 返回正式响应,运行时通过Complete写缓存、写响应、清理 processing 与 pending 状态文件;
  • now - StartedAt > TimeoutMs,直接以Timeout错误码完成。

这就是为什么 skill 文档反复强调:deferred 命令必须等待最终响应,不能看到请求被接收就结束。CLI 端的 185 秒自动延长等待正是为此设计。

AI 操作 Unity 的任务路由

来自 et-unitybridge-ai-ops.md 的任务路由表,是 AI 使用桥接包的最高频场景索引:

目标优先命令族常见前置
状态 / 连通性PingHostStateEditorGetStateRequest
编译 / 刷新CompileRefreshRegenProjectAssetRefreshRequestAssetImportRequestIsCompiling == false
PlayMode / 热重载EnterPlayExitPlayReloadEditorPauseRequest检查IsPlaying/IsPlayingOrWillChangePlaymode
资源AssetSearchRequestAssetFindRequestAssetLoadRequestAssetReadTextRequestAssetGetPathRequest先限定 filter/path/count
场景SceneGetHierarchyRequestSceneGetActiveRequestSceneLoadRequestSceneSaveRequestSceneNewRequest写操作前确认当前场景
选择集SelectionGetRequestSelectionSetRequestSelectionAddRequestSelectionRemoveRequestSelectionClearRequest先读当前 selection
对象 / TransformGameObject*RequestTransform*RequestFind/GetInfo/Get
InspectorInspectorGet*RequestInspectorSet*RequestInspectorAddComponentRequestInspectorRemoveComponentRequest先读组件和属性名
PrefabPrefabInstantiateRequestPrefabSaveRequestPrefabApplyRequestPrefabGet*RequestPrefabUnpackRequest先确认 asset path / instance
截图 / GameViewScreenshotCaptureRequestGameView*Request先读分辨率
测试UnityTestRunRequest用精确正则
批量BatchExecuteRequest先单步验证

核心操作模式

读状态(先读后写):

dotnet ./Bin/ET.UnityBridge.dll '{"_t":"HostState"}'

只总结关键字段:ErrorMessageIsCompilingIsPlayingIsPlayingOrWillChangePlaymode、所需命令是否存在。

执行 deferred 命令:

dotnet ./Bin/ET.UnityBridge.dll '{"_t":"Refresh"}'

若返回unity is compiling,先轮询Ping直到IsCompiling == false再重试。

查资源(小范围限定):

dotnet ./Bin/ET.UnityBridge.dll '{"_t":"AssetFindRequest","Filter":"t:Prefab","MaxResults":10}'

读场景层级:

dotnet ./Bin/ET.UnityBridge.dll '{"_t":"SceneGetHierarchyRequest","Depth":2,"IncludeInactive":false}'

写操作后用GameObjectGetInfoRequestTransformGetRequest验证结果,不能只相信命令返回成功。

读 Inspector:

dotnet ./Bin/ET.UnityBridge.dll '{"_t":"InspectorGetComponentsRequest","Path":"<HierarchyPath>"}'

先读组件列表和属性名,再执行 set 命令,不要猜测SerializedProperty路径。

跑 Editor 测试(精确正则,避免全量):

dotnet ./Bin/ET.UnityBridge.dll '{"_t":"UnityTestRunRequest","Name":"^Unitybridge_DeferredHandlerRunContext_Test$"}'

判定标准:Error == 0Matched > 0Failed == 0

输出解读与常见错误

响应通用规则

  • Error == 0表示成功,非 0 表示失败,需结合Message判断原因;
  • CompileResponse额外包含DurationMs(编译耗时);
  • EnterPlayResponse/ExitPlayResponse额外包含IsPlaying
  • PingResponse/HostStateResponse额外包含Time/IsCompiling/IsPlaying/IsPlayingOrWillChangePlaymode/CodeMode/UnityVersion/AvailableCommands

常见错误排查表

错误信息含义与处理
wait unity bridge response timeoutUnity 未打开、项目未加载完、桥接根目录不一致,或 Editor 未处理请求;先检查ET_UNITY_BRIDGE_ROOT/--root是否两端一致
unity is compilingUnity 正在编译,暂时不能开始新的延迟命令;轮询Ping等待编译结束
unity already in playmode or changing playmode已处于 PlayMode,不能重复执行EnterPlay
unity not in playmodeExitPlay/Reload的前置条件不满足,需先EnterPlay
handler is missing命令名拼写错误或宿主版本不支持;先用HostState确认AvailableCommands
execute menu item failed菜单路径不存在,或 Unity 当前状态不允许执行

省 Token 操作规范

skill 文档明确要求 AI 端遵循“最小读取”原则:

  • 不要完整读取所有 proto、handler 或AvailableCommands
  • 先用HostStaterg发现命令名,再只打开相关 proto 小片段和对应 handler;
  • 先执行最小读命令确认目标,再做写操作;批量操作前先验证 1 个样本;
  • 不要把完整 JSON 响应贴给用户,只总结ErrorMessage和关键字段;
  • 优先 UnityBridge 命令而非 GUI 点击,除非命令缺失或用户明确要求。

测试与验证

本包在 Scripts/Editor/Test 目录提供了覆盖各命令族协议消息与 handler 行为的 Editor 测试,例如Unitybridge_DeferredHandlerRunContext_Test.cs(deferred 上下文)、Unitybridge_GameViewSetResolutionHandlerInvalidSize_Test.cs(非法参数)、Unitybridge_ExitPlayModeHandlerNotInPlayModeError_Test.cs(错误前置条件)等,可作为命令行为与错误码语义的权威参考。测试辅助类 UnityBridgeHandlerTestSupport.cs 与 UnityBridgeProtocolTestSupport.cs 用于构造请求与断言响应。

更多参考

  • skill 入口文档:skills/et-unitybridge/SKILL.md
  • CLI 传输、等待、返回值解读:references/et-unitybridge-cli.md
  • AI 操作 Unity 的任务路由与操作模式:references/et-unitybridge-ai-ops.md
  • 命令行入口实现:DotNet~/Program.cs
  • 协议定义:Proto/UnityBridge_C_11100.proto、Proto/UnityBridge_C_11400.proto

掌握以上内容后,你即可用一句dotnet ./Bin/ET.UnityBridge.dll '{"_t":"Ping"}'建立与 Unity Editor 的桥接,并沿任务路由表逐步完成从状态查询到资源、场景、PlayMode、测试的完整 AI 自动化操作闭环。

【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET

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

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

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

立即咨询