1. 为什么我决定让 Agent 接管 FairyGUI 的 UI 拼装
做 Unity 项目的人大概都有过这种体验:策划给了一张界面草图,美术出了切图,然后你打开 FairyGUI 编辑器,开始一个按钮一个按钮地拖、一个文本一个文本地对齐、一个列表一个列表地配。一个中等复杂度的界面,光是摆位置、调层级、绑命名,就能耗掉大半天。更别提后面策划说“这个按钮往左挪 20 像素”“这个弹窗再加一行说明”,你又得回去重新拖一遍。
这套流程本身没错,FairyGUI 作为 Unity 生态里非常成熟的 UI 中间件,它的编辑器能力、运行时性能、包体管理都经得起考验。问题出在“手工拼装”这个环节——它高度重复、极度依赖肉眼对齐、而且几乎无法版本化追踪。你很难用 Git 去 diff 一个.fairy工程里某个组件的位置变化,也很难让另一个同事在不打开编辑器的情况下理解这个界面是怎么搭出来的。
我最近在几个项目里尝试了一条新路子:把 FairyGUI 的界面搭建过程交给 Agent 来做。这里的 Agent 不是那种“帮你写两句代码”的补全工具,而是能理解界面结构、能调用工具、能读写工程文件的智能体。配合 MCP(Model Context Protocol)这类协议,Agent 可以像人一样操作 FairyGUI 的工程数据,甚至直接生成可用的组件树。
先说清楚这套方案适合谁:如果你已经在用 FairyGUI,并且团队里有 Unity 开发基础,那这套东西能帮你把 UI 搭建的效率拉高一个量级;如果你还在纯手写 UGUI,也可以先看看思路,因为核心逻辑是通用的——把“界面描述”和“界面实现”解耦。至于完全没接触过 Unity 的朋友,这篇文章可能门槛偏高,但里面关于 Agent 工具调用的设计思路,放到其他领域一样成立。
我踩过的坑不少,从最开始想让 Agent 直接操作编辑器 UI(失败),到后来转向操作工程文件(可行),中间折腾了好几轮。下面把整套思路、关键细节和实操过程完整拆开讲。
2. 整体设计思路:为什么不让 Agent 直接点编辑器
2.1 两条路线的取舍:模拟操作 vs 数据驱动
最开始我的想法很朴素:既然 Agent 能控制鼠标键盘,那就让它像人一样打开 FairyGUI 编辑器,拖控件、填属性不就行了。实测下来这条路基本走不通,原因有三个。
第一,FairyGUI 编辑器的 UI 元素定位极不稳定。它的控件树、属性面板、舞台区域都是自绘的,没有标准的无障碍接口,Agent 想通过截图识别再点击,误差率很高。第二,即使点中了,拖拽这种连续操作对 Agent 来说很难精确控制,一个 10 像素的偏差在界面上就是明显的错位。第三,也是最致命的——这种方式完全不可复现。今天 Agent 点对了,明天换个分辨率、换个编辑器版本,可能就全乱了。
所以我转向了第二条路:数据驱动。FairyGUI 的工程本质上是一堆结构化数据,组件、元件、属性、层级关系都以文件形式存在。Agent 不需要“看见”界面,它只需要理解这套数据结构,然后生成或修改对应的文件。编辑器只是这套数据的一个可视化前端,真正决定界面长什么样的是底层数据。
这个判断是整个方案的基石。一旦接受“界面即数据”,Agent 的介入方式就变得非常自然:它读需求、生成数据、写回工程,编辑器打开就能看到结果。
2.2 Agent 在这个流程里到底扮演什么角色
很多人对 Agent 的理解还停留在“会写代码的聊天机器人”。在这个项目里,Agent 的角色要具体得多,它同时承担了四个职责。
需求解析者:把策划的自然语言描述(“一个带搜索框的列表页,每行有头像、标题和操作按钮”)翻译成结构化的界面定义。这一步考验的是 Agent 对界面语义的理解,而不是代码能力。
结构生成者:根据界面定义,生成 FairyGUI 的组件树。包括用哪些元件类型(按钮、文本、列表、滚动容器)、层级怎么嵌套、命名怎么规范。
工具调用者:通过 MCP 协议调用具体的工具函数,比如“创建组件”“设置属性”“添加子元件”。Agent 不直接写文件,而是通过工具接口操作,这样能保证数据格式的正确性。
校验反馈者:生成完之后,Agent 需要能读取工程状态,检查有没有命名冲突、层级错误、引用缺失,然后自我修正。
这四个职责里,最容易被低估的是最后一个。我一开始只让 Agent 生成,不让它校验,结果经常出现组件名重复、父级引用错误这种低级问题。后来加上了校验环节,返工率直接降了一半以上。
2.3 MCP 协议为什么是关键拼图
MCP 这个词最近出现频率很高,但很多人还是不太清楚它到底解决什么问题。用一句话说:MCP 是让 Agent 和外部工具之间有了统一的对话方式。
在没有 MCP 之前,我要让 Agent 操作 FairyGUI 工程,得自己写一堆函数,然后在提示词里告诉 Agent“你可以调用 createComponent 这个函数,参数是……”。这种方式能用,但很脆弱:换个模型、换个工具集,提示词就得重写。而且 Agent 经常“幻觉”出不存在的函数名,或者参数格式传错。
MCP 把这层标准化了。工具方按照 MCP 协议暴露自己的能力,Agent 方按照协议去发现和调用。双方不需要互相了解内部实现,只要遵守同一套约定就行。在这个项目里,我写了一个 FairyGUI 的 MCP Server,把“创建组件”“设置属性”“查询元件”这些操作暴露成标准工具,Agent 通过 Cursor 这类支持 MCP 的客户端连接上来,就能直接调用。
这里有个实操细节值得说:MCP Server 的工具描述一定要写得足够清楚,包括每个参数的类型、取值范围、必填与否。Agent 判断该调哪个工具、怎么传参,全靠这些描述。我一开始描述写得很简略,Agent 经常把“宽度”传到“高度”上,后来把描述补全,这类错误基本消失了。
2.4 和 Cursor 的配合方式
Cursor 在这个方案里扮演的是“Agent 宿主”的角色。它提供了模型能力、对话界面和 MCP 客户端。我在 Cursor 里配置好 FairyGUI 的 MCP Server 之后,就可以在对话里直接说“帮我创建一个登录界面”,Agent 会自动调用工具去生成。
Cursor 的中文设置、提示词管理这些基础操作网上教程很多,这里不展开。重点说一个和本项目强相关的点:Cursor 的规则文件(Rules)。我会在项目里放一个规则文件,写明 FairyGUI 的命名规范、常用元件类型、层级约定。Agent 每次生成前都会读这个规则,这样出来的组件树风格统一,不会这次用btn_login下次用loginButton。
这个规则文件的价值在于,它把“团队约定”变成了 Agent 的“先验知识”。没有它,Agent 每次都在猜;有了它,Agent 是在执行标准。
3. 核心细节解析:FairyGUI 工程结构与 Agent 操作要点
3.1 先搞懂 FairyGUI 工程里到底有什么
要让 Agent 操作 FairyGUI,你得先自己清楚这套工程的数据长什么样。FairyGUI 的工程目录里,核心是这几类东西。
包(Package):一个工程可以有多个包,包是资源的组织单位。每个包对应一个文件夹,里面有package.xml描述包的整体信息。
组件(Component):界面或界面片段的定义,对应.xml文件。一个组件里包含若干元件,以及它们的位置、大小、层级关系。
元件(GObject):组件里的具体元素,类型包括图形(Graph)、文本(Text)、按钮(Button)、列表(List)、装载器(Loader)等。每个元件在组件 XML 里是一个节点,带一堆属性。
资源(Resource):图片、字体、声音等外部资源,通过 ID 被元件引用。
Agent 要做的,本质上就是生成和修改这些 XML。但直接让 Agent 写 XML 风险很大,因为 FairyGUI 的 XML 结构有不少隐式约定,比如 ID 的分配规则、relation节点的写法、扩展属性的位置。所以我选择用 MCP 工具把这层封装起来,Agent 调用的是语义化的函数,底层 XML 由工具负责生成。
3.2 组件树的层级设计原则
界面搭得好不好,很大程度上取决于层级设计。我在项目里定了几条规则,也写进了给 Agent 的规则文件。
第一条,按功能分区,不按视觉分区。比如一个列表页,应该是“顶部搜索区 + 内容列表区 + 底部操作区”这样的结构,而不是“上面一块 + 中间一块 + 下面一块”。功能分区的好处是,后续要改某一块,能快速定位。
第二条,容器元件优先。能用 Group 或 Component 包起来的一组元件,就不要散着放。这样层级清晰,也方便整体控制显隐和位移。
第三条,命名带前缀。按钮用btn_,文本用txt_,图片用img_,列表用list_。这个约定看起来琐碎,但在 Agent 生成时非常有用——Agent 看到btn_开头就知道这是个按钮,设置属性时会自动带上按钮相关的扩展属性。
第四条,避免过深层级。FairyGUI 的渲染是按层级来的,层级太深会影响性能,也不好维护。我一般控制在四层以内。
这几条规则我会在提示词里明确告诉 Agent,也会在 MCP 工具的校验环节里做检查。比如 Agent 生成了一个五层嵌套的结构,校验工具会提示“层级过深,建议合并”。
3.3 属性映射:自然语言到 FairyGUI 属性的翻译
这是整个方案里最考验细节的部分。策划说“这个按钮大一点”,Agent 得知道“大一点”对应到 FairyGUI 里是改width和height,而且得有个合理的默认值。策划说“这个文本要居中”,Agent 得知道是设align为center,而不是去调位置。
我在 MCP Server 里做了一层属性映射表,把常见的自然语言描述对应到具体属性。比如:
| 自然语言描述 | FairyGUI 属性 | 典型取值 |
|---|---|---|
| 大一点 / 小一点 | width, height | 按基准值缩放 1.2 / 0.8 |
| 居中 | align | center |
| 靠左 / 靠右 | align | left / right |
| 加粗 | bold | true |
| 半透明 | alpha | 0.5 |
| 置顶 | 调整层级顺序 | 移到同级最后 |
| 隐藏 | visible | false |
这张表不是让 Agent 死记,而是作为工具的默认行为。Agent 说“把标题居中”,工具收到align=center的意图后自动应用。这样 Agent 不需要精确知道 FairyGUI 的属性名,只需要表达意图。
实测下来,这层映射极大降低了 Agent 的出错率。之前 Agent 经常把“居中”理解成改 x 坐标,现在有了映射表,它会走正确的属性通道。
3.4 命名规范与 ID 管理
FairyGUI 里每个元件都有名字和 ID。名字是给人看的,ID 是给程序引用的。Agent 生成时,名字要符合规范,ID 要保证唯一。
ID 这块有个坑:FairyGUI 的 ID 是包内唯一的,如果 Agent 随便生成,很容易和已有元件冲突。我的做法是让 MCP 工具维护一个 ID 分配器,每次创建元件时从当前最大 ID 往后递增,绝不重复。Agent 不需要关心 ID 具体是多少,它只管调createComponent,ID 由工具负责。
名字这块,我要求 Agent 遵循“前缀 + 语义”的格式。比如btn_submit、txt_username、list_message。这样后续程序里引用的时候,一眼就能看出是什么。而且命名规范统一之后,Agent 在生成时也能根据名字推断元件类型,减少属性设置错误。
提示:命名规范一定要在项目初期就定好,并且写进 Agent 的规则文件。中途改规范的成本极高,因为所有引用都要跟着改。
4. 实操过程:从零搭一个界面给 Agent
4.1 环境准备与 MCP Server 搭建
先说环境。你需要的东西不多:一个 Unity 项目(装了 FairyGUI)、一个 FairyGUI 工程、一个支持 MCP 的客户端(我用的是 Cursor)、以及一个自己写的 FairyGUI MCP Server。
MCP Server 我用 Node.js 写,因为它生态成熟、上手快。核心是引入 MCP 的 SDK,然后定义工具。一个最小的工具定义大概长这样:
server.tool( "create_component", "在指定包中创建一个新组件", { packageName: { type: "string", description: "包名" }, componentName: { type: "string", description: "组件名,需符合命名规范" }, width: { type: "number", description: "组件宽度" }, height: { type: "number", description: "组件高度" } }, async ({ packageName, componentName, width, height }) => { // 读取 package.xml,创建组件节点,写回 return { content: [{ type: "text", text: `组件 ${componentName} 创建成功` }] }; } );工具定义里最关键的是描述文字。Agent 判断该不该调这个工具、参数怎么传,全靠这段描述。我建议描述里把约束条件也写清楚,比如“组件名必须以字母开头,只能包含字母数字下划线”。
Server 写好后,在 Cursor 的 MCP 配置里加上这个 Server 的启动命令,重启 Cursor,Agent 就能发现这些工具了。
4.2 用自然语言描述界面需求
环境就绪后,就可以开始让 Agent 干活了。我在 Cursor 里输入的需求大概是这样:
帮我创建一个登录界面组件,命名为
LoginPanel,尺寸 750x1334。包含以下元素:顶部一个标题文本txt_title,内容为“用户登录”,居中,字号 36;中间一个输入框区域,包含用户名输入input_username和密码输入input_password,垂直排列,间距 20;底部一个登录按钮btn_login,文字为“登录”,宽度 400,高度 80,水平居中。
这段描述里,我刻意把每个元件的名字、类型、关键属性都写清楚了。这不是因为 Agent 笨,而是因为描述越精确,生成越可控。如果你只说“帮我做个登录界面”,Agent 也能生成,但出来的东西大概率不符合你的预期,改起来更费劲。
4.3 Agent 调用工具生成组件树
Agent 收到需求后,会先规划步骤:先创建组件,再依次创建各个元件,最后设置属性和层级。这个过程在 Cursor 里能看到它一步步调用工具。
第一步,调用create_component,传入LoginPanel、750、1334。工具在package.xml里注册这个组件,并创建对应的 XML 文件。
第二步,调用create_element创建标题文本。这里 Agent 会传type: "text"、name: "txt_title"、text: "用户登录"、align: "center"、fontSize: 36。工具负责把这些属性写进组件 XML。
第三步,创建输入框。这里有个细节:FairyGUI 的输入框其实是TextInput类型的元件,Agent 需要知道这个类型。我在工具描述里明确写了“输入框使用 TextInput 类型”,Agent 就不会搞错。
第四步,创建按钮。按钮在 FairyGUI 里通常是Button类型,带一个title属性。Agent 传title: "登录",工具自动处理按钮的扩展属性。
第五步,设置层级和位置。Agent 根据“垂直排列、间距 20”这个描述,计算出每个元件的 y 坐标,然后调用set_position工具。
整个过程大概十几秒,一个可用的登录界面组件就生成好了。打开 FairyGUI 编辑器,能看到完整的组件树和界面。
4.4 校验与修正:让 Agent 自己检查
生成完之后,我不会直接就用,而是让 Agent 跑一遍校验。我在 MCP Server 里加了一个validate_component工具,它会检查几件事:命名是否符合规范、ID 是否唯一、层级是否过深、有没有引用不存在的资源、必填属性是否缺失。
Agent 调用这个工具后,会拿到一份检查报告。如果有问题,它会根据报告自我修正。比如报告说“txt_title的 align 属性值为空”,Agent 就会重新调用set_property补上。
这个校验环节我强烈建议加上。它相当于给 Agent 装了一个“自检程序”,能在问题扩散之前拦住大部分低级错误。我统计过,加了校验之后,需要人工介入修正的比例从 40% 降到了 15% 左右。
4.5 参数计算:位置和尺寸怎么定
位置和尺寸的计算是实操里最容易出问题的地方。Agent 不是设计师,它不知道“看起来舒服”是什么概念。所以我在工具里内置了一套布局计算逻辑。
以垂直排列为例,Agent 只需要说“这几个元件垂直排列,间距 20”,工具会自己算:第一个元件放在起始 y 坐标,第二个元件放在第一个的 y + 高度 + 20,以此类推。Agent 不需要自己算坐标,它只需要表达布局意图。
水平居中也是类似。工具收到“水平居中”的意图后,会用(父容器宽度 - 元件宽度) / 2算出 x 坐标。这个计算过程 Agent 不用管,工具负责。
这套逻辑的好处是,Agent 的职责被限定在“理解意图”和“调用工具”上,复杂的数值计算交给工具。这样既降低了 Agent 的出错率,也让整个流程更可控。
注意:布局计算逻辑一定要考虑边界情况,比如元件宽度超过父容器、间距为负数、元件数量为零。这些情况我在工具里都做了兜底处理,否则 Agent 传个异常值进来,整个组件就废了。
5. 常见问题与排查技巧实录
5.1 Agent 生成的组件打不开或报错
这是最常见的问题,通常有几个原因。第一,XML 格式错误,比如标签没闭合、属性值没加引号。这种情况一般是 MCP 工具写文件时出了问题,检查工具的序列化逻辑。第二,ID 冲突,两个元件用了同一个 ID。检查 ID 分配器是否正常工作。第三,引用了不存在的资源 ID。检查 Agent 有没有凭空捏造资源引用。
排查方法很简单:用 FairyGUI 编辑器打开工程,它会直接告诉你哪个文件哪一行有问题。根据报错定位到具体元件,再看是工具的问题还是 Agent 传参的问题。
5.2 元件位置全乱了
位置乱通常是布局计算的问题。先检查 Agent 传的布局意图是否正确,比如它是不是把“垂直排列”理解成了“水平排列”。再检查工具的计算逻辑,特别是当元件有旋转、缩放时,坐标计算会更复杂。
我遇到过一次,Agent 生成的元件全部堆在左上角。查下来是工具在计算位置时,没有考虑父容器的坐标系,直接用绝对坐标算的。修正之后就好了。这个坑提醒我,坐标系一定要在工具层面统一,不能让 Agent 去操心。
5.3 Agent 不调用工具,直接输出 XML
有时候 Agent 会“偷懒”,不调工具,直接在回复里写一段 XML 让你自己贴。这种情况一般是工具描述不够清晰,或者 Agent 觉得调工具太麻烦。
解决办法有两个:一是在提示词里明确要求“必须通过工具操作,不要直接输出 XML”;二是把工具描述写得更吸引人,让 Agent 觉得调工具是更自然的选择。我还会在规则文件里写一条“所有工程修改必须通过 MCP 工具完成”,强化这个约束。
5.4 命名冲突和规范不一致
Agent 生成时如果没读规则文件,很容易出现命名冲突。比如这次生成btn_login,下次生成loginBtn。解决办法就是把命名规范写进规则文件,并且在 MCP 工具的校验环节里强制检查。
我还会在工具里加一个“命名建议”功能:Agent 传一个名字进来,工具检查是否冲突,如果冲突就返回一个建议名。这样 Agent 不用自己纠结,工具帮它决定。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决方式 |
|---|---|---|---|
| 组件打不开 | XML 格式错误 | 检查文件序列化逻辑 | 修正工具写文件代码 |
| 元件 ID 冲突 | ID 分配器失效 | 检查分配器状态 | 重置分配器,重新生成 |
| 位置全乱 | 坐标系不统一 | 检查布局计算逻辑 | 统一使用父容器相对坐标 |
| Agent 不调工具 | 工具描述不清 | 检查工具描述文字 | 补充描述,强化提示词约束 |
| 命名冲突 | 未读规则文件 | 检查规则文件加载 | 强制校验命名规范 |
| 属性设置无效 | 属性名拼写错误 | 检查属性映射表 | 补全映射表,加校验 |
5.6 几个我踩过的坑
第一个坑是过早追求全自动。我一开始想让 Agent 从需求到成品全自动完成,结果发现中间环节太多,任何一步出错都会导致整体失败。后来改成半自动:Agent 负责生成,我负责审核和微调。效率反而更高,因为审核比从头做快得多。
第二个坑是忽视工具的错误处理。MCP 工具如果遇到异常直接崩溃,Agent 会收到一个模糊的错误信息,然后开始瞎猜。后来我在每个工具里都加了详细的错误返回,告诉 Agent 具体哪里错了、该怎么改。Agent 的自我修正能力一下子就上来了。
第三个坑是规则文件写得太笼统。一开始我只写了“遵循命名规范”,Agent 根本不知道具体是什么规范。后来改成“按钮用 btn_ 前缀,文本用 txt_ 前缀,图片用 img_ 前缀”,Agent 立刻就懂了。规则文件要具体到可执行的程度,不能停留在原则层面。
6. 这套方案还能怎么扩展
6.1 接入设计稿自动生成
现在 Agent 是根据自然语言描述生成界面。下一步很自然的方向是:直接读设计稿,自动生成 FairyGUI 组件。设计稿里有位置、尺寸、颜色、文字,这些信息完全可以结构化提取,然后喂给 Agent。
我试过用一些设计工具的 API 导出图层信息,再让 Agent 转换成 FairyGUI 结构。效果还不错,尤其是对于规整的界面,还原度能到八成以上。剩下的两成主要是交互逻辑和动态内容,这些设计稿里本来就没有,需要人工补。
6.2 和版本管理结合
FairyGUI 工程是文本文件,天然适合 Git 管理。Agent 生成组件后,可以自动提交一个 commit,commit message 里写清楚这次生成了什么。这样每次改动都有记录,回滚也方便。
更进一步,可以让 Agent 在生成前先拉取最新代码,避免冲突。生成后自动跑一遍校验,校验通过再提交。这套流程跑顺了,UI 搭建就真正变成了“可追踪、可回滚、可协作”的工程化流程。
6.3 多 Agent 协作
单个 Agent 负责一个界面没问题,但一个项目有几十个界面,串行生成太慢。可以考虑多 Agent 并行:一个 Agent 负责登录模块,一个负责主界面,一个负责设置页。每个 Agent 操作不同的包或组件,互不干扰。
这里的关键是任务划分要清晰,不能让两个 Agent 同时改同一个组件。我在项目里按模块划分,每个模块对应一个包,Agent 之间通过包隔离。这样并行生成也不会冲突。
6.4 扩展到其他 UI 框架
这套思路不局限于 FairyGUI。只要一个 UI 框架的工程数据是结构化的、可读写的,就能用同样的方式接入 Agent。比如 UGUI 的 Prefab、Web 前端的组件文件、移动端的布局 XML,本质上都是“界面即数据”。
我在另一个项目里试过用类似的方式生成 Web 组件,把 FairyGUI 的 MCP Server 换成 Web 组件的 MCP Server,Agent 的提示词和规则文件稍微调整一下就能复用。这说明核心方法论是通用的,具体实现只是适配层的问题。
7. 我个人的一些实操体会
这套方案跑下来,最大的感受是:Agent 不是替代人,而是把人从重复劳动里解放出来。以前我花在拖控件上的时间,现在可以用来思考交互逻辑和视觉细节。Agent 生成的是骨架,我补的是灵魂。
另一个体会是,工具的质量决定 Agent 的上限。Agent 再聪明,如果工具接口设计得烂,它也做不出好东西。我在 MCP Server 上花的调试时间,比调提示词的时间多得多,但我觉得值。工具稳了,Agent 的表现就稳了。
最后说一个细节:别指望一次生成就完美。我现在的工作流是“Agent 生成初稿 → 我审核 → 提修改意见 → Agent 改”。通常两三轮就能达到可用状态。这个效率比纯手工高太多了,而且改的过程也是在给 Agent 积累上下文,后面生成类似界面会越来越准。
如果你也在做 Unity 项目,也在用 FairyGUI,我建议你试试这条路。不用一上来就搞全套,先从一个小界面开始,把 MCP Server 搭起来,跑通一个流程,再慢慢扩展。踩几个坑是正常的,但跑通之后,你会发现 UI 搭建这件事,真的可以换个做法了。