ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Agent自动化FairyGUI UI搭建:语义树驱动的实践

Agent自动化FairyGUI UI搭建:语义树驱动的实践 从凌晨两点被 UI 走查打回的那一瞬间开始我就一直在思考一个问题FairyGUI 在游戏客户端里撑起了九成以上的界面但我们花在把 UI 稿翻译成组件树上的时间可能比真正做功能逻辑的时间还要多。这个翻译过程既不性感也不复杂就是反复拖拽、对齐、设关联、配控制器偏偏它躲不掉。所以当 Agent 这波能力起来之后我最想试的一件事就是到底能不能让 Agent 直接接管 FairyGUI把这层又脏又累的手拼 UI工程给自动化掉。这篇就聊聊我实测下来的思路、踩坑和一些可以复用的方案给同样在做 Agent 化 UI 开发的团队一个参考。1. 手拼 UI到底浪费了多少人力先看清 FairyGUI 开发链路里的真实成本1.1 从 UI 稿到上线一条没人想走却不得不走的流水线FairyGUI 本身的定位是跨引擎 UI 编辑器美术/UI 设计把界面切图交给客户端客户端的 TA 或开发者在 FairyGUI 编辑器里把图片资源拉进包Package然后开始对着设计稿摆组件底图放哪、关闭按钮多大、标题字号多少、列表格子的锚点怎么设、翻页控制器要配几个页面。一套常规的主界面或背包界面熟练的人也要大半天遇到反复走查改位置时间直接翻倍。这条链路里最隐蔽的成本还不是拼本身而是每次返工都要在同一个地方重新拖一遍。比如 UI 稿里把右侧的按钮从 120px 改到 132px听起来只是改一个数字实际却要回到编辑器里选中组件、打开属性面板、修改宽度、再检查关联关系是否被破坏。这类微调在项目中期一周能来几十次每一个来回都在烧人效。如果给团队算一笔账一个标准的 MMORPG 前期核心面板主界面、背包、商城、养成、活动、邮件、好友大约 50 到 100 个一个熟练的 UI 开发者单面板平均 1 到 3 天光首版 UI 搭建就会吃掉 3 到 6 人月。这个数字在大多数中小团队里已经足够让负责人牙疼了。而手拼这个词恰恰精准概括了问题根源我们把大量精力花在了有规律、可预测、重复度极高的工作上却没有一套机制把它批量消化掉。1.2 FairyGUI 的两种拼编辑器手动 vs 运行时代码要找到解法先得看清楚 FairyGUI 有两条拼 UI 的路径。第一条是可视化编辑器路径也是绝大多数团队在用的方式资源导入 Package在编辑器中拖拽组件、设置布局属性、配置关联和控制器最后发布导出生成引擎侧可以加载的二进制资源包。第二条是运行时代码路径跳过编辑器直接在引擎侧用 FairyGUI 的 SDK API 动态创建对象。比如 Unity 里UIPackage.CreateObject(Bag, BagPanel)加载一个已发布的面板再通过GetChild(grid)拿到列表并填充数据。代码路径适合做参数化 UI比如根据服务器配置动态生成按钮数量但完全用它徒手搭一套复杂面板代码量极大而且很难调试所以团队一般只在编辑器路径覆盖不到的场景用它。而 Agent 的想象空间在于它可以把两条路径都接管掉。要么直接生成编辑器工程里的描述文件要么生成一套运行时代码。前者更贴近现有团队的工作方式后者离自动化更近但工程改造量更大。实操中我倾向于先把前者跑通。1.3 为什么传统自动化脚本一直没能解决这件事说到自动化第一反应是写宏或者模板。早在 Agent 出现之前就有人尝试用 FairyGUI 编辑器的批处理能力、外部脚本批量生成 XML、或者做一套UI 配置表来批量搭建界面但最后基本都只覆盖到了 20% 的标准化场景原因很简单传统脚本只能处理预先定义好的模板。比如你可以写脚本生成 30 个背包格子但脚本没法回答这个背包格子左上角的筛选按钮在窄屏上是否要隐藏这类语义问题。UI 需求本质上是自然语言和视觉描述中间还夹杂着产品偏好、布局直觉和设计规范这些没法穷举成脚本参数。Agent 不一样的地方在于它能理解语义能从一个模糊的描述里推理出这里应该有一个标题栏、一个列表区、一组底部操作按钮并且按目标格式输出结构化内容。这也是它真正能把手拼工程替代掉的技术前提。2. Agent 能接管FairyGUI 的底气组件本质只是一套有规律的描述文件2.1 拆开一个 component.xml 看它的真实结构很多人把 FairyGUI 编辑器当成一个黑盒觉得界面都是它画出来的、没法程序化干预这是误解。FairyGUI 的工程本质上是文件夹加描述文件的集合每个 Package 有资源目录每个 Component 对应一个 XML 描述文件里面定义了显示列表、坐标、尺寸、关联、控制器、动效等等。下面是一个经过简化、保留核心字段的组件结构示意component idc1 nameLoginPanel width1280 height720 displayList image idn1 namebg srcui://common/bg_main xy0,0 size1280,720/ button idn2 nameloginBtn srcui://common/btn_login xy560,500 size160,80 relation target sidecenter/ /button text idn3 nametitleText fontfont_main size36 color#FFFFFF xy520,420 aligncenter/ /displayList /component这里displayList就是组件内的显示对象列表n1/n2/n3是节点 idname是你在编辑器里看到的名字src指向资源链接relation表示关联关系。整个结构很像网页里 DOM 加 CSS 的组合只不过字段和语义更偏游戏 UI。这套结构非常规整凡是规整的东西LLM 就能学、能生成、能被程序校验。2.2 FairyGUI 三元组package、component、resource 之间的关系要安全地让 Agent 碰 FairyGUI 数据必须让它先理解三个概念Package 是资源包容器Resource 是图片/字体/音频等原始资源Component 是由 Resource 组装出来的可复用界面节点。三者靠 URL 串联比如ui://common/btn_login指的是common这个包里的btn_login资源。还有一层容易忽略的关联是嵌套组件一个 Component 可以引用另一个 Component比如主界面里嵌着头像组件。这层嵌套关系在生成时最容易出问题因为 Agent 生成到一半经常忘记被引用的子组件必须已经存在于同一个 Package 中导致编辑器打开工程时报资源缺失。我建议在给 Agent 的任务描述里永远把三元组关系写死先列 resource 清单再列 component 清单最后列 component 之间的引用关系。这个顺序不能乱乱一次后面全塌。2.3 Agent 为什么比传统脚本更适合干这件事回到第一节的结论Agent 的不可替代性在于语义理解。给它一句背包界面顶部是标题和关闭按钮中间是 6 行 5 列格子底部左侧是出售、右侧是分解它能够推理出这是一个 1280x720或项目标准分辨率的面板顶部高度约 100px按钮右上对齐网格用 GList 或手动铺 30 个格子底部按钮左右分布。这些推理在传统脚本里要写成几百条规则而且换个 UI 就不适用了。另外Agent 写代码的对话式纠错能力也很有用。你不需要一次性让它生成完美结果而是可以把它生成的 XML 或代码丢回去问它grid 列表的 itemRenderer 为什么没有执行它能顺着代码上下文帮你排查。在后续的实测里这种交互方式反而比一次性指令省时间得多。3. 让 Agent 干活的第一步把UI需求翻译成可执行的任务链3.1 给 Agent 建一个 FairyGUI 领域知识库提示词/知识注入想让 Agent 输出能用的结果不能直接一句帮我写个背包界面就完事它大概率会给你一坨看起来合理但跑不起来的 XML。我的做法是事先搭一套 FairyGUI 领域知识注入核心包括四块内容FairyGUI 组件体系说明GImage、GLoader、GButton、GList、GTextField 等各自的用途和关键属性本项目 UI 规范标准分辨率、常用字体、按钮最小尺寸、间距规范、命名前缀规则组件 XML 结构示例给它几个真实导出的简单 XML 片段让它学习字段的组织方式约束边界资源必须先声明、relation target 必须存在、组件 id 不可重复。这块知识不需要做得很重直接把几条 system prompt 配置到 Agent 里就行。对于项目专属规范我会把规范文档里的关键条款摘出来转成Agent 可读条款而不是把整个文档塞进去。实测下来塞整份文档反而会让它在生成时犹豫不决摘要是更可控的做法。3.2 任务拆解模板布局树、组件类型、关系约束、事件绑定AI Agent 要稳定输出前提是给它稳定的中间格式。我自己设计的拆解模板分四层Agent 每次先输出一个结构化的UI 语义树把它理解到的需求落到 JSON 里然后才允许碰 XML{ panel: { name: BagPanel, resolution: 1280x720, children: [ { type: GImage, name: bg, layout: fullscreen }, { type: GTextField,name: title, text: 背包, pos: top_center, offset: [0, 30] }, { type: GButton, name: closeBtn, pos: top_right, offset: [-30, 30] }, { type: GList, name: grid, pos: center, cols: 5, rows: 6, gap: [8, 8] }, { type: GButton, name: sellBtn, pos: bottom_left, offset: [30, -30] }, { type: GButton, name: decomposeBtn, pos: bottom_right, offset: [-30, -30] } ], relations: [ { from: closeBtn, to: bg, side: top-right }, { from: grid, to: bg, side: center } ] } }这个语义树的好处是它与 FairyGUI 编辑器无关Agent 不需要关心组件 id 怎么排、relation 语法怎么写它只要把布局意图描述清楚。接下来由程序把它转成目标格式。这个设计把AI 的自由发挥和工程的确定性切开了是我整个方案里最关键的一步。3.3 用语义树 - XML的确定性转换器避免 Agent 手写 XML 翻车刚开始我尝试让 Agent 直接输出 XML结果问题非常多组件 id 重复、relation target 写错、属性名大小写不对、XML 特殊字符没转义甚至输出到一半因为 token 限制直接截断。后来我改成Agent 只输出 JSONXML 由 Python 转换器去生成。转换器不需要很复杂按 FairyGUI 的 XML 规格把 JSON 里的布局语义映射成节点属性即可。比如说pos: top_center, offset: [0, 30]就换算成xy和居中锚点cols和rows就生成一个指定grid的列行配置。这样做有三个好处一是输出稳定字段永远不会拼错二是可以注入项目级规则比如所有按钮最小尺寸不小于 160x80三是 XML 里那些AI 最容易胡编的 id、资源 URL 可以完全由程序支配。同样的逻辑也可以用在运行时 API 生成上JSON 语义树直接转 C# 调用代码只是目标格式不同。一个语义树两头输出Agent 的职责始终保持在理解和描述这一层这是 Agent 最擅长、也不容易出错的层。3.4 对接引擎侧让 Agent 顺便写出绑定代码组件只是界面骨架上线前还得写绑定逻辑。这个环节同样可以交给 Agent而且它的表现比我预期好。把语义树里面板、列表和按钮的 name 约定好之后让 Agent 基于 FairyGUI 的 Unity 运行时 API 生成如下这类代码var view UIPackage.CreateObject(Bag, BagPanel).asCom; GList grid view.GetChild(grid).asList; grid.itemRenderer (index, obj) { var item (GButton)obj; item.icon GetIconByIndex(index); item.title GetCountByIndex(index).ToString(); }; grid.numItems 30; view.GetChild(closeBtn).asButton.onClick.Add(() view.Hide()); view.GetChild(sellBtn).asButton.onClick.Add(OnSellSelected); view.GetChild(decomposeBtn).asButton.onClick.Add(OnDecomposeSelected);这段代码本身不难但让 Agent 生成可以省去大量的查文档和试错时间尤其是当项目里封装了自己的UIPackageHelper时Agent 也能按你的封装风格生成统一调用。相比手写质量差异不大速度差异非常明显。4. 实测记录用 Agent 生成一个背包界面省了多少事4.1 实验设计从一句需求描述出发为了验证这套方案到底能在真实项目里省多少时间我拿一个标准背包界面做了对照实验。手工组按传统流程在 FairyGUI 编辑器里从零搭建导入资源、拖组件、配关联、写绑定完整做完记录耗时。Agent 组则走上面说的语义树管线喂给 Agent 的原始需求只有一句话做一个背包面板1280x720底部铺个背景顶部居中标题背包右上角关闭按钮中间是 6 行 5 列物品格子每个格子有图标、数量、品质边框底部左侧出售按钮右侧分解按钮列表支持按品质筛选。然后让它输出语义树、绑定代码再用转换器生成 XML最后人工在 FairyGUI 编辑器里打开验证并调整细节。4.2 真实的生成过程语义树、XML、绑定代码的输出与修正Agent 首轮输出的语义树基本可用但有两处偏离了项目规范一是它给格子图标设计的尺寸是 60x60而项目标准格子是 72x72二是它把筛选逻辑直接写进了GList的numItems但实际项目里的筛选是改变数据源重建列表。我在对话里指出这两点后它第二轮就修正了语义树并同步调整了 C# 绑定代码里 itemRenderer 的写法。转换器生成的 XML 打开后只改了一处底部两个按钮的初始位置与设计稿偏了几个像素属于主观审美差异手动挪一下就完事。整体时间对比如下环节手工组Agent 组资源整理与导入30 分钟30 分钟工作量相同面板搭建与布局2.5 小时8 分钟Agent 生成语义树XML控制器与动效配置1 小时50 分钟Agent 协助但核心靠人绑定代码编写1 小时15 分钟编辑器内校验与微调40 分钟35 分钟总计约 5 小时 40 分钟约 2 小时 18 分钟最大的收益出现在面板搭建和绑定代码两段加起来省了将近 3 小时。控制器和动效依然是人力密集区Agent 目前能帮的有限原因后面细说。4.3 卡住我的四个坑为什么 Agent 生成的 XML 会打不开第一次跑通之前我也翻过车踩过的坑如果按杀伤力排有四个特别值得说坑一是资源 URL 乱写。Agent 在语义树里写的资源名比如btn_sell如果实际 Package 里根本没有这张图XML 生成得再规整也没用编辑器打开直接报资源缺失。后面我在转换器里加了资源白名单校验凡是语义树里出现的资源引用先查项目资源表查不到就抛错。坑二是 relation target 引用不存在。Agent 很爱写这个按钮相对那个底图右对齐但 FairyGUI 的 relation 要求目标必须是真实存在的组件名一旦名字对不上编辑器里面板直接打不开或样式错乱。解决办法是在 JSON 转换阶段做一次引用完整性检查所有relations里的from/to都必须在children里存在。坑三是嵌套组件没注册。主界面里嵌一个头像组件Agent 会在语义树里正常引用它但忘了这个头像组件本身需要先在 Package 里定义为一个独立 Component。这不是 XML 语法问题而是工程结构问题。所以我在 Prompt 里单独加了一条硬性约束被引用的子组件必须出现在 composition 清单里。坑四是 Agent 生成大文件时被 token 截断。一个稍微复杂的面板 XML 很容易上万字符Agent 一次性输出经常断在半路。这个问题的根治方法还是 3.3 节的方案Agent 只输出高密度的 JSON大段 XML 交给脚本生成彻底绕开 token 限制。4.4 哪些环节真的能省时间哪些是伪需求实测之后我对Agent 接管 UI这件事的认知冷静了很多。真正省时间的是四类工作第一类是静态布局初稿也就是从空白面板到元素位置基本可用这个阶段第二类是列表、网格、循环结构这类重复度高的东西是 Agent 强项第三类是命名规范和绑定代码只要约定好规则它每次都能一致输出第四类是批量改样式比如统一把某类按钮换成新的九宫格图直接在语义树里改一处转换器全量重生成。省不了时间的也很明确控制器状态机的复杂切换逻辑、Transition 动效细节、界面手感微调这个按钮的间距就是看着不对劲、DrawCall 优化和美术审美相关的东西这些人类的主观判断 Agent 做到不了位。另外跨机型适配经常要在真机上看完再调这个流程 Agent 还插不上手。认清这些边界比盲目追求全自动更重要。5. 更务实的半接管方案Agent 与 FairyGUI 编辑器的协作流5.1 全自动 vs 半接管我的建议如果你问我A 直接生成项目最终要用的包可不可行我的建议是现阶段别做。原因很朴素FairyGUI 编辑器没有为 AI Agent 准备公开的脚本化写回接口生成的 XML 必须回到编辑器里打开校验、重新发布这个过程本身就要求人来兜底。但全流程 Agent 辅助 人在环上是完全可以跑通的。我的分工建议是环节负责方说明需求理解与布局初稿Agent输出语义树 JSONXML/绑定代码生成确定性脚本 Agent脚本保证语法正确资源与引用完整性校验脚本自动检查拦截低级错误在编辑器内确认人打开工程、目视检查控制器、动效、手感人Agent 提供建议但不下决定回归与走查人 Agent走查报告由 Agent 整理这种协作流听起来没有一键生成整个 UI酷但它在项目里能稳定运行不会因为一次生成错误让团队对 Agent 失去信任。5.2 最小可用工作流Agent 出稿、人来落位、脚本做校验落地这套方案我建议从小面板开始试点流程分五步。第一步从项目里挑一个最标准化的面板比如通用弹窗、确认框、设置项把它的真实 XML 结构喂给 Agent让它照着这个风格输出新的变体。第二步Agent 输出语义树 JSON你在对话里跟它确认布局意图这个阶段把它当UI 转译员用。第三步转换器把 JSON 转成 XML同时跑资源校验和引用校验有错直接退回第二步让 Agent 改。第四步把 XML 放进 FairyGUI 工程目录用编辑器打开确认显示效果。第五步让 Agent 根据语义树生成绑定代码人做 code review 后合入。建议第五步之后顺手沉淀一份Prompt 模板。这个模板把前述的知识注入、任务拆解要求、校验规则都固化下来下次团队成员直接用模板开新对话不用每次从头给 Agent 军训一遍。5.3 配套的校验脚本在 XML 进编辑器之前先拦一道我在前面反复提到校验脚本这里给出一个精简的检查清单建议直接做成脚本自动执行XML 格式合法标签闭合、属性值带引号、无非法字符资源引用存在所有srcui://包名/资源名都能在工程资源表里查到relation 目标存在所有关联引用的组件名都在同一组件或父级真实存在组件 id 唯一displayList里 n1、n2 编号不能重复嵌套组件已注册被引用的子组件已在 Package 的组件清单里尺寸与锚点合法无负数坐标、无超出面板的离谱值。脚本不需要很聪明能用 Python 简单地解析 XML 和 JSON 就行。它的核心价值是把 Agent 和转换器的低级错误挡在编辑器外面让人类每一次打开 FairyGUI 编辑器时面对的都是需要审美判断的问题而不是XML 错了打不开这种纯粹浪费时间的问题。6. 结语接管不是替代而是把最脏的翻译活接过去最后说点我自己的真实体会。做这套方案之前我以为最大的难点是让 Agent 学会 FairyGUI 的格式做之后才明白真正难的是让 Agent 学会不要在格式上自作主张。给它一套语义树 JSON 加确定性转换器之后它反而变得非常好用因为它不用再纠结输出细节只需要做自己最擅长的事情把一句自然语言的需求翻译成结构化的布局意图。这个项目给我最大的教训是Agent 接管的边界必须由人先钉死。你给它越大的自由度它越容易生产看起来华丽但无法落地的结果你给它一个窄而清晰的任务边界它的输出才真正能进流水线。如果你也想在项目里做类似的事我的建议是从一个弹窗、一个设置页这种小面板开始把知识注入和校验脚本跑熟之后再往复杂界面扩。等整条链路跑顺了你会发现原来每次打开 FairyGUI 编辑器最想骂人的那部分时间真的可以省下来了。
返回列表