什么是高端网站建设山东网站定制设计
- 作者: 五速梦信息网
- 时间: 2026年04月20日 09:19
当前位置: 首页 > news >正文
什么是高端网站建设,山东网站定制设计,广州网站二级等保,有什么网站可以做投票工具效果#xff1a; 第一步#xff0c;把psd图层转换为可编辑的节点树#xff0c;并自动解析UI类型、自动绑定UI子元素#xff1a; 第二步, 点击“生成UIForm按钮生成UI预制体 (若有UI类型遗漏可在下拉菜单手动点选UI类型)#xff1a; 验证一键生成UI效果: 书接上…工具效果 第一步把psd图层转换为可编辑的节点树并自动解析UI类型、自动绑定UI子元素 第二步, 点击“生成UIForm按钮生成UI预制体 (若有UI类型遗漏可在下拉菜单手动点选UI类型) 验证一键生成UI效果: 书接上回【Unity编辑器扩展】(二)PSD转UGUI Prefab, 图层解析和碎图导出_psd导入unity_TopGames的博客-CSDN博客 先上总结 工具包含的功能
- 支持UGUI和TextMeshProGUI并新增FillColor(纯色)共16种UI类型 Button、TMP Button、 Dropdown、TMP Dropdown、FillColor、Image、InputField、TMP InputField、Mask、RawImage、Scroll View、Slider、Text、TMP Text、Toggle、TMP Toggle。
- 支持自定义UI类型匹配词支持扩展自定义解析器Helper易扩展。
- 支持批量导出图片和单独导出某个图层图片美术仅提供psd无需切图。
- 支持自动同步UI元素位置和文本字体、字号、行列间距、字体颜色。解放繁琐的手动调节流程。
- 自动根据UI类型导出图片为Sprite、Texture2D类型并支持选择导出后是否压缩图片若UI需要9宫拉伸自动对Sprite设置9宫边界。
- 支持编辑(手动调节层级或UI类型)如果UI设计师有遗漏标记类型程序可手动点选类型类型刷新后工具自动绑定UI元素。
- 支持编辑阶段预览psd图层、组。
- 支持任意UI类型嵌套组合psd图层层级导出为UI预制体后保持一致。 Aspose.PSD库虽然很强大但它毕竟是脱离PS的独立解析库对于PS的有些功能支持并不完善比如图层特效(如描边、浮雕、阴影等)把单个图层转换为图片图层的特效会丢失。对于文本图层转换为图片后会有字体样式改变的问题。比如PS文本用的是宋体字体转换为图片后变成了默认的雅黑字体。 好在Aspose.PSD支持半个PS智能对象为什么说是半个因为Aspose.PSD完美支持PS智能对象图层但是通过Aspose.PSD把带有特效的PS图层转换为智能对象后会丢失图层特效。 为了解决这一问题不得不对之前的设计做出让步写一个自动转换图层为智能对象的PS脚本以避免设计师手动转换会有遗漏。UI设计师交付psd前通过脚本自动把所有文本图层和带有特效的图层转换为智能对象。这样才能绕过Aspose.PSD导出图片丢失图层特效的问题。 尽管有了使用PS脚本这个小瑕疵但相比让UI设计师单独切图并手动标识各个切图位置大小、字体字号颜色等他们仍然觉得这是一个巨大的解放。同样对于技术来说也节省大量时间。即使设计师遗漏了UI类型标记也可以通过下拉框选择图层的UI类型仅需简单标记类型就可以一键生成UI预制体。 Aspose.PSD仍在每月一个版本更新迭代期待功能完善摆脱所有瑕疵。 PSD转UGUI功能/工作流及原理: 一、PSD规范要求(UI设计师) 由于UI大多属于复合型UI(如上图)即由多种UI元素类型组合而成。例如Dropdown(下拉菜单)主要由下拉框下拉列表下拉列表Item三个主体组成而三个主体又是由其他多个UI元素组成。 UI是由一个或多个UI元素构成因此多个元素之间必须有父子节点的关系。而PS图层中没有这种关系只能通过组(Group)把多个图层包起来而组本身是一个空图层。 例如一个Button通常包含一个背景图和一个按钮文本。图层结构如下 实际上UI设计师原本也是需要用组来管理图层和切图的这一规范并不是问题。主要是UI类型标记通过对图层命名以.类型工具通过对图层类型的识别以及每种UI有单独的解析Helper最大程度上智能判定识别UI元素类型对于无迹可寻的元素仍然需要设计师手动标记UI类型。 例如Button解析器(ButtonHelper), 会依次按类型查找图层, 可以最大化放宽对图层标记类型 buttonBackground LayerNode.FindSubLayerNode(GUIType.Background, GUIType.Image, GUIType.RawImage); buttonText LayerNode.FindSubLayerNode(GUIType.Button_Text, GUIType.Text, GUIType.TMPText); 二、解析规则配置 支持配置文本图层和非文本图层的默认类型例如文本图层默认识别为Text或TextMeshProGUI类型普通图层默认识别为Image或RawImage类型。 UI Type: 主UI类型和子UI类型。支持的类型如下 UIPrefab: UI模板预制体。 TypeMatchesUI类型匹配名, 例如Button的匹配项有.bt.btn.button。图层名以这些字符结尾就会被识别为Button。 UIHelper: UI的解析逻辑。不同的UI通过重写解析方法对UI元素和对应PS图层进行绑定以及生成最终的UI GameObject。 Comment注释说明用于一键导出说明文档给UI设计师参考。 总的来说规则配置文件是为了更灵活宽松可以自由自定义多个UI类型的别名。 以下是一键导出的文档内容 使用说明: 单元素UI即单个图层的UI如Image、Text、单图Button可以直接在图层命名结尾加上.类型来标记UI类型。 如A.btn表示按钮。 多元素UI: 对于多个图片组成的复合型UI可以通过使用组包裹多个UI元素。在“组”命名结尾加上.类型来标记UI类型。 组里的图层命名后夹.类型来标记为UI子元素类型。 各种UI类型支持任意组合如一个组类型标记为Button组内包含一个按钮背景图层一个艺术字图层(非文本图层)就可以组成一个按钮内带有艺术字图片的按钮。 UI类型标识: 图层/组命名以.类型结尾 UI类型标识列表: Image: UI图片, Sprite精灵图,支持九宫拉伸 类型标识: .img, .image, RawImage: Texture贴图, 不支持九宫拉伸 类型标识: .rimg, .tex, .rawimg, .rawimage, Text: UGUI普通Text文本 类型标识: .txt, .text, .label, TMPText: Text Mesh Pro, 加强版文本类型. 通常无需标注此类型使用Text类型即可 类型标识: .tmptxt, .tmptext, .tmplabel, Mask: 遮罩图根据遮罩图alpha对可使区域混合 类型标识: .msk, .mask, FillColor: 纯色直角矩形图例如直角矩形纯色图层可以在Unity中设置颜色实现无需导出纯色图片 类型标识: .col, .color, .fillcolor, Background: 背景图, 如Button背景Toggle背景、InputField背景、ScrollView等 类型标识: .bg, .background, .panel, Button: 按钮, 通常包含按钮背景图、按钮文本 类型标识: .bt, .btn, .button, TMPButton: 按钮(Text Mesh Pro) 类型标识: .tmpbt, .tmpbtn, .tmpbutton, Button_Highlight: 按钮高亮时显示的按钮图片(当按钮为多种状态图切换时) 类型标识: .onover, .light, .highlight, Button_Press: 按住按钮时显示的图片(当按钮为多种状态图切换时) 类型标识: .press, .click, .touch, Button_Select: 选中按钮时显示的图片(当按钮为多种状态图切换时) 类型标识: .select, .focus, Button_Disable: 禁用按钮时显示的图片(当按钮为多种状态图切换时) 类型标识: .disable, .forbid, Button_Text: 按钮文本必须是文本图层. 如果是艺术字图片可以标记为Image 类型标识: .bttxt, .btlb, .bttext, .btlabel, .buttontext, .buttonlabel, Dropdown: 下拉菜单, 由下拉框、下拉列表(ScrollVIew)、Toggle类型的item组成 类型标识: .dpd, .dropdown, TMPDropdown: 按钮(Text Mesh Pro) 类型标识: .tmpdpd, .tmpdropdown, Dropdown_Label: 下拉框上显示的文本 类型标识: .dpdlb, .dpdlabel, .dpdtxt, .dpdtext, .dropdowntext, .dropdownlabel, .dropdowntxt, .dropdownlb, Dropdown_Arrow: 下拉框箭头图标 类型标识: .dpdicon, .dpdarrow, .arrow, .dropdownarrow, InputField: 文本输入框通常由输入框背景图、提示文本、输入文本组成 类型标识: .ipt, .input, .inputbox, .inputfield, TMPInputField: 文本输入框(Text Mesh Pro) 类型标识: .tmpipt, .tmpinput, .tmpinputbox, .tmpinputfield, InputField_Placeholder: 输入框内的提示文本 类型标识: .placeholder, .ipttips, .tips, .inputtips, InputField_Text: 输入框输入的文本(样式) 类型标识: .ipttxt, .ipttext, .iptlb, .iptlabel, .inputtext, .inputlabel, Toggle: 单选框/复选框 类型标识: .tg, .toggle, .checkbox, TMPToggle: 勾选框/单选框/复选框(Text Mesh Pro) 类型标识: .tmptg, .tmptoggle, .tmpcheckbox, Toggle_Checkmark: 勾选框勾选状态图标 类型标识: .mark, .tgmark, .togglemark, Toggle_Label: 勾选框文本 类型标识: .tglb, .tgtxt, .toggletext, .togglelabel, Slider: 滑动条/进度条通常由背景图和填充条组成 类型标识: .sld, .slider, Slider_Fill: 滑动条/进度条的填充条 类型标识: .fill, .sldfill, .sliderfill, Slider_Handle: 滑动条的拖动滑块 类型标识: .handle, .sldhandle, .sliderhandle, ScrollView: 滚动列表通常由背景图、垂直/水平滚条背景图以及垂直/水平滚动条组成 类型标识: .sv, .scrollview, .lst, .listview, ScrollView_Viewport: 滚动列表的视口遮罩图 类型标识: .vpt, .viewport, .svmask, .lstmask, .listviewport, .scrollviewport, ScrollView_HorizontalBarBG: 滚动列表的水平滑动条背景图 类型标识: .hbarbg, .hbarbackground, .hbarpanel, ScrollView_HorizontalBar: 滚动列表的水平滑动条 类型标识: .hbar, .svhbar, .lsthbar, ScrollView_VerticalBarBG: 滚动列表的垂直滑动条背景图 类型标识: .vbarbg, .vbarbackground, .vbarpanel, ScrollView_VerticalBar: 滚动列表的垂直滑动条 类型标识: .vbar, .svvbar, .lstvbar, UGUI Parser代码 #if UNITY_EDITOR using Aspose.PSD.FileFormats.Psd.Layers.FillLayers; using System; using System.IO; using System.Linq; using System.Text; using TMPro; using UnityEditor; using UnityEngine;namespace UGF.EditorTools.Psd2UGUI {public enum GUIType{Null 0,Image,RawImage,Text,Button,Dropdown,InputField,Toggle,Slider,ScrollView,Mask,FillColor, //纯色填充TMPText,TMPButton,TMPDropdown,TMPInputField,TMPToggle,//UI的子类型, 以101开始。 0-100预留给UI类型, 新类型从尾部追加Background 101, //通用背景//Button的子类型Button_Highlight,Button_Press,Button_Select,Button_Disable,Button_Text,//Dropdown/TMPDropdown的子类型Dropdown_Label,Dropdown_Arrow,//InputField/TMPInputField的子类型InputField_Placeholder,InputField_Text,//Toggle的子类型Toggle_Checkmark,Toggle_Label,//Slider的子类型Slider_Fill,Slider_Handle,//ScrollView的子类型ScrollView_Viewport, //列表可视区域的遮罩图ScrollView_HorizontalBarBG, //水平滑动栏背景ScrollView_HorizontalBar,//水平滑块ScrollView_VerticalBarBG, //垂直滑动栏背景ScrollView_VerticalBar, //垂直滑动块}[Serializable]public class UGUIParseRule{public GUIType UIType;public string[] TypeMatches; //类型匹配标识public GameObject UIPrefab; //UI模板public string UIHelper; //UIHelper类型全名public string Comment;//注释}[CustomEditor(typeof(UGUIParser))]public class UGUIParserEditor : Editor{private SerializedProperty readmeProperty;private void OnEnable(){readmeProperty serializedObject.FindProperty(readmeDoc);}public override void OnInspectorGUI(){serializedObject.Update();if (GUILayout.Button(导出使用文档)){(target as UGUIParser).ExportReadmeDoc();}EditorGUILayout.LabelField(使用说明:);readmeProperty.stringValue EditorGUILayout.TextArea(readmeProperty.stringValue, GUILayout.Height(100));serializedObject.ApplyModifiedProperties();base.OnInspectorGUI();}}[CreateAssetMenu(fileName Psd2UIFormConfig, menuName ScriptableObject/Psd2UIForm Config【Psd2UIForm工具配置】)]public class UGUIParser : ScriptableObject{public const int UITYPE_MAX 100;[SerializeField] GUIType defaultTextType GUIType.Text;[SerializeField] GUIType defaultImageType GUIType.Image;[SerializeField] GameObject uiFormTemplate;[SerializeField] UGUIParseRule[] rules;[HideInInspector][SerializeField] string readmeDoc 使用说明;public GUIType DefaultText defaultTextType;public GUIType DefaultImage defaultImageType;public GameObject UIFormTemplate uiFormTemplate;private static UGUIParser mInstance null;public static UGUIParser Instance{get{if (mInstance null){var guid AssetDatabase.FindAssets(t:UGUIParser).FirstOrDefault();mInstance AssetDatabase.LoadAssetAtPathUGUIParser(AssetDatabase.GUIDToAssetPath(guid));}return mInstance;}}public static bool IsMainUIType(GUIType tp){return (int)tp UITYPE_MAX;}public Type GetHelperType(GUIType uiType){if (uiType GUIType.Null) return null;var rule GetRule(uiType);if (rule null || string.IsNullOrWhiteSpace(rule.UIHelper)) return null;return Type.GetType(rule.UIHelper);}public UGUIParseRule GetRule(GUIType uiType){foreach (var rule in rules){if (rule.UIType uiType) return rule;}return null;}/// summary/// 根据图层命名解析UI类型/// /summary/// param namelayer/param/// param namecomType/param/// returns/returnspublic bool TryParse(PsdLayerNode layer, out UGUIParseRule result){result null;var layerName layer.BindPsdLayer.Name;if (Path.HasExtension(layerName)){var tpTag Path.GetExtension(layerName).Substring(1).ToLower();foreach (var rule in rules){foreach (var item in rule.TypeMatches){if (tpTag.CompareTo(item.ToLower()) 0){result rule;return true;}}}}switch (layer.LayerType){case PsdLayerType.TextLayer:result rules.First(itm itm.UIType defaultTextType);break;case PsdLayerType.LayerGroup:result rules.First(itm itm.UIType GUIType.Null);break;default:result rules.First(itm itm.UIType defaultImageType);break;}return result ! null;}/// summary/// 根据图层大小和位置设置UI节点大小和位置/// /summary/// param namelayerNode/param/// param nameuiNode/param/// param namepos是否设置位置/parampublic static void SetRectTransform(PsdLayerNode layerNode, UnityEngine.Component uiNode, bool pos true, bool width true, bool height true, int extSize 0){if (uiNode ! null layerNode ! null){var rect layerNode.LayerRect;var rectTransform uiNode.GetComponentRectTransform();if (width) rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rect.size.x extSize);if (height) rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, rect.size.y extSize);if (pos){rectTransform.position rect.position rectTransform.rect.size * (rectTransform.pivot - Vector2.one * 0.5f)*0.01f;}}}/// summary/// 把LayerNode图片保存到本地并返回/// /summary/// param namelayerNode/param/// returns/returnspublic static Texture2D LayerNode2Texture(PsdLayerNode layerNode){if (layerNode ! null){var spAssetName layerNode.ExportImageAsset(false);var texture AssetDatabase.LoadAssetAtPathTexture2D(spAssetName);return texture;}return null;}/// summary/// 把LayerNode图片保存到本地并返回/// /summary/// param namelayerNode/param/// param nameauto9Slice若没有设置Sprite的九宫,是否自动计算并设置九宫/param/// returns/returnspublic static Sprite LayerNode2Sprite(PsdLayerNode layerNode, bool auto9Slice false){if (layerNode ! null){var spAssetName layerNode.ExportImageAsset(true);var sprite AssetDatabase.LoadAssetAtPathSprite(spAssetName);if (sprite ! null){if (auto9Slice){var spImpt AssetImporter.GetAtPath(spAssetName) as TextureImporter;var rawReadable spImpt.isReadable;if (!rawReadable){spImpt.isReadable true;spImpt.SaveAndReimport();}if (spImpt.spriteBorder Vector4.zero){spImpt.spriteBorder CalculateTexture9SliceBorder(sprite.texture, layerNode.BindPsdLayer.Opacity);spImpt.isReadable rawReadable;spImpt.SaveAndReimport();}}return sprite;}}return null;}/// summary/// 自动计算贴图的 9宫 Border/// /summary/// param nametexture/param/// param namealphaThreshold0-255/param/// returns/returnspublic static Vector4 CalculateTexture9SliceBorder(Texture2D texture, byte alphaThreshold 3){int width texture.width;int height texture.height;Color32[] pixels texture.GetPixels32();int minX width;int minY height;int maxX 0;int maxY 0;// 寻找不透明像素的最小和最大边界for (int y 0; y height; y){for (int x 0; x width; x){int pixelIndex y * width x;Color32 pixel pixels[pixelIndex];if (pixel.a alphaThreshold){minX Mathf.Min(minX, x);minY Mathf.Min(minY, y);maxX Mathf.Max(maxX, x);maxY Mathf.Max(maxY, y);}}}// 计算最优的borderSizeint borderSizeX (maxX - minX) / 3;int borderSizeY (maxY - minY) / 3;int borderSize Mathf.Min(borderSizeX, borderSizeY);// 根据边界和Border Size计算Nine Slice Borderint left minX borderSize;int right maxX - borderSize;int top minY borderSize;int bottom maxY - borderSize;// 确保边界在纹理范围内left Mathf.Clamp(left, 0, width - 1);right Mathf.Clamp(right, 0, width - 1);top Mathf.Clamp(top, 0, height - 1);bottom Mathf.Clamp(bottom, 0, height - 1);return new Vector4(left, top, width - right, height - bottom);}/// summary/// 把PS的字体样式同步设置到UGUI Text/// /summary/// param nametxtLayer/param/// param nametext/parampublic static void SetTextStyle(PsdLayerNode txtLayer, UnityEngine.UI.Text text){if (text null) return;text.gameObject.SetActive(txtLayer ! null);if (txtLayer ! null txtLayer.ParseTextLayerInfo(out var str, out var size, out var charSpace, out float lineSpace, out var col, out var style, out var tmpStyle, out var fName)){var tFont FindFontAsset(fName);if (tFont ! null) text.font tFont;text.text str;text.fontSize size;text.fontStyle style;text.color col;text.lineSpacing lineSpace;}}/// summary/// 把PS的字体样式同步设置到TextMeshProUGUI/// /summary/// param nametxtLayer/param/// param nametext/parampublic static void SetTextStyle(PsdLayerNode txtLayer, TextMeshProUGUI text){if (txtLayer ! null txtLayer.ParseTextLayerInfo(out var str, out var size, out var charSpace, out float lineSpace, out var col, out var style, out var tmpStyle, out var fName)){var tFont FindTMPFontAsset(fName);if (tFont ! null) text.font tFont;text.text str;text.fontSize size;text.fontStyle tmpStyle;text.color col;text.characterSpacing charSpace;text.lineSpacing lineSpace;}}/// summary/// 根据字体名查找TMP_FontAsset/// /summary/// param namefontName/param/// returns/returnspublic static TMP_FontAsset FindTMPFontAsset(string fontName){var fontGuids AssetDatabase.FindAssets(t:TMP_FontAsset);foreach (var guid in fontGuids){var fontPath AssetDatabase.GUIDToAssetPath(guid);var font AssetDatabase.LoadAssetAtPathTMP_FontAsset(fontPath);if (font ! null font.faceInfo.familyName fontName){return font;}}return null;}/// summary/// 根据字体名查找Font Asset/// /summary/// param namefontName/param/// returns/returnspublic static UnityEngine.Font FindFontAsset(string fontName){var fontGuids AssetDatabase.FindAssets(t:font);foreach (var guid in fontGuids){var fontPath AssetDatabase.GUIDToAssetPath(guid);var font AssetImporter.GetAtPath(fontPath) as TrueTypeFontImporter;if (font ! null font.fontTTFName fontName){return AssetDatabase.LoadAssetAtPathUnityEngine.Font(fontPath);}}return null;}internal static UnityEngine.Color LayerNode2Color(PsdLayerNode fillColor, Color defaultColor){if (fillColor ! null fillColor.BindPsdLayer is FillLayer fillLayer){var layerColor fillLayer.GetPixel(fillLayer.Width / 2, fillLayer.Height / 2);return new UnityEngine.Color(layerColor.R, layerColor.G, layerColor.B, fillLayer.Opacity) / (float)255;}return defaultColor;}/// summary/// 导出UI设计师使用规则文档/// /summary/// exception crefNotImplementedException/exceptioninternal void ExportReadmeDoc(){var exportDir EditorUtility.SaveFolderPanel(选择文档导出路径, Application.dataPath, null);if (string.IsNullOrWhiteSpace(exportDir) || !Directory.Exists(exportDir)){return;}var docFile UtilityBuiltin.ResPath.GetCombinePath(exportDir, Psd2UGUI设计师使用文档.doc);var strBuilder new StringBuilder();strBuilder.AppendLine(使用说明:);strBuilder.AppendLine(this.readmeDoc);strBuilder.AppendLine(Environment.NewLine Environment.NewLine);strBuilder.AppendLine(UI类型标识: 图层/组命名以.类型结尾);strBuilder.AppendLine(UI类型标识列表:);foreach (var rule in rules){if (rule.UIType GUIType.Null) continue;strBuilder.AppendLine(\({rule.UIType}: {rule.Comment});strBuilder.Append(类型标识: );foreach (var tag in rule.TypeMatches){strBuilder.Append(\).{tag}, );}strBuilder.AppendLine();strBuilder.AppendLine();}try{File.WriteAllText(docFile, strBuilder.ToString(), System.Text.Encoding.UTF8);EditorUtility.RevealInFinder(docFile);}catch (Exception e){Debug.LogException(e);}}} } #endif 三、PS脚本编写一键转换特效图层/文本图层为智能对象 为了辅助UI设计师避免手动转换智能对象会有遗漏设计师交付PSD文件前需要执行自动化脚本把特效图层/字体转为智能对象这样即使不同设备字库丢失也能保持字体原本样式。PS脚本是用js语言编写没有代码提示是最大的障碍。好在没有复杂逻辑只是遍历当前打开的psd文档图层判断图层是否带有特效或是否为文本图层把符合条件的图层转换为智能对象: // 判断图层是否包含特效 function hasLayerEffect(layer) {app.activeDocument.activeLayer layer;var hasEffect false;try {var ref new ActionReference();var keyLayerEffects app.charIDToTypeID( Lefx );ref.putProperty( app.charIDToTypeID( Prpr ), keyLayerEffects );ref.putEnumerated( app.charIDToTypeID( Lyr ), app.charIDToTypeID( Ordn ), app.charIDToTypeID( Trgt ) );var desc executeActionGet( ref );if ( desc.hasKey( keyLayerEffects ) ) {hasEffect true;}}catch(e) {hasEffect false;}return hasEffect; }function convertLayersToSmartObjects(layers) {for (var i layers.length - 1; i 0; i–) {var layer layers[i];if (layer.typename LayerSet){convertLayersToSmartObjects(layer.layers); // Recursively convert layers in layer sets} else{if (hasLayerEffect(layer)){if(layer.kind LayerKind.TEXT)convertToSmartObject(layer); // Convert layers with layer effects to smart objectselse layer.rasterize(RasterizeType.SHAPE);}}} } // 把图层转换为智能对象功能等同右键图层-转为智能对象 function convertToSmartObject(layer) {app.activeDocument.activeLayer layer;// 创建一个新的智能对象var idnewPlacedLayer stringIDToTypeID(newPlacedLayer);executeAction(idnewPlacedLayer, undefined, DialogModes.NO);} // 导出处理后的PSD文件 function exportPSD() {var doc app.activeDocument;var savePath Folder.selectDialog(选择psd导出路径);if (savePath ! null) {var saveOptions new PhotoshopSaveOptions();saveOptions.embedColorProfile true;saveOptions.alphaChannels true;var saveFile new File(savePath / doc.name);doc.saveAs(saveFile, saveOptions, true, Extension.LOWERCASE);alert(PSD已成功导出!);} } function convertAndExport(){convertLayersToSmartObjects (app.activeDocument.layers);//exportPSD(); } app.activeDocument.suspendHistory(Convert2SmartObject, convertAndExport();); //~ convertLayersToSmartObjects (app.activeDocument.layers);四、Psd转UGUI编辑器 1. Unity中右键PSD文件把PS图层转换成节点每个节点绑定一个对应图层。 2. 解析UI设计师为UI标记的类型自动标识图层是否需要导出自动绑定UI子元素。
- 查漏补缺对于没有标记类型并且没有正确识别绑定的UI元素进行手动选择类型。 编辑器根节点提供各项持久化保存设置并且支持自动压缩图片。压缩方法可参考之前写过的压缩工具:【Unity编辑器扩展】包体优化神器,图片压缩,批量生成图集/图集变体,动画压缩_unity 图片压缩_TopGames的博客-CSDN博客
- 解析psd图层重新解析psd为节点树状图。
- 导出Images把编辑器下勾选的图层节点导出为图片资源。
- 生成UIForm把当前的节点树解析生成为UI界面预制体。 Psd2UIForm编辑器代码 #if UNITY_EDITOR using Aspose.PSD.FileFormats.Psd; using Aspose.PSD.FileFormats.Psd.Layers; using Aspose.PSD.FileFormats.Psd.Layers.SmartObjects; using Aspose.PSD.ImageLoadOptions; using GameFramework; using HarmonyLib; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityGameFramework.Runtime;namespace UGF.EditorTools.Psd2UGUI {#region Crack[HarmonyPatch(typeof(System.Xml.XmlElement), nameof(System.Xml.XmlElement.InnerText), MethodType.Getter)]class CrackAspose{static void Postfix(ref string result){if (result 20220516){result 20500516;}//else if (result 20210827)//{// __result 20250827;//}}}#endregion[CustomEditor(typeof(Psd2UIFormConverter))]public class Psd2UIFormConverterInspector : UnityEditor.Editor{Psd2UIFormConverter targetLogic;GUIContent parsePsd2NodesBt;GUIContent exportUISpritesBt;GUIContent generateUIFormBt;GUILayoutOption btHeight;private void OnEnable(){btHeight GUILayout.Height(30);targetLogic target as Psd2UIFormConverter;parsePsd2NodesBt new GUIContent(解析psd图层, 把psd图层解析为可编辑节点树);exportUISpritesBt new GUIContent(导出Images, 导出勾选的psd图层为碎图);generateUIFormBt new GUIContent(生成UIForm, 根据解析后的节点树生成UIForm Prefab);if (string.IsNullOrWhiteSpace(Psd2UIFormSettings.Instance.UIFormOutputDir)){Debug.LogWarning(\(UIForm输出路径为空!);}}private void OnDisable(){Psd2UIFormSettings.Save();}public override void OnInspectorGUI(){EditorGUILayout.BeginVertical(box);{EditorGUILayout.BeginHorizontal();{EditorGUILayout.LabelField(自动压缩图片:, GUILayout.Width(150));Psd2UIFormSettings.Instance.CompressImage EditorGUILayout.Toggle(Psd2UIFormSettings.Instance.CompressImage);EditorGUILayout.EndHorizontal();}EditorGUILayout.BeginHorizontal();{EditorGUILayout.LabelField(UI图片导出路径:, GUILayout.Width(150));Psd2UIFormSettings.Instance.UIImagesOutputDir EditorGUILayout.TextField(Psd2UIFormSettings.Instance.UIImagesOutputDir);if (GUILayout.Button(选择路径, GUILayout.Width(80))){var retPath EditorUtility.OpenFolderPanel(选择导出路径, Psd2UIFormSettings.Instance.UIImagesOutputDir, null);if (!string.IsNullOrWhiteSpace(retPath)){if (!retPath.StartsWith(Assets/)){retPath Path.GetRelativePath(Directory.GetParent(Application.dataPath).FullName, retPath);}Psd2UIFormSettings.Instance.UIImagesOutputDir retPath;Psd2UIFormSettings.Save();}GUIUtility.ExitGUI();}EditorGUILayout.EndHorizontal();}EditorGUILayout.BeginHorizontal();{Psd2UIFormSettings.Instance.UseUIFormOutputDir EditorGUILayout.ToggleLeft(使用UIForm导出路径:, Psd2UIFormSettings.Instance.UseUIFormOutputDir, GUILayout.Width(150));EditorGUI.BeginDisabledGroup(!Psd2UIFormSettings.Instance.UseUIFormOutputDir);{Psd2UIFormSettings.Instance.UIFormOutputDir EditorGUILayout.TextField(Psd2UIFormSettings.Instance.UIFormOutputDir);if (GUILayout.Button(选择路径, GUILayout.Width(80))){var retPath EditorUtility.OpenFolderPanel(选择导出路径, Psd2UIFormSettings.Instance.UIFormOutputDir, null);if (!string.IsNullOrWhiteSpace(retPath)){if (!retPath.StartsWith(Assets/)){retPath Path.GetRelativePath(Directory.GetParent(Application.dataPath).FullName, retPath);}Psd2UIFormSettings.Instance.UIFormOutputDir retPath;Psd2UIFormSettings.Save();}GUIUtility.ExitGUI();}EditorGUI.EndDisabledGroup();}EditorGUILayout.EndHorizontal();}//EditorGUILayout.BeginHorizontal();//{// Psd2UIFormSettings.Instance.AutoCreateUIFormScript EditorGUILayout.ToggleLeft(生成UIForm代码:, Psd2UIFormSettings.Instance.AutoCreateUIFormScript, GUILayout.Width(150));// EditorGUI.BeginDisabledGroup(!Psd2UIFormSettings.Instance.AutoCreateUIFormScript);// {// Psd2UIFormSettings.Instance.UIFormScriptOutputDir EditorGUILayout.TextField(Psd2UIFormSettings.Instance.UIFormScriptOutputDir);// if (GUILayout.Button(选择路径, GUILayout.Width(80)))// {// var retPath EditorUtility.OpenFolderPanel(选择导出路径, Psd2UIFormSettings.Instance.UIFormScriptOutputDir, null);// if (!string.IsNullOrWhiteSpace(retPath))// {// if (!retPath.StartsWith(Assets/))// {// retPath Path.GetRelativePath(Directory.GetParent(Application.dataPath).FullName, retPath);// }// Psd2UIFormSettings.Instance.UIFormScriptOutputDir retPath;// Psd2UIFormSettings.Save();// }// GUIUtility.ExitGUI();// }// EditorGUI.EndDisabledGroup();// }// EditorGUILayout.EndHorizontal();//}EditorGUILayout.EndVertical();}EditorGUILayout.BeginHorizontal();{if (GUILayout.Button(parsePsd2NodesBt, btHeight)){Psd2UIFormConverter.ParsePsd2LayerPrefab(targetLogic.PsdAssetName, targetLogic);}if (GUILayout.Button(exportUISpritesBt, btHeight)){targetLogic.ExportSprites();}EditorGUILayout.EndHorizontal();}if (GUILayout.Button(generateUIFormBt, btHeight)){targetLogic.GenerateUIForm();}base.OnInspectorGUI();}public override bool HasPreviewGUI(){return targetLogic.BindPsdAsset ! null;}public override void OnPreviewGUI(Rect r, GUIStyle background){GUI.DrawTexture(r, targetLogic.BindPsdAsset.texture, ScaleMode.ScaleToFit);//base.OnPreviewGUI(r, background);}}/// summary/// Psd文件转成UIForm prefab/// /summary[ExecuteInEditMode][RequireComponent(typeof(SpriteRenderer))]public class Psd2UIFormConverter : MonoBehaviour{const string RecordLayerOperation Change Export Image;public static Psd2UIFormConverter Instance { get; private set; }[ReadOnlyField][SerializeField] public string psdAssetChangeTime;//文件修改时间标识[Tooltip(UIForm名字)][SerializeField] private string uiFormName;[Tooltip(关联的psd文件)][SerializeField] private UnityEngine.Sprite psdAsset;[Header(Debug:)][SerializeField] bool drawLayerRectGizmos true;[SerializeField] UnityEngine.Color drawLayerRectGizmosColor UnityEngine.Color.green;private PsdImage psdInstance;//psd文件解析实例private GUIStyle uiTypeLabelStyle;public string PsdAssetName psdAsset ! null ? AssetDatabase.GetAssetPath(psdAsset) : null;public UnityEngine.Sprite BindPsdAsset psdAsset;public Vector2Int UIFormCanvasSize { get; private set; } new Vector2Int(750, 1334);private void OnEnable(){Instance this;uiTypeLabelStyle new GUIStyle();uiTypeLabelStyle.fontSize 13;uiTypeLabelStyle.fontStyle UnityEngine.FontStyle.BoldAndItalic;UnityEngine.ColorUtility.TryParseHtmlString(#7ED994, out var color);uiTypeLabelStyle.normal.textColor color;EditorApplication.hierarchyWindowItemOnGUI OnHierarchyGUI;if (psdInstance null !string.IsNullOrWhiteSpace(PsdAssetName)){RefreshNodesBindLayer();}}private void Start(){if (this.CheckPsdAssetHasChanged()){if (EditorUtility.DisplayDialog(PSD - UIForm, \){gameObject.name}关联的psd文件[{this.PsdAssetName}]已改变,是否重新解析节点树?, 是, 否)){if (Psd2UIFormConverter.ParsePsd2LayerPrefab(this.PsdAssetName, this)){RefreshNodesBindLayer();}}}else{RefreshNodesBindLayer();}}private void OnDrawGizmos(){if (drawLayerRectGizmos){var nodes this.GetComponentsInChildrenPsdLayerNode();Gizmos.color drawLayerRectGizmosColor;foreach (var item in nodes){if (item.NeedExportImage()){Gizmos.DrawWireCube(item.LayerRect.position * 0.01f, item.LayerRect.size * 0.01f);}}}}private void OnHierarchyGUI(int instanceID, Rect selectionRect){if (Event.current null) return;var node EditorUtility.InstanceIDToObject(instanceID) as GameObject;if (node null || node this.gameObject) return;if (!node.TryGetComponentPsdLayerNode(out var layer)) return;Rect tmpRect selectionRect;tmpRect.x 35;tmpRect.width 10;Undo.RecordObject(layer, RecordLayerOperation);EditorGUI.BeginChangeCheck();{layer.markToExport EditorGUI.Toggle(tmpRect, layer.markToExport);if (EditorGUI.EndChangeCheck()){if (Selection.gameObjects.Length 1) SetExportImageTg(Selection.gameObjects, layer.markToExport);EditorUtility.SetDirty(layer);}}tmpRect.width Mathf.Clamp(selectionRect.xMax * 0.2f, 100, 200);tmpRect.x selectionRect.xMax - tmpRect.width;//EditorGUI.LabelField(tmpRect, layer.UIType.ToString(), uiTypeLabelStyle);if (EditorGUI.DropdownButton(tmpRect, new GUIContent(layer.UIType.ToString()), FocusType.Passive)){var dropdownMenu PopEnumMenuGUIType(layer.UIType, selectUIType {layer.SetUIType(selectUIType);EditorUtility.SetDirty(layer);});dropdownMenu.ShowAsContext();}}private GenericMenu PopEnumMenuT(T currentValue, ActionT onSelectEnum) where T : Enum{var names Enum.GetValues(typeof(T));var dropdownMenu new GenericMenu();foreach (T item in names){dropdownMenu.AddItem(new GUIContent(item.ToString()), item.Equals(currentValue), () { onSelectEnum(item); });}return dropdownMenu;}/// summary/// 批量勾选导出图片/// /summary/// param nameselects/param/// param nameexportImg/paramprivate void SetExportImageTg(GameObject[] selects, bool exportImg){var selectLayerNodes selects.Where(item item?.GetComponentPsdLayerNode() ! null).ToArray();foreach (var layer in selectLayerNodes){layer.GetComponentPsdLayerNode().markToExport exportImg;}}private void OnDestroy(){EditorApplication.hierarchyWindowItemOnGUI - OnHierarchyGUI;if (this.psdInstance ! null !psdInstance.Disposed){psdInstance.Dispose();}}private void RefreshNodesBindLayer(){if (psdInstance null || psdInstance.Disposed){if (!File.Exists(PsdAssetName)){Debug.LogError(\(刷新节点绑定图层失败! psd文件不存在);return;}var psdOpts new PsdLoadOptions(){LoadEffectsResource true,ReadOnlyMode false,};psdInstance Aspose.PSD.Image.Load(PsdAssetName, psdOpts) as PsdImage;UIFormCanvasSize.Set(psdInstance.Size.Width, psdInstance.Size.Height);}var layers GetComponentsInChildrenPsdLayerNode(true);foreach (var layer in layers){layer.InitPsdLayers(psdInstance);}var spRender gameObject.GetOrAddComponentSpriteRenderer();spRender.sprite this.psdAsset;}#regionconst string AsposeLicenseKey 此处为Aspose.PSD证书;static bool licenseInitiated false;[InitializeOnLoadMethod]static void InitAsposeLicense(){if (licenseInitiated) return;var harmonyHook new Harmony(Crack.Aspose);harmonyHook.PatchAll();new Aspose.PSD.License().SetLicense(new MemoryStream(Convert.FromBase64String(AsposeLicenseKey)));licenseInitiated true;harmonyHook.UnpatchAll();//GetAllLayerType();}static void GetAllLayerType(){var psdLib Utility.Assembly.GetAssemblies().FirstOrDefault(item item.GetName().Name Aspose.PSD);var layers psdLib.GetTypes().Where(tp tp.IsSubclassOf(typeof(Layer)) !tp.IsAbstract);string layerEnumNames ;foreach (var item in layers){layerEnumNames \){item.Name},\n;}Debug.Log(layerEnumNames);}#endregion Aspose License[MenuItem(Assets/GF Editor Tool/Psd2UIForm Editor, priority 0)]static void Psd2UIFormPrefabMenu(){if (Selection.activeObject null) return;var assetPath AssetDatabase.GetAssetPath(Selection.activeObject);if (Path.GetExtension(assetPath).ToLower().CompareTo(.psd) ! 0){Debug.LogWarning(\(选择的文件({assetPath})不是psd格式, 工具只支持psd转换为UIForm);return;}string psdLayerPrefab GetPsdLayerPrefabPath(assetPath);if (!File.Exists(psdLayerPrefab)){if (ParsePsd2LayerPrefab(assetPath)){OpenPsdLayerEditor(psdLayerPrefab);}}else{OpenPsdLayerEditor(psdLayerPrefab);}}public bool CheckPsdAssetHasChanged(){if (psdAsset null) return false;var fileTag GetAssetChangeTag(PsdAssetName);return psdAssetChangeTime.CompareTo(fileTag) ! 0;}public static string GetAssetChangeTag(string fileName){return new FileInfo(fileName).LastWriteTime.ToString(yyyyMMddHHmmss);}/// summary/// 打开psd图层信息prefab/// /summary/// param namepsdLayerPrefab/parampublic static void OpenPsdLayerEditor(string psdLayerPrefab){PrefabStageUtility.OpenPrefab(psdLayerPrefab);}/// summary/// 把Psd图层解析成节点prefab/// /summary/// param namepsdPath/param/// returns/returnspublic static bool ParsePsd2LayerPrefab(string psdFile, Psd2UIFormConverter instanceRoot null){if (!File.Exists(psdFile)){Debug.LogError(\)Error: Psd文件不存在:{psdFile});return false;}var texImporter AssetImporter.GetAtPath(psdFile) as TextureImporter;if (texImporter.textureType ! TextureImporterType.Sprite){texImporter.textureType TextureImporterType.Sprite;texImporter.mipmapEnabled false;texImporter.alphaIsTransparency true;texImporter.SaveAndReimport();}var prefabFile GetPsdLayerPrefabPath(psdFile);var rootName Path.GetFileNameWithoutExtension(prefabFile);bool needDestroyInstance instanceRoot null;if (instanceRoot ! null){ParsePsdLayer2Root(psdFile, instanceRoot);instanceRoot.RefreshNodesBindLayer();return true;}else{Psd2UIFormConverter rootLayer CreatePsdLayerRoot(rootName);rootLayer.SetPsdAsset(psdFile);ParsePsdLayer2Root(psdFile, rootLayer);PrefabUtility.SaveAsPrefabAsset(rootLayer.gameObject, prefabFile, out bool savePrefabSuccess);if (needDestroyInstance) GameObject.DestroyImmediate(rootLayer.gameObject);AssetDatabase.Refresh();if (savePrefabSuccess AssetDatabase.GUIDFromAssetPath(StageUtility.GetCurrentStage().assetPath) ! AssetDatabase.GUIDFromAssetPath(prefabFile)){PrefabStageUtility.OpenPrefab(prefabFile);}return savePrefabSuccess;}}private static void ParsePsdLayer2Root(string psdFile, Psd2UIFormConverter converter){//清空已有节点重新解析for (int i converter.transform.childCount - 1; i 0; i–){GameObject.DestroyImmediate(converter.transform.GetChild(i).gameObject);}var psdOpts new PsdLoadOptions(){LoadEffectsResource true,ReadOnlyMode false};using (var psd Aspose.PSD.Image.Load(psdFile, psdOpts) as PsdImage){ListGameObject layerNodes new ListGameObject { converter.gameObject };for (int i 0; i psd.Layers.Length; i){var layer psd.Layers[i];var curLayerType layer.GetLayerType();if (curLayerType PsdLayerType.SectionDividerLayer){var layerGroup (layer as SectionDividerLayer).GetRelatedLayerGroup();var layerGroupIdx ArrayUtility.IndexOf(psd.Layers, layerGroup);var layerGropNode CreatePsdLayerNode(layerGroup, layerGroupIdx);layerNodes.Add(layerGropNode.gameObject);}else if (curLayerType PsdLayerType.LayerGroup){var lastLayerNode layerNodes.Last();layerNodes.Remove(lastLayerNode);if (layerNodes.Count 0){var parentLayerNode layerNodes.Last();lastLayerNode.transform.SetParent(parentLayerNode.transform);}}else{var newLayerNode CreatePsdLayerNode(layer, i);newLayerNode.transform.SetParent(layerNodes.Last().transform);newLayerNode.transform.localPosition Vector3.zero;}}}converter.psdAssetChangeTime GetAssetChangeTag(psdFile);var childrenNodes converter.GetComponentsInChildrenPsdLayerNode(true);foreach (var item in childrenNodes){item.RefreshUIHelper(false);}EditorUtility.SetDirty(converter.gameObject);}private void SetPsdAsset(string psdFile){this.psdAsset AssetDatabase.LoadAssetAtPathUnityEngine.Sprite(psdFile);if (string.IsNullOrWhiteSpace(Psd2UIFormSettings.Instance.UIImagesOutputDir)){Psd2UIFormSettings.Instance.UIImagesOutputDir Path.GetDirectoryName(psdFile);}if (string.IsNullOrWhiteSpace(this.uiFormName)){this.uiFormName this.psdAsset.name;}}/// summary/// 获取解析好的psd layers文件/// /summary/// param namepsd/param/// returns/returnspublic static string GetPsdLayerPrefabPath(string psd){return UtilityBuiltin.ResPath.GetCombinePath(Path.GetDirectoryName(psd), Path.GetFileNameWithoutExtension(psd) _psd_layers_parsed.prefab);}private static Psd2UIFormConverter CreatePsdLayerRoot(string rootName){var node new GameObject(rootName);node.gameObject.tag EditorOnly;var layerRoot node.AddComponentPsd2UIFormConverter();return layerRoot;}private static PsdLayerNode CreatePsdLayerNode(Layer layer, int bindLayerIdx){string nodeName layer.Name;if (string.IsNullOrWhiteSpace(nodeName)){nodeName \(PsdLayer-{bindLayerIdx};}else{if (Path.HasExtension(layer.Name)){nodeName Path.GetFileNameWithoutExtension(layer.Name);}}var node new GameObject(nodeName);node.gameObject.tag EditorOnly;var layerNode node.AddComponentPsdLayerNode();layerNode.BindPsdLayerIndex bindLayerIdx;InitLayerNodeData(layerNode, layer);return layerNode;}/// summary/// 根据psd图层信息解析并初始化图层UI类型、是否导出等信息/// /summary/// param namelayerNode/param/// param namelayer/paramprivate static void InitLayerNodeData(PsdLayerNode layerNode, Layer layer){if (layer null || layer.Disposed) return;var layerTp layer.GetLayerType();layerNode.BindPsdLayer layer;if (UGUIParser.Instance.TryParse(layerNode, out var initRule)){layerNode.SetUIType(initRule.UIType, false);}layerNode.markToExport layerTp ! PsdLayerType.LayerGroup !(layerTp PsdLayerType.TextLayer layerNode.UIType.ToString().EndsWith(Text) layerNode.UIType ! GUIType.FillColor);layerNode.gameObject.SetActive(layer.IsVisible);}/// summary/// 导出psd图层为Sprites碎图/// /summary/// param namepsdAssetName/paraminternal void ExportSprites(){//var pngOpts new PngOptions()//{// ColorType Aspose.PSD.FileFormats.Png.PngColorType.Truecolor//};//this.psdInstance.Save(Assets/AAAGame/Sprites/UI/Preview.png, pngOpts);//return;var exportLayers this.GetComponentsInChildrenPsdLayerNode().Where(node node.NeedExportImage());var exportDir GetUIFormImagesOutputDir();if (!Directory.Exists(exportDir)){Directory.CreateDirectory(exportDir);}int exportIdx 0;int totalCount exportLayers.Count();foreach (var layer in exportLayers){var assetName layer.ExportImageAsset();if (assetName null){Debug.LogWarning(\)导出图层[name:{layer.name}, layerIdx:{layer.BindPsdLayerIndex}]图片失败!);}exportIdx;EditorUtility.DisplayProgressBar(\(导出进度({exportIdx}/{totalCount}), \)导出UI图片:{assetName}, exportIdx / (float)totalCount);}EditorUtility.ClearProgressBar();AssetDatabase.Refresh();}/// summary/// 根据解析后的节点树生成UIForm Prefab/// /summaryinternal void GenerateUIForm(){if (Psd2UIFormSettings.Instance.UseUIFormOutputDir string.IsNullOrWhiteSpace(Psd2UIFormSettings.Instance.UIFormOutputDir)){Debug.LogError(\(生成UIForm失败! UIForm导出路径为空:{Psd2UIFormSettings.Instance.UIFormOutputDir});return;}if (Psd2UIFormSettings.Instance.UseUIFormOutputDir){ExportUIPrefab(Psd2UIFormSettings.Instance.UIFormOutputDir);}else{string lastSaveDir string.IsNullOrWhiteSpace(Psd2UIFormSettings.Instance.LastUIFormOutputDir) ? Assets : Psd2UIFormSettings.Instance.LastUIFormOutputDir;string selectDir EditorUtility.SaveFolderPanel(保存目录, lastSaveDir, null);if (!string.IsNullOrWhiteSpace(selectDir)){if (!selectDir.StartsWith(Assets/))selectDir Path.GetRelativePath(Directory.GetParent(Application.dataPath).FullName, selectDir);Psd2UIFormSettings.Instance.LastUIFormOutputDir selectDir;ExportUIPrefab(selectDir);}}}private bool ExportUIPrefab(string outputDir){if (!string.IsNullOrWhiteSpace(outputDir)){if (!Directory.Exists(outputDir)){try{Directory.CreateDirectory(outputDir);AssetDatabase.Refresh();}catch (Exception err){Debug.LogError(\)导出UI prefab失败:{err.Message});return false;}}}if (string.IsNullOrWhiteSpace(uiFormName)){Debug.LogError(导出UI Prefab失败: UI Form Name为空, 请填写UI Form Name.);return false;}var prefabName UtilityBuiltin.ResPath.GetCombinePath(outputDir, \({uiFormName}.prefab);if (File.Exists(prefabName)){if (!EditorUtility.DisplayDialog(警告, \)prefab文件已存在, 是否覆盖:{prefabName}, 覆盖生成, 取消生成)){return false;}}var uiHelpers GetAvailableUIHelpers();if (uiHelpers null || uiHelpers.Length 1){return false;}var uiFormRoot GameObject.Instantiate(UGUIParser.Instance.UIFormTemplate, Vector3.zero, Quaternion.identity);uiFormRoot.name uiFormName;int curIdx 0;int totalCount uiHelpers.Length;foreach (var uiHelper in uiHelpers){EditorUtility.DisplayProgressBar(\(生成UIFrom:({curIdx}/{totalCount}), \)正在生成UI元素:{uiHelper.name}, curIdx /(float)totalCount);var uiElement uiHelper.CreateUI();if (uiElement null) continue;var goPath GetGameObjectInstanceIdPath(uiHelper.gameObject, out var goNames);var parentNode GetOrCreateNodeByInstanceIdPath(uiFormRoot, goPath, goNames);uiElement.transform.SetParent(parentNode.transform, true);uiElement.transform.position new Vector3(this.UIFormCanvasSize.x * 0.5f, this.UIFormCanvasSize.y * 0.5f, 0);}var uiStrKeys uiFormRoot.GetComponentsInChildrenUIStringKey(true);for (int i uiStrKeys.Length - 1; i 0; i–){DestroyImmediate(uiStrKeys[i]);}var uiPrefab PrefabUtility.SaveAsPrefabAsset(uiFormRoot, prefabName, out bool saveSuccess);if (saveSuccess){DestroyImmediate(uiFormRoot);Selection.activeGameObject uiPrefab;}EditorUtility.ClearProgressBar();return true;}private GameObject GetOrCreateNodeByInstanceIdPath(GameObject uiFormRoot, string[] goPath, string[] goNames){GameObject result uiFormRoot;if (goPath ! null goNames ! null){for (int i 0; i goPath.Length; i){var nodeId goPath[i];var nodeName goNames[i];GameObject targetNode null;foreach (Transform child in result.transform){if (child.gameObject result) continue;var idKey child.GetComponentUIStringKey();if (idKey ! null nodeId idKey.Key){targetNode child.gameObject;break;}}if (targetNode null){targetNode new GameObject(nodeName);targetNode.transform.SetParent(result.transform, false);targetNode.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);var targetNodeKey targetNode.GetOrAddComponentUIStringKey();targetNodeKey.Key nodeId;}result targetNode;}}return result;}private string[] GetGameObjectInstanceIdPath(GameObject go, out string[] names){names null;if (go null || go.transform.parent null || go.transform.parent this.transform) return null;var parentGo go.transform.parent;string[] result new string[1] { parentGo.gameObject.GetInstanceID().ToString() };names new string[1] { parentGo.gameObject.name };while (parentGo.parent ! null parentGo.parent ! this.transform){ArrayUtility.Insert(ref result, 0, parentGo.parent.gameObject.GetInstanceID().ToString());ArrayUtility.Insert(ref names, 0, parentGo.parent.gameObject.name);parentGo parentGo.parent;}return result;}private UIHelperBase[] GetAvailableUIHelpers(){var uiHelpers this.GetComponentsInChildrenUIHelperBase();uiHelpers uiHelpers.Where(ui ui.LayerNode.IsMainUIType).ToArray();Listint dependInstIds new Listint();foreach (var item in uiHelpers){foreach (var depend in item.GetDependencies()){int dependId depend.gameObject.GetInstanceID();if (!dependInstIds.Contains(dependId)){dependInstIds.Add(dependId);}}}for (int i uiHelpers.Length - 1; i 0; i–){var uiHelper uiHelpers[i];if (dependInstIds.Contains(uiHelper.gameObject.GetInstanceID())){ArrayUtility.RemoveAt(ref uiHelpers, i);}}return uiHelpers;}/// summary/// 把图片设置为为Sprite或Texture类型/// /summary/// param namedir/parampublic static void ConvertTexturesType(string[] texAssets, bool isImage true){foreach (var item in texAssets){var texImporter AssetImporter.GetAtPath(item) as TextureImporter;if (texImporter null){Debug.LogError(\(TextureImporter为空:{item});continue;}if (isImage){texImporter.textureType TextureImporterType.Sprite;texImporter.spriteImportMode SpriteImportMode.Single;texImporter.alphaSource TextureImporterAlphaSource.FromInput;texImporter.alphaIsTransparency true;texImporter.mipmapEnabled false;}else{texImporter.textureType TextureImporterType.Default;texImporter.textureShape TextureImporterShape.Texture2D;texImporter.alphaSource TextureImporterAlphaSource.FromInput;texImporter.alphaIsTransparency true;texImporter.mipmapEnabled false;}texImporter.SaveAndReimport();}}/// summary/// 压缩图片文件/// /summary/// param nameasset文件名(相对路径Assets)/param/// returns/returnspublic static bool CompressImageFile(string asset){var assetPath asset.StartsWith(Assets/) ? Path.GetFullPath(asset, Directory.GetParent(Application.dataPath).FullName) : asset;var compressTool Utility.Assembly.GetType(UGF.EditorTools.CompressTool);if (compressTool null) return false;var compressMethod compressTool.GetMethod(CompressImageOffline, new Type[] { typeof(string), typeof(string) });if (compressMethod null) return false;return (bool)compressMethod.Invoke(null, new object[] { assetPath, assetPath });}/// summary/// 获取UIForm对应的图片导出目录/// /summary/// returns/returnspublic string GetUIFormImagesOutputDir(){return UtilityBuiltin.ResPath.GetCombinePath(Psd2UIFormSettings.Instance.UIImagesOutputDir, uiFormName);}public SmartObjectLayer ConvertToSmartObjectLayer(Layer layer){var smartObj psdInstance.SmartObjectProvider.ConvertToSmartObject(new Layer[] { layer });return smartObj;}} } #endif7. 图层节点编辑器扩展提供导出图片按钮以便单独导出选择图层UI类型切换时自动添加对应的Helper解析器并自动绑定子UI #if UNITY_EDITOR using UnityEngine; using Aspose.PSD.FileFormats.Psd.Layers; using Aspose.PSD.ImageOptions; using UnityEditor; using System.IO; using System.Linq; using Aspose.PSD.FileFormats.Psd; using Aspose.PSD.FileFormats.Psd.Layers.SmartObjects; using GameFramework;namespace UGF.EditorTools.Psd2UGUI {[CanEditMultipleObjects][CustomEditor(typeof(PsdLayerNode))]public class PsdLayerNodeInspector : Editor{PsdLayerNode targetLogic;private void OnEnable(){targetLogic target as PsdLayerNode;targetLogic.RefreshLayerTexture();}public override void OnInspectorGUI(){serializedObject.Update();base.OnInspectorGUI();EditorGUI.BeginChangeCheck();{targetLogic.UIType (GUIType)EditorGUILayout.EnumPopup(UI Type, targetLogic.UIType);if (EditorGUI.EndChangeCheck()){targetLogic.SetUIType(targetLogic.UIType);}}EditorGUILayout.BeginHorizontal();{if (GUILayout.Button(导出图片)){foreach (var item in targets){if (item null) continue;(item as PsdLayerNode)?.ExportImageAsset();}}EditorGUILayout.EndHorizontal();}serializedObject.ApplyModifiedProperties();}public override bool HasPreviewGUI(){var layerNode (target as PsdLayerNode);return layerNode ! null layerNode.PreviewTexture ! null;}public override void OnPreviewGUI(Rect r, GUIStyle background){var layerNode (target as PsdLayerNode);GUI.DrawTexture(r, layerNode.PreviewTexture, ScaleMode.ScaleToFit);//base.OnPreviewGUI(r, background);}public override string GetInfoString(){var layerNode (target as PsdLayerNode);return layerNode.LayerInfo;}}[ExecuteInEditMode][DisallowMultipleComponent]public class PsdLayerNode : MonoBehaviour{[ReadOnlyField] public int BindPsdLayerIndex -1;[ReadOnlyField][SerializeField] PsdLayerType mLayerType PsdLayerType.Unknown;[SerializeField] public bool markToExport;[HideInInspector] public GUIType UIType;public Texture2D PreviewTexture { get; private set; }public string LayerInfo { get; private set; }public Rect LayerRect { get; private set; }public PsdLayerType LayerType { get mLayerType; }public bool IsMainUIType UGUIParser.IsMainUIType(UIType);/// summary/// 绑定的psd图层/// /summaryprivate Layer mBindPsdLayer;public Layer BindPsdLayer{get mBindPsdLayer;set{mBindPsdLayer value;mLayerType mBindPsdLayer.GetLayerType();//if (IsTextLayer(out var txtLayer) !txtLayer.TextBoundBox.IsEmpty)//{// LayerRect AsposePsdExtension.PsdRect2UnityRect(txtLayer.TextBoundBox, Psd2UIFormConverter.Instance.UIFormCanvasSize);//}//else{LayerRect mBindPsdLayer.GetLayerRect();}LayerInfo \){LayerRect};}}private void OnDestroy(){if (PreviewTexture ! null){DestroyImmediate(PreviewTexture);}}public void SetUIType(GUIType uiType, bool triggerParseFunc true){this.UIType uiType;RemoveUIHelper();if (triggerParseFunc){RefreshUIHelper(true);}}public void RefreshUIHelper(bool refreshParent false){if (UIType GUIType.Null) return;var uiHelperTp UGUIParser.Instance.GetHelperType(UIType);if (uiHelperTp ! null){var helper gameObject.GetOrAddComponent(uiHelperTp) as UIHelperBase;helper.ParseAndAttachUIElements();}if (refreshParent){var parentHelper transform.parent?.GetComponentUIHelperBase();parentHelper?.ParseAndAttachUIElements();}EditorUtility.SetDirty(this);}private void RemoveUIHelper(){var uiHelpers this.GetComponentsUIHelperBase();if (uiHelpers ! null){foreach (var uiHelper in uiHelpers){DestroyImmediate(uiHelper);}}EditorUtility.SetDirty(this);}/// summary/// 是否需要导出此图层/// /summary/// returns/returnspublic bool NeedExportImage(){return gameObject.activeSelf markToExport;}/// summary/// 导出图片/// /summary/// param nameforceSpriteType强制贴图类型为Sprite/param/// returns/returnspublic string ExportImageAsset(bool forceSpriteType false){string assetName null;if (this.RefreshLayerTexture()){var bytes PreviewTexture.EncodeToPNG();var imgName Utility.Text.Format({0}_{1}, string.IsNullOrWhiteSpace(name) ? UIType : name, BindPsdLayerIndex);var exportDir Psd2UIFormConverter.Instance.GetUIFormImagesOutputDir();if (!Directory.Exists(exportDir)){try{Directory.CreateDirectory(exportDir);AssetDatabase.Refresh();}catch (System.Exception){return null;}}var imgFileName UtilityBuiltin.ResPath.GetCombinePath(exportDir, imgName .png);File.WriteAllBytes(imgFileName, bytes);if (Psd2UIFormSettings.Instance.CompressImage){bool compressResult Psd2UIFormConverter.CompressImageFile(imgFileName);if (compressResult){Debug.Log(\(成功压缩图片:{imgFileName});}else{Debug.LogWarning(\)压缩图片失败:{imgFileName});}}assetName imgFileName;bool isImage !(this.UIType GUIType.FillColor || this.UIType GUIType.RawImage);AssetDatabase.Refresh();Psd2UIFormConverter.ConvertTexturesType(new string[] { imgFileName }, isImage || forceSpriteType);}return assetName;}public bool RefreshLayerTexture(bool forceRefresh false){if (!forceRefresh PreviewTexture ! null){return true;}if (BindPsdLayer null || BindPsdLayer.Disposed) return false;var pngOpt new PngOptions{ColorType Aspose.PSD.FileFormats.Png.PngColorType.TruecolorWithAlpha};if (BindPsdLayer.CanSave(pngOpt)){if (PreviewTexture ! null){DestroyImmediate(PreviewTexture);}PreviewTexture this.ConvertPsdLayer2Texture2D();}return PreviewTexture ! null;}/// summary/// 把psd图层转成Texture2D/// /summary/// param namepsdLayer/param/// returnsTexture2D/returnspublic Texture2D ConvertPsdLayer2Texture2D(){if (BindPsdLayer null || BindPsdLayer.Disposed) return null;MemoryStream ms new MemoryStream();var pngOpt new Aspose.PSD.ImageOptions.PngOptions(){ColorType Aspose.PSD.FileFormats.Png.PngColorType.TruecolorWithAlpha,FullFrame true};if (BindPsdLayer.Opacity 255 || LayerType PsdLayerType.LayerGroup){BindPsdLayer.Save(ms, pngOpt);}else{var smartLayer Psd2UIFormConverter.Instance.ConvertToSmartObjectLayer(BindPsdLayer);smartLayer.Save(ms, pngOpt);}//var bitmap BindPsdLayer.ToBitmap();//bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);var buffer new byte[ms.Length];ms.Position 0;ms.Read(buffer, 0, buffer.Length);Texture2D texture new Texture2D(BindPsdLayer.Width, BindPsdLayer.Height);texture.alphaIsTransparency true;texture.LoadImage(buffer);texture.Apply();ms.Dispose();return texture;}/// summary/// 从第一层子节点按类型查找LayerNode/// /summary/// param nameuiTp/param/// returns/returnspublic PsdLayerNode FindSubLayerNode(GUIType uiTp){for (int i 0; i transform.childCount; i){var child transform.GetChild(i)?.GetComponentPsdLayerNode();if (child ! null child.UIType uiTp) return child;}return null;}/// summary/// 依次查找给定多个类型,返回最先找到的类型/// /summary/// param nameuiTps/param/// returns/returnspublic PsdLayerNode FindSubLayerNode(params GUIType[] uiTps){foreach (var tp in uiTps){var result FindSubLayerNode(tp);if (result ! null) return result;}return null;}public PsdLayerNode FindLayerNodeInChildren(GUIType uiTp){var layers GetComponentsInChildrenPsdLayerNode(true);if (layers ! null layers.Length 0){return layers.FirstOrDefault(layer layer.UIType uiTp);}return null;}/// summary/// 判断该图层是否为文本图层/// /summary/// param namelayer/param/// returns/returnspublic bool IsTextLayer(out TextLayer layer){layer null;if (BindPsdLayer null) return false;if (BindPsdLayer is SmartObjectLayer smartLayer){layer smartLayer.GetSmartObjectInnerTextLayer() as TextLayer;return layer ! null;}else if (BindPsdLayer is TextLayer txtLayer){layer txtLayer;return layer ! null;}return false;}internal void InitPsdLayers(PsdImage psdInstance){BindPsdLayer psdInstance.Layers[BindPsdLayerIndex];}internal bool ParseTextLayerInfo(out string text, out int fontSize, out float characterSpace, out float lineSpace, out Color fontColor, out UnityEngine.FontStyle fontStyle, out TMPro.FontStyles tmpFontStyle, out string fontName){text null; fontSize 0; characterSpace 0f; lineSpace 0f; fontColor Color.white; fontStyle FontStyle.Normal; tmpFontStyle TMPro.FontStyles.Normal; fontName null;if (IsTextLayer(out var txtLayer)){text txtLayer.Text;fontSize (int)txtLayer.Font.Size;fontColor new Color(txtLayer.TextColor.R, txtLayer.TextColor.G, txtLayer.TextColor.B, txtLayer.Opacity) / (float)255;if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Bold) txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Italic)){fontStyle UnityEngine.FontStyle.BoldAndItalic;}else if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Bold)){fontStyle UnityEngine.FontStyle.Bold;}else if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Italic)){fontStyle UnityEngine.FontStyle.Italic;}else{fontStyle UnityEngine.FontStyle.Normal;}if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Italic)){tmpFontStyle | TMPro.FontStyles.Italic;}if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Bold)){tmpFontStyle | TMPro.FontStyles.Bold;}if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Underline)){tmpFontStyle | TMPro.FontStyles.Underline;}if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Strikeout)){tmpFontStyle | TMPro.FontStyles.Strikethrough;}fontName txtLayer.Font.Name;if (txtLayer.TextData.Items.Length 0){var txtData txtLayer.TextData.Items[0];characterSpace txtData.Style.Tracking * 0.1f;lineSpace (float)txtData.Style.Leading * 0.1f;}return true;}return false;}} }#endif 五、UI元素解析/生成器Helper 定义HelperBase解析器基类不同的UI类型重写UI初始化方法如需支持新的UI类型可以很方便进行扩展支持 #if UNITY_EDITOR using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityGameFramework.Runtime;namespace UGF.EditorTools.Psd2UGUI {public abstract class UIHelperBase : MonoBehaviour{public PsdLayerNode LayerNode this.GetComponentPsdLayerNode();private void OnEnable(){ParseAndAttachUIElements();}/// summary/// 解析并关联UI元素,并且返回已经关联过的图层(已关联图层不再处理)/// /summary/// param namelayerNode/param/// returns/returnspublic abstract void ParseAndAttachUIElements();/// summary/// 获取UI依赖的LayerNodes/// /summary/// returns/returnspublic abstract PsdLayerNode[] GetDependencies();/// summary/// 把UI实例进行UI元素初始化/// /summary/// param nameuiRoot/paramprotected abstract void InitUIElements(GameObject uiRoot);/// summary/// 筛选出UI依赖的非空LayerNode/// /summary/// param namenodes/param/// returns/returnsprotected PsdLayerNode[] CalculateDependencies(params PsdLayerNode[] nodes){if (nodes null || nodes.Length 0) return null;for (int i nodes.Length - 1; i 0; i–){var node nodes[i];if (node null || node LayerNode) ArrayUtility.RemoveAt(ref nodes, i);}return nodes;}internal GameObject CreateUI(GameObject uiInstance null){if ((int)this.LayerNode.UIType UGUIParser.UITYPE_MAX || LayerNode.UIType GUIType.Null) return null;if (uiInstance null){var rule UGUIParser.Instance.GetRule(this.LayerNode.UIType);if (rule null || rule.UIPrefab null){Debug.LogWarning($创建UI类型{LayerNode.UIType}失败:Rule配置项不存在或UIPrefab为空);return null;}uiInstance GameObject.Instantiate(rule.UIPrefab, Vector3.zero, Quaternion.identity);if (LayerNode.IsMainUIType){uiInstance.name this.name;var key uiInstance.GetOrAddComponentUIStringKey();key.Key this.gameObject.GetInstanceID().ToString();}}InitUIElements(uiInstance);return uiInstance;}} } #endif1. Text解析器 #if UNITY_EDITOR using UnityEngine;namespace UGF.EditorTools.Psd2UGUI {[DisallowMultipleComponent]public class TextHelper : UIHelperBase{[SerializeField] PsdLayerNode text;public override PsdLayerNode[] GetDependencies(){return CalculateDependencies(text);}public override void ParseAndAttachUIElements(){if (LayerNode.IsTextLayer(out var _)){text LayerNode;}else{LayerNode.SetUIType(UGUIParser.Instance.DefaultImage);}}protected override void InitUIElements(GameObject uiRoot){var textCom uiRoot.GetComponentInChildrenUnityEngine.UI.Text();UGUIParser.SetTextStyle(text, textCom);UGUIParser.SetRectTransform(text, textCom);}} } #endif 从ps文本图层获取文本字体、字号、颜色、字间距等信息然后从Unity工程中查找对应的字体文件并赋值给Text组件: internal bool ParseTextLayerInfo(out string text, out int fontSize, out float characterSpace, out float lineSpace, out Color fontColor, out UnityEngine.FontStyle fontStyle, out TMPro.FontStyles tmpFontStyle, out string fontName){text null; fontSize 0; characterSpace 0f; lineSpace 0f; fontColor Color.white; fontStyle FontStyle.Normal; tmpFontStyle TMPro.FontStyles.Normal; fontName null;if (IsTextLayer(out var txtLayer)){text txtLayer.Text;fontSize (int)txtLayer.Font.Size;fontColor new Color(txtLayer.TextColor.R, txtLayer.TextColor.G, txtLayer.TextColor.B, txtLayer.Opacity) / (float)255;if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Bold) txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Italic)){fontStyle UnityEngine.FontStyle.BoldAndItalic;}else if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Bold)){fontStyle UnityEngine.FontStyle.Bold;}else if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Italic)){fontStyle UnityEngine.FontStyle.Italic;}else{fontStyle UnityEngine.FontStyle.Normal;}if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Italic)){tmpFontStyle | TMPro.FontStyles.Italic;}if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Bold)){tmpFontStyle | TMPro.FontStyles.Bold;}if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Underline)){tmpFontStyle | TMPro.FontStyles.Underline;}if (txtLayer.Font.Style.HasFlag(Aspose.PSD.FontStyle.Strikeout)){tmpFontStyle | TMPro.FontStyles.Strikethrough;}fontName txtLayer.Font.Name;if (txtLayer.TextData.Items.Length 0){var txtData txtLayer.TextData.Items[0];characterSpace txtData.Style.Tracking * 0.1f;lineSpace (float)txtData.Style.Leading * 0.1f;}return true;}return false;} 根据字体内部名查找ttf字体和TextMeshPro字体资源 /// summary/// 根据字体名查找TMP_FontAsset/// /summary/// param namefontName/param/// returns/returnspublic static TMP_FontAsset FindTMPFontAsset(string fontName){var fontGuids AssetDatabase.FindAssets(t:TMP_FontAsset);foreach (var guid in fontGuids){var fontPath AssetDatabase.GUIDToAssetPath(guid);var font AssetDatabase.LoadAssetAtPathTMP_FontAsset(fontPath);if (font ! null font.faceInfo.familyName fontName){return font;}}return null;}/// summary/// 根据字体名查找Font Asset/// /summary/// param namefontName/param/// returns/returnspublic static UnityEngine.Font FindFontAsset(string fontName){var fontGuids AssetDatabase.FindAssets(t:font);foreach (var guid in fontGuids){var fontPath AssetDatabase.GUIDToAssetPath(guid);var font AssetImporter.GetAtPath(fontPath) as TrueTypeFontImporter;if (font ! null font.fontTTFName fontName){return AssetDatabase.LoadAssetAtPathUnityEngine.Font(fontPath);}}return null;}
- Image解析器 #if UNITY_EDITOR using UnityEngine;namespace UGF.EditorTools.Psd2UGUI {[DisallowMultipleComponent]public class ImageHelper : UIHelperBase{[SerializeField] PsdLayerNode image;public override PsdLayerNode[] GetDependencies(){return CalculateDependencies(image);}public override void ParseAndAttachUIElements(){image LayerNode;}protected override void InitUIElements(GameObject uiRoot){var imgCom uiRoot.GetComponentInChildrenUnityEngine.UI.Image();UGUIParser.SetRectTransform(image,imgCom);imgCom.sprite UGUIParser.LayerNode2Sprite(image, imgCom.type UnityEngine.UI.Image.Type.Sliced);}} } #endif 自动把ps图层导出为Sprite资源若Image为Sliced模式则自动计算并设置Sprite 9宫边界 /// summary/// 把LayerNode图片保存到本地并返回/// /summary/// param namelayerNode/param/// param nameauto9Slice若没有设置Sprite的九宫,是否自动计算并设置九宫/param/// returns/returnspublic static Sprite LayerNode2Sprite(PsdLayerNode layerNode, bool auto9Slice false){if (layerNode ! null){var spAssetName layerNode.ExportImageAsset(true);var sprite AssetDatabase.LoadAssetAtPathSprite(spAssetName);if (sprite ! null){if (auto9Slice){var spImpt AssetImporter.GetAtPath(spAssetName) as TextureImporter;var rawReadable spImpt.isReadable;if (!rawReadable){spImpt.isReadable true;spImpt.SaveAndReimport();}if (spImpt.spriteBorder Vector4.zero){spImpt.spriteBorder CalculateTexture9SliceBorder(sprite.texture, layerNode.BindPsdLayer.Opacity);spImpt.isReadable rawReadable;spImpt.SaveAndReimport();}}return sprite;}}return null;} 根据图片的Alpha通道计算出9宫边界通常设置9宫边界还会考虑图片纹理因素但程序难以智能识别这里自动9宫只是适用于普通情况还需要根据实际效果进行手动调整 /// summary/// 自动计算贴图的 9宫 Border/// /summary/// param nametexture/param/// param namealphaThreshold0-255/param/// returns/returnspublic static Vector4 CalculateTexture9SliceBorder(Texture2D texture, byte alphaThreshold 3){int width texture.width;int height texture.height;Color32[] pixels texture.GetPixels32();int minX width;int minY height;int maxX 0;int maxY 0;// 寻找不透明像素的最小和最大边界for (int y 0; y height; y){for (int x 0; x width; x){int pixelIndex y * width x;Color32 pixel pixels[pixelIndex];if (pixel.a alphaThreshold){minX Mathf.Min(minX, x);minY Mathf.Min(minY, y);maxX Mathf.Max(maxX, x);maxY Mathf.Max(maxY, y);}}}// 计算最优的borderSizeint borderSizeX (maxX - minX) / 3;int borderSizeY (maxY - minY) / 3;int borderSize Mathf.Min(borderSizeX, borderSizeY);// 根据边界和Border Size计算Nine Slice Borderint left minX borderSize;int right maxX - borderSize;int top minY borderSize;int bottom maxY - borderSize;// 确保边界在纹理范围内left Mathf.Clamp(left, 0, width - 1);right Mathf.Clamp(right, 0, width - 1);top Mathf.Clamp(top, 0, height - 1);bottom Mathf.Clamp(bottom, 0, height - 1);return new Vector4(left, top, width - right, height - bottom);}
- Dropdown解析器对于多种元素组成的复合型、嵌套型UI可以很好的支持并且可以任意嵌套组合没有限制和约束。例如Dropdown内包含了一个ScrollView和一个Toggle类型的Item就可以直接用ScrollView Helper和Toggle Helper分别对其解析 #if UNITY_EDITOR using UnityEngine; using UnityEngine.UI;namespace UGF.EditorTools.Psd2UGUI {[DisallowMultipleComponent]public class DropdownHelper : UIHelperBase{[SerializeField] PsdLayerNode background;[SerializeField] PsdLayerNode label;[SerializeField] PsdLayerNode arrow;[SerializeField] PsdLayerNode scrollView;[SerializeField] PsdLayerNode toggleItem;public override PsdLayerNode[] GetDependencies(){return CalculateDependencies(background, label, arrow, scrollView, toggleItem);}public override void ParseAndAttachUIElements(){background LayerNode.FindSubLayerNode(GUIType.Background, GUIType.Image, GUIType.RawImage);label LayerNode.FindSubLayerNode(GUIType.Dropdown_Label, GUIType.Text, GUIType.TMPText);arrow LayerNode.FindSubLayerNode(GUIType.Dropdown_Arrow);scrollView LayerNode.FindSubLayerNode(GUIType.ScrollView);toggleItem LayerNode.FindSubLayerNode(GUIType.Toggle);}protected override void InitUIElements(GameObject uiRoot){var dpd uiRoot.GetComponentDropdown();UGUIParser.SetRectTransform(background, dpd);var bgImg dpd.targetGraphic as Image;bgImg.sprite UGUIParser.LayerNode2Sprite(background, bgImg.type Image.Type.Sliced) ?? bgImg.sprite;UGUIParser.SetTextStyle(label, dpd.captionText);UGUIParser.SetRectTransform(label, dpd.captionText);var arrowImg dpd.transform.Find(Arrow)?.GetComponentImage();if (arrowImg ! null){UGUIParser.SetRectTransform(arrow, arrowImg);arrowImg.sprite UGUIParser.LayerNode2Sprite(arrow, arrowImg.type Image.Type.Sliced);}if (scrollView ! null){var svTmp uiRoot.GetComponentInChildrenScrollRect(true).GetComponentRectTransform();if (svTmp ! null){var sViewGo scrollView.GetComponentScrollViewHelper()?.CreateUI(svTmp.gameObject);if (sViewGo ! null){var sViewRect sViewGo.GetComponentRectTransform();UGUIParser.SetRectTransform(scrollView, sViewRect);sViewRect.anchorMin Vector2.zero;sViewRect.anchorMax new Vector2(1, 0);sViewRect.anchoredPosition new Vector2(0, -2);}if (toggleItem ! null){var itemTmp dpd.itemText ! null ? dpd.itemText.transform.parent : null;if (itemTmp ! null){toggleItem.GetComponentToggleHelper()?.CreateUI(itemTmp.gameObject);}}}}}} } #endif 实现了每种基础UI元素的解析后就可以任意进行UI元素组合例如Slider中包含Slider背景和填充条在Slider中添加一个文本图层解析出来后就是一个内涵Text文本的Slider进度条解析前后的节点层级始终保持统一 由于篇幅原因其它UI类型的解析代码就不贴了UGUI和TextMeshProGUI共16种UI类型全部完美支持。 最后附上psd源文件效果图和一键生成的UGUI预制体效果对比图运行时效果(左)psd原图(右)
- 上一篇: 什么是分类信息网站营销蛋糕店网站建设模版
- 下一篇: 什么是企业网站flash网站开源
相关文章
-
什么是分类信息网站营销蛋糕店网站建设模版
什么是分类信息网站营销蛋糕店网站建设模版
- 技术栈
- 2026年04月20日
-
什么是电子商务网站开发网站建设职业描述
什么是电子商务网站开发网站建设职业描述
- 技术栈
- 2026年04月20日
-
什么是电子商务网站开发seo批量建站
什么是电子商务网站开发seo批量建站
- 技术栈
- 2026年04月20日
-
什么是企业网站flash网站开源
什么是企业网站flash网站开源
- 技术栈
- 2026年04月20日
-
什么是企业型网站中国企业500强标准
什么是企业型网站中国企业500强标准
- 技术栈
- 2026年04月20日
-
什么是三合一网站建设如何做拼车网站app
什么是三合一网站建设如何做拼车网站app
- 技术栈
- 2026年04月20日
