做过游戏UI的朋友一定体会过这种绝望:策划提了一版新界面,美术丢过来一张合并好的总成图,没有保留分层PSD,而你需要在Unity里把一个一个按钮、头像、输入框从图上抠出来再重新拼好。我在技术社区泡了几年,关于“PSD自动转游戏UI”的讨论看过不少,可大部分方案要么停留在自动切图,要么需要美术额外维护一堆配置表,最后都因为太重而死在半路上。
这篇我打算把我自己搭过、也在团队里跑过的思路完整写下来:把PSD当成数据源,而不是图片源;把“自动转UI”这件事拆成约定命名、脚本解析、引擎装配三段,互相之间用JSON清单衔接。最近很多人关注的“图片转PSD分层插件”也正好能接进去,作为老资源抢救的补位方案。适合的读者是游戏UI美术、技术美术,以及被UI搭建折磨的程序员。看完你会知道这套东西能解决什么、不能解决什么,以及从哪一步开始比较容易落地。
1. 告别手工切图:为什么UI资源转换卡在PSD这一步
1.1 传统PSD转UI流程里的隐性时间黑洞
先聊一个我观察到的现象:很多团队做UI资源转换,其实根本不是在“抠图”这一步浪费时间的。一个普通登录界面,PSD里可能有二十多个图层,美术动手抠一次大概半小时能完成,真正让人崩溃的是抠完之后的场景——导出的原图要根据UI状态切成普通图、九宫格图、透明按钮底图;要在引擎里一个个拖到Canvas下,设置锚点、调坐标、对像素;填border参数还要反复缩放看拉伸效果。
我习惯用一张表来估算这种工作的消耗来源,一个中等复杂度的背包界面通常是这样的:
| 工作项 | 手工处理耗时 | 疲劳感来源 |
|---|---|---|
| 整理图层与重命名 | 30分钟左右 | 纠结图层叫“图层 2”还是“副本 3” |
| 切图导出 | 40分钟左右 | 一个图层一个图层反复框选、Ctrl+J |
| 设置九宫格参数 | 20分钟左右 | 反复试边界,边缘差一个像素都难受 |
| 在引擎节点树里搭结构 | 40分钟左右 | 建空节点、拖图片、改层级顺序 |
| 锚点与分辨率适配 | 30分钟左右 | 不同比例屏幕下预览,反复调anchor |
小半天就这么没了。而且在长期迭代的项目里,这个流程不是跑一次就完事——策划改文案要重新切,美术调一次配色要重新导,UI状态从普通到按下到禁用,每一张都要走一遍流程。真正的问题不是谁操作不熟练,而是“重复劳动密度太高”,高到人一多就一定出错。
1.2 为什么大家开始搜“图片转PSD分层”类插件
最近“图片转PSD分层插件”这个词搜索量涨得很厉害,并不是大家突然对PSD有情怀了,而是现实需求堆出来的。我遇到的典型场景至少有三类:
一是老项目翻新。很多运营了好几年的游戏,原班人马早散了,服务器端代码还在,美术资源却只有当年打包进客户端的合并图,分层PSD可能早就躺在离职同事的硬盘里找不到了。二是外包交付物不完整。拿到手的PSD只有最终效果图,源文件里是合并图层,甚至干脆只给PNG。三是前期用AI生成构图、或者从网图找灵感,想把这个效果直接变成可继续编辑的UI源文件。
这些场景都需要一个前置步骤:从一张成品图还原出多图层结构。市面上这些图片转分层PSD工具干的事就是把人从“照着图重新画一遍”里解放出来。不过必须说清楚,它们跑出来的结果,离“生产级UI源文件”还差得远,这个我在第4部分会展开讲。它在整条链路里更像一个“上游数据抢救器”,真正的自动化转UI还得靠一套系统化流程来接手。
1.3 从“自动切图”到“自动搭UI”的跨越
这里要澄清一个概念:自动切图和自动转UI完全是两件事。自动切图做完,你得到的是一堆PNG;自动转UI做完,你得到的是一个可以在引擎里直接运行、节点结构清晰、该拉伸的地方会拉伸、该适配的地方能适配的界面框架。
所以我的新思路是用三层结构来处理这件事:
第一层是约定层,用图层命名给PSD里的元素打上“UI语义”标签,比如这是九宫格、这是按钮、这是锚点参考。第二层是解析层,用PS脚本把图层树读出来,同时导出切图和一份完整的JSON描述清单。第三层是装配层,引擎侧读取JSON,自动在场景里生成UI节点树、设置组件参数、挂上预制体引用。
这套思路最核心的变化,是让PSD不再只被当作图片素材库,而是变成一个包含位置、层级、尺寸、语义的“数据结构源”。只有完成了这种视角切换,自动化才有真正的发挥空间。
2. 新思路核心:把PSD当成数据源而不是图片源
2.1 图层树就是天然的UI结构树
PSD内部本身就是一棵树:文件夹(LayerSet)下面套图层,图层可以整组复制、整体隐藏、批量调整透明度。这个层级关系和游戏引擎里的UI节点树几乎一一对应。父级组对应一个空节点,子图层对应子节点,组内顺序对应渲染顺序。既然如此,用脚本把图层树完整读出来,其实就拿到了UI节点树的雏形。
我在项目里用Photoshop的ExtendScript跑过一段很简单的递归遍历,代码大概是这个意思:
var doc = app.activeDocument; var root = { name: doc.name, width: doc.width.value, height: doc.height.value, children: [] }; function walk(layers, target) { for (var i = 0; i < layers.length; i++) { var layer = layers[i]; var item = { name: layer.name, visible: layer.visible, opacity: layer.opacity / 100, bounds: [layer.bounds[0].value, layer.bounds[1].value, layer.bounds[2].value, layer.bounds[3].value] }; if (layer.typename === "LayerSet") { item.children = []; walk(layer.layers, item.children); } target.push(item); } } walk(doc.layers, root.children);这段代码会把图层的名称、可见性、透明度、包围盒、父子关系全部抓出来。单看这些数据,其实已经足够判断一个界面大概长什么样了。再配合导出切片,引擎端就能根据数据把节点搭出来。这样做的价值在于,整个转换过程中不需要人手动去“看”界面——机器直接读数据结构,速度和准确性都远高于肉眼。
2.2 约定式命名:把美术意图传进引擎
那么问题来了:PSD里只存了图层名称和坐标,可“这个图是九宫格拉伸”“那个位置是按钮可点击区域”“这个角要固定住”这些UI语义,PSD本身并不知道。想让计算机理解,就必须有一种机器能解析的语义层。我见过不少团队用配置表去维护这些信息,效果都不好,因为配置表一旦和PSD不同步,就是一场灾难。
最便宜可靠的方案是约定式命名,直接用图层名字传递语义。我们在项目里定了一套很轻的后缀标记:
#n:忽略该图层,不参与导出#9:该图按九宫格拉伸#9(12,12,24,24):九宫格参数,分别对应左、上、右、下的透明边界宽度#a_左上、#a_右上、#a_居中:该图层在父节点内的锚点#btn_前缀:标记为一个按钮节点#txt_前缀:标记为文本节点
这套标记美术学起来非常快,因为大家本来就会给图层起名,只是以前起得比较随意,现在变成了一种“人机共识”。例如一个关闭按钮的图层树:
grp_close #btn_关闭 ├── bg #9(8,8,8,8) └── icon脚本读到这些标记后,就会把grp_close识别为按钮,bg识别为九宫格背景,icon识别为普通贴图。不使用额外配置文件,PSD本身就是配置载体,这能从根本上避免“两套文件不同步”的问题。
2.3 混合模式、蒙版与特殊样式的处理策略
当然,PSD里的图层不可能都那么干净,混合模式和蒙版是最常见的坑。正片叠底、柔光、颜色加深这些效果在引擎UI里很难做到百分百一致,尤其是不同平台下UI渲染还受图集影响。我的处理原则是:在导出阶段尽量“摊平”。
具体来说,如果某个图层带有投影、外发光、描边等图层样式,我会建议前端美术在使用自动化管线前,先把图层栅格化并合并到对应图层上,这样导出的就是带特效的最终位图。如果图层用了蒙版,判断一下:蒙版只是简单圆角或者裁剪,就直接裁边导出;蒙版很复杂,那也合并后再导出。文本图层则相反,尽量保留文本信息,输出到JSON里,让引擎侧用真实的Text组件去渲染,而不是把文字导成图片。
| 图层状态 | 推荐处理方式 | 原因 |
|---|---|---|
| 普通图形层 | 直接导出PNG | 保留透明通道即可 |
| 投影/发光图层 | 合并图层样式后导出 | 避免引擎还原偏差 |
| 被蒙版裁剪的图层 | 裁切后导出 | 引擎里再叠加蒙版性价比太低 |
| 九宫格背景 | 保持足够透明边后导出 | 配合slice参数使用 |
| 文本图层 | 导出文字内容+字体信息 | 引擎原生支持文字渲染 |
这里要有一说一,自动化不是神仙术,做不到100%像素级复现。设计上追求的是“视觉误差可控”,只要核心UI元素位置、尺寸、拉伸行为正确,效果细节上的误差可以在引擎里手动微调。
3. 落地方案一:基于PS脚本的约定式自动切图管线
3.1 Photoshop脚本能读到什么数据
Photoshop的ExtendScript虽然语法老一些,但能力其实很强。新一代的UXP插件系统也能做类似的事,而且跨平台、可用现代JS写,是明显的大趋势。脚本里可以遍历到每个图层的属性:name、bounds、visible、opacity、blendMode、locked,还能直接调用导出接口把图层输出为PNG/WebP等格式。
我一般会在脚本里加一步校验:把所有图层的名字先扫一遍,凡是不符合命名规范的,列出警告清单,防止后面生成一个带“图层 3 副本”这类垃圾名字的UI树。这一步非常关键,相当于给流程设了一个前置质量门禁。
3.2 导出规则设计:切片、命名、JSON清单
切图规则要简单可预期,我们团队的目录约定大概是这样:
exports/ images/ # 普通贴图 slices/ # 九宫格贴图 fonts/ # 文字信息及字体文件 ui_json/ # 每个界面的结构说明文件脚本根据图层上的标记自动分类,比如带#9的送去slices,普通图层送去images,带#txt_的文本图层把内容写入JSON而不是导图片。最终每个界面都会生成一份清单文件,结构有点像这样:
{ "canvas": { "width": 1920, "height": 1080 }, "name": "login_panel", "type": "panel", "children": [ { "name": "bg", "type": "image", "pos": [0, 0], "size": [1920, 1080], "source": "images/login_bg.png", "slice": { "left": 40, "top": 40, "right": 40, "bottom": 40 } }, { "name": "btn_start", "type": "button", "pos": [860, 700], "size": [200, 80], "source": "images/btn_start.png", "anchor": [0.5, 0.5] } ] }这份JSON就是PSD和引擎之间唯一的“翻译契约”。引擎侧只需要关心JSON里的字段,不需要关心PSD长什么样。
3.3 坐标系统一与像素密度处理
坐标单位不一致是很容易翻车的点。PSD里通常默认72dpi,坐标为左上原点,而Unity里Canvas坐标可能是左下原点,UE的UMG用的又是另一种坐标体系。我在脚本里会把“设计分辨率”固定成一个常量,比如1920x1080,导出的坐标统一以这个设计分辨率为准。同时输出坐标前做一次Y轴翻转:如果引擎原点在左下,那就用y = designHeight - psdY - elementHeight做转换。
这里我基于常见项目实践补充一句:不管用什么引擎,一定要在脚本和引擎侧把基准分辨率统一,否则坐标偏一点,后面每个界面都要手动救,自动化就没有意义了。我自己写过一版忘记翻转Y轴的脚本,结果所有按钮全部头朝下,排查了整整一个下午才意识到是坐标问题。
3.4 脚本管线的成本与收益
经常有人问我“我小团队,值得搞这套吗”。按我的经验,一个基础的PS导出脚本加JSON生成器,找一个熟悉脚本开发的人来做,一般2到3周能跑通第一版,后面再花时间打磨细节。收益也很直接:跑通之后,每个界面的资源准备工作从半天压缩到几分钟,剩下的只是人工复查和微调。单次开发看起来投入不小,但只要项目还要继续迭代两个月以上,这笔账基本不会亏。
不过这套方案对输入质量有要求:PSD图层结构越规范,自动化成功率越高。如果项目本来就没有图层命名习惯,那scripts写得再好也拦不住“图层 1副本”这种输入。所以团队级落地的时候,规范推行比写脚本本身更难,也更值得花时间。
4. 落地方案二:AI辅助分层的兜底流程(图片转PSD分层)
4.1 什么情况下需要AI辅助分层
并不是所有项目都能拿到规范PSD。我自己接过好几次这种任务:运营多年的老游戏要做版本升级,美术资源只剩下客户端里的UIPrefab和贴图;又或者是外包交付了一个文件夹,里面全是“最终效果图.png”,问就是找不到源文件;再比如用AI出图工具生成了一张高质量界面概念图,想直接拿来做可编辑的UI底子。
这种时候,最现实的办法就是先“逆分层”——用图片转PSD分层的工具,把一张成品图尽可能拆分成可编辑的多层PSD。这是整个自动化工作流里唯一适合用AI兜底的地方。
4.2 图片转PSD分层插件的实际效果与边界
最近社区里讨论比较多的图片转PSD分层插件,核心能力是利用图像语义分割把画面里的元素拆开:角色、背景、前景装饰、物品能分到不同图层,还能输出带透明通道的PSD。我用下来的感觉是:它离“生产环境直接可用”还有一段距离,但已经有很强的辅助价值。
先说优点:对主体和背景的分离很有效,能从一张成品图快速生成结构近似的图层树;边缘整体是闭合的,比手工用魔棒抠图干净得多。再说硬伤:图层命名基本没有章法,出来的图层可能叫“图层5”或者“主体_分离_3”;透明边缘偶尔会带一圈半透明的杂边;复杂的图层样式、文字图层它还原不了,文字基本都拍成了位图。
所以我的结论是:这类工具的价值在于“抢救”而不是“生产”。它能把一个无从下手的合并图变成可继续编辑的底稿,但后续必须人工整理——去杂边、重新分组、手动给关键元素补上#9等语义标记。
4.3 AI分层之后继续跑自动转换的完整链路
实际项目里,我把AI分层的结果和自动转UI流程接起来后,形成了一条“资源复活SOP”,步骤是这样的:
第一步,用图片转PSD分层工具把成品图拆出若干独立图层,保存为PSD。第二步,在PS里做低成本整理:把主体、背景、按钮分组,去掉明显杂边,在需要拉伸的图层上补#9标记,在关键按钮上补#btn_前缀。这个过程大概十几分钟,但能显著提高后续自动化成功率。第三步,跑第3部分里的PS脚本,导出切片和JSON清单。第四步,引擎侧读取JSON生成UI Prefab。
这条链路我至少跑过三次老项目翻新,节省的时间非常可观。原来这种需求基本等于照着原图重做UI,现在至少能保底还原出80%的静态结构,剩下的再手动微调。
4.4 分层质量验收清单
如果准备把AI分层的结果接进正式管线,我建议你把它当“外购资源”一样做验收,而不是直接闭眼导入。这是总结的检查项:
- 透明背景的边缘不能有白边或黑边,半透明杂边需要清理
- 需要九宫格拉伸的图层,透明安全边要留足,否则拉伸后边缘会模糊
- 文字尽量保留真实文字属性,别一上来就拍成位图
- 图层命名必须符合团队命名规范,否则后面自动生成出来的节点全是一堆“图层5”
- 分辨率要和设计的基准分辨率一致,不然坐标会整体偏移
AI分层工具现在进步很快,但把它的输出直接当成生产资源仍然有风险。我见过不止一个团队因为偷懒跳过验收,结果自动生成的UI里一半按钮边缘带白线,最后还得返工。让AI做它擅长的事,让流程做它擅长的事,这才是正确姿势。
5. 引擎侧还原:从JSON到可运行的UI Prefab
5.1 Unity侧的自动构建思路
拿到JSON之后,引擎侧要做的就是“把数据变成节点”。在Unity里我通常用Editor脚本处理,核心思路是:
在资源导入阶段,先由AssetPostprocessor监听图片资源,自动把TextureImporter的Texture Type设为Sprite,有slice字段的图直接写入SpriteEditor的border。再写一个Editor工具,读取上一章生成的JSON,内存里创建UI节点树,给每个节点对应添加RectTransform、CanvasRenderer、Image、Button、Text等组件,设置好坐标、尺寸、锚点,组件之间的事件引用也一并串起来,最后保存成Prefab并放进场景里。
如果不深入代码细节,我会强调一个原则:这批脚本最好只在编辑器里运行一次,生成时就保存为预制体,运行时不依赖任何动态读取。原因是把UI节点用运行时脚本临时拼出来,会增加加载时间,也容易引出一堆生命周期问题。让JSON只在编辑期“翻译”成Prefab,游戏运行时拿到的就是干干净净的静态资源。
5.2 九宫格与锚点的自动映射
九宫格参数在Unity里对应Sprite的border,我上面JSON里已经写了slice字段。导入时直接把它写进TextureImporter,生成的Sprite就自动带上了九宫格属性,UI的Image组件设为Sliced就能正确拉伸。锚点对应RectTransform的anchorMin和anchorMax,映射关系可以做成一张表:
| PSD标记 | anchorMin | anchorMax | 语义 |
|---|---|---|---|
| #a_左上 | (0, 1) | (0, 1) | 固定在左上 |
| #a_右上 | (1, 1) | (1, 1) | 固定在右上 |
| #a_左下 | (0, 0) | (0, 0) | 固定在左下 |
| #a_右下 | (1, 0) | (1, 0) | 固定在右下 |
| #a_居中 | (0.5, 0.5) | (0.5, 0.5) | 固定在中心 |
| #a_顶部居中 | (0.5, 1) | (0.5, 1) | 顶部水平居中 |
有了这张映射表,脚本就能把“这个按钮固定在哪”的问题完全自动化。剩下需要人工参与的,就是不同分辨率下安全区、刘海屏等特殊情况。
5.3 层级顺序与自适应缩放的细节
层级顺序这里有个容易搞反的细节:PSD里最顶层的图层在视觉上最靠前,而Unity的UGUI里后添加的兄弟节点渲染顺序更靠前。所以脚本生成节点时,需要把PSD图层从上到下的顺序反转之后再设置siblingIndex,否则导出来的界面层级是反的。
自适应缩放方面,我的习惯是把CanvasScaler设为Scale With Screen Size,referenceResolution和PSD里的设计分辨率保持一致,这样坐标数据可以直接用,不用二次换算。同时要留意项目是否做了横竖屏适配,横竖屏切换时锚点配置决定一切。之前有个界面在竖屏没问题,切横屏后按钮飞出屏幕,查了半天发现是锚点默认居中,没有按角色位置设置锚点,导致拉伸时整个面板跟着画面中心跑了。
5.4 UE等其他引擎的迁移思路
Unity之外,UE的UMG也完全可以套同一套思路。UMG的CanvasPanel自带锚点对齐逻辑,但面板结构是通过Widget Blueprint保存的,比较难用纯数据直接生成。通常的做法是写一个编辑器插件,读取JSON后用C++或者Python脚本动态创建Widget,调用其构造函数与相关Blueprint面板属性,生成后保存为资产。相比Unity,UE侧的自动生成门槛会高一些,但流程逻辑是一样的。
6. 我在实际项目中踩过的坑和总结
6.1 最难的坑:美术团队命名规范执行不到位
写脚本的阶段,我以为最难的是技术实现,真正跑起来之后才发现,最难的是让美术团队稳定地执行命名规范。我们第一版脚本上线后,跑一个合作外包交付的界面,导出的资产清单里一半是“图层 2”“组 5”,生成的UI节点树当场变成灾难。
后来我给Photoshop挂了一个检查脚本,导出前先扫描一遍图层名,不符合规范的直接列出清单,并且提供“一键按层级自动重命名”的功能。这不只是为了程序方便,其实对美术也有好处——规范命名的PSD以后谁接手都好改。自动化程度越高,对输入规范性要求越高,这条约束是躲不掉的。
6.2 一次九宫格参数丢失的完整排查链路
再分享一次让我印象很深的排查过程。现象是:某个面板的背景图在运行时被拉得四角模糊、边缘发虚,看起来很不正常。
排查时我先把Image组件确认了一遍,Type确实是Sliced,但这没有解决问题。接着检查对应Sprite的border,发现全部是0,也就是九宫格参数压根没进引擎。再去查JSON清单,发现slice字段是空的。回到PS脚本,发现那个图层上根本没有#9标记。最后打开原PSD,真相大白——这个背景图层在交付之前被某个美术同学“合并图层”操作处理过,原本一圈透明安全边被裁掉了,九宫格标记自然也没了。
事后从三个方向上做了改进:一是规定九宫格图不允许合并图层,标记必须保留在图层名上;二是PS脚本里增加预警机制,发现尺寸比例接近九宫格但缺少标记的图层时,导出warning日志;三是在引擎侧增加border为0时的报错提示信息,避免静默失败。这次教训让我明白一个道理:自动化流程里的每一步都应该留下可追溯的结构化痕迹,否则出了问题,你连“它什么时候开始错的”都定位不到。
6.3 “自动”的正确姿态:自动化负责80%,留20%给人
做这套管线越久,我越坚定一个想法:自动化不是“全自动”,而是一条流水线。机器负责80%的重复劳动——切图、切片、搭层级、设锚点、命名;人负责剩余20%的审美与体验判断——按钮按下的动画、转场效果、多语言文字长度适配、特殊状态下的布局微调。
别一上来就想着做成“一键全自动,生成完直接上线”,那个目标在当前技术条件下不现实,而且会带来一个副作用:当自动生成的UI看起来“差不多”时,团队容易集体麻木,不再逐像素检查,最后上线的界面细节经不起推敲。我给团队的建议往往是:自动生成的资源必须过一遍人工review,review重点不是“图有没有切对”,而是“这个界面看起来是否还是设计师想要的味道”。
6.4 做完一遍之后,我体会最深的事
这套管线最值钱的部分,其实不是脚本本身,而是你在搭的过程中被逼着把“UI到底该怎么组织”重新想了一遍。以前大家做UI全凭手感,节点树乱得像毛线团;现在因为需要机器去解析,整个结构必须清晰干净,反而倒逼团队形成了统一规范。这个收益是长期的,哪怕以后不用脚本了,这些规范也会让跨岗位协作顺畅很多。
扩展方向也很多。我试过把JSON清单进一步对接UI自动化测试,让测试脚本直接按节点名定位元素;也试过把标记规范反向用到AI生成的UI设计图上,让AI出图时自带可解析的图层语义;图集打包、SpriteAtlas生成同样可以提前接入这套流程,把零散贴图自动聚合。这一切的前提是你先把“从PSD到引擎”这段路走通,而这篇写的,就是我走过之后留下的路标。