PS自动导出切图并在Unity中自动搭建UGUI

由于公司功能重复性项目很多,故思考用自动化脚本代替原本繁杂的拼UI的工作。还能减少被UI喷UI摆的位置不对的问题。网上搜有PSD2UGUI这个插件,但是要30刀。我需要的功能应该没有插件那么复杂,凭借我多年复制粘贴工程师的代码经验,觉得自己应该也能写出来。

设计思路:

1,PS执行自动化脚本将需要的切图切出,并保存好图片在PS中的位置。

2,Unity编写editor脚本,根据导入的PS图片,自动生成相关UI布局,并绑定相关脚本。

步骤一:PS自动切图

PS脚本是用JS写的,官方给的编辑器是AdobeExtendScriptToolkit。由于我比较懒用的绿色版装不上就直接用记事本写了。直接上代码:

//用户选择导出的文件夹
var outputFolder = Folder.selectDialog("选择输出的文件夹");

var doc = app.activeDocument;

var layers = app.activeDocument.layers;



//遍历所有父级层。
for(var i=0; i<layers.length; i++)
{	
	var childlayer = layers[i].layers;
	//alert(layers[i].name);
	FindLayers(outputFolder+"/"+layers[i].name,layers[i]);			
}
//递归调用每个层级,遇到图层就导出,否则继续递归
function FindLayers(parentname,_currentLayer){
	if(_currentLayer instanceof LayerSet){		
		for(var i=0; i<_currentLayer.layers.length; i++){	
			nowdir = parentname +"/";
			checkFolder(nowdir);
			nowdir += _currentLayer.layers[i].name; 
			FindLayers(nowdir,_currentLayer.layers[i]);
		}						
	}else{
		//alert(parentname+"isImage");	
		SolveLayer(_currentLayer,parentname);
	}
}
//单独导出图片
function SolveLayer(mylayer,filename){
	mylayer.copy();			
	var bounds = mylayer.bounds;
	var posx = bounds[0].toString();
	posx = posx.replace(' ','_');
	var posy = bounds[1].toString();
	posy = posy.replace(' ','_');
		//计算当前图层的宽度,为范围数组变量的第三个值与第一个值的差。
	var width    = bounds[2] - bounds[0];
		//计算当前图层的高度,为范围数组变量的第四个值与第二个值的差。
	var height   = bounds[3] - bounds[1];
			//alert("width:"+width+"height:"+height);
		//创建一个新文档,新文档的尺寸为拷贝到内存中图层的尺寸一致。
	app.documents.add(width, height, 72, "myDocument", NewDocumentMode.RGB, 
                                        DocumentFill.TRANSPARENT);

	//将内存中的图层,复制到新文档。
	app.activeDocument.paste();
	var newfilename = filename+"_"+posx+"_"+posy+".png";
	//alert(newfilename);
	var file = new File(newfilename);
	SaveImage(doc,file);
	app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}

function checkFolder(path){
    var folder = Folder(path)
    if(!folder.exists) folder.create()
}


function SaveImage(doc,saveDir){	
	var options = PNGSaveOptions;//保存png模式
	var asCopy = true;//副本方式保存
	var extensionType = Extension.LOWERCASE;//拓展名小写
	doc.saveAs(saveDir, options, asCopy, extensionType);
}

代码是百度各个功能然后拼一起的。没怎么研究API,如果哪位大佬有更好的写法,请指出。文件保存成.js或者.jsx都行。.jsx好点,PS默认读取.jsx。可以节约重新选择文件类型的时间,至少多摸鱼2秒钟。

PSD文件内需要切图的图片需要提前处理好,每个图层就是一个切图。文字需要栅格化,形状需要先转换成智能对象再栅格化。各位请自行处理,如果不会可以呼叫UI。

注意!:需要将PS内的单位设置成像素,否则在UGUI中无法自动准确的定位。设置方法:

在菜单栏中的 视图->标尺。或者直接ctrl+R 打开标尺.  在标尺处右键,选择像素。如下图所示:

PS点击  文件->脚本->浏览  选中刚才的.jsx结尾的脚本。脚本运行后选择导出的文件路径,不要在运行过程中操作PS。脚本会根据图层关系,自动生成相应的文件关系。

步骤二:UGUI自动创建。

在unity项目中创建Editor文件夹,文件夹下任意创建个C#脚本。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;

public class ConvertTexture2Sprite : Editor
{
    private static Dictionary<string, Sprite> SpriteDic = new Dictionary<string, Sprite>();
    private static Dictionary<string, Image> CreatedImageDic = 
    new Dictionary<string,Image>();
    private static List<Image> NoPaireToggleList = new List<Image>();
    [MenuItem("Convert/ConvertSprite")]
    // Update is called once per frame
    public static void ChangeSprite()
    {
        var selection = Selection.GetFiltered<Texture>(SelectionMode.DeepAssets);
        
        foreach (var item in selection)
        {
            var path = AssetDatabase.GetAssetPath(item);
            var import = (TextureImporter)AssetImporter.GetAtPath(path);
            import.textureType = TextureImporterType.Sprite;
            import.SaveAndReimport();
        }
        Debug.Log("转换完成:"+selection.Length);
    }
    
    [MenuItem("Tools/GenerateGUI")]
    public static void AutoGUI()
    {
        SpriteDic.Clear();
        CreatedImageDic.Clear();
        NoPaireToggleList.Clear();
        var selections = Selection.GetFiltered<Texture>(SelectionMode.DeepAssets);
        foreach (var item in selections)
        {
            var path = AssetDatabase.GetAssetPath(item);
            var icon = AssetDatabase.LoadAssetAtPath<Sprite>(path);
            SpriteDic.Add(path, icon);
        }
        CreateUI(SpriteDic);

        //foreach (var item in SpriteDic)
        //{
        //    var re = GetRelativePath(item.Key);
        //    Debug.Log(re);
        //    Debug.Log(item.Key + ":" + item.Value);
        //}
    }

    private static void CreateUI(Dictionary<string, Sprite> spriteDic)
    {
        var canvas = GameObject.Find("Canvas");
        if (canvas == null)
        {
            Debug.Log("找不到canvas,请手动生成,并命名为Canvas");
            return;
        }
        foreach (var item in spriteDic)
        {
            var relativePath = GetRelativePath(item.Key);
            var layers = relativePath.Split('/');
            var currentParent = canvas.transform;
            for (int i = 0; i < layers.Length; i++)
            {
                currentParent = CheckPathAndAutoCreate(currentParent, layers[i]);
            }
            var imagename = GetName(item.Value.name);
            var image = new GameObject(imagename, typeof(RectTransform), typeof(Image)).GetComponent<Image>();
            image.transform.SetParent(currentParent);
            var rect = image.GetComponent<RectTransform>();
            rect.anchorMin = new Vector2(0, 1);
            rect.anchorMax = new Vector2(0, 1);
            rect.pivot = new Vector2(0, 1);
            image.sprite = item.Value;
            image.SetNativeSize();
            var pos = GetImagPos(item.Value.name);
            rect.anchoredPosition = pos;
           
            Debug.Log(image.name);
            CreatedImageDic[image.name] = image;
            if (imagename.Contains("_On")|| imagename.Contains("_on")) {
                var sourcename = imagename.Replace("_On", "").Replace("_on","");
                if (CreatedImageDic.ContainsKey(sourcename))
                {
                    var targetimage = CreatedImageDic[sourcename];
                    var to = targetimage.gameObject.AddComponent<Toggle>();
                    to.graphic = image;
                    image.transform.SetParent(targetimage.transform);
                }
                else {
                    NoPaireToggleList.Add(image);
                }                
            }
            if (imagename.Contains("_Btn")|| imagename.Contains("_btn")) {
                image.gameObject.AddComponent<Button>();
            }

            RestPivot(rect);
           
        }
        for (int i = 0; i < NoPaireToggleList.Count; i++)
        {
            var sourcename = NoPaireToggleList[i].name.Replace("_On", "");
            if (CreatedImageDic.ContainsKey(sourcename))
            {
                var targetimage = CreatedImageDic[sourcename];
                var to = targetimage.gameObject.AddComponent<Toggle>();
                to.graphic = NoPaireToggleList[i];
                NoPaireToggleList[i].transform.SetParent(targetimage.transform);
            }
            else
            {
                Debug.LogError("can't find ToggleP is" + sourcename);
            }
        }
    }
    private static void RestPivot(RectTransform rect) {

        var tpos = rect.transform.position;
        rect.anchorMin = new Vector2(0.5f, 0.5f);
        rect.anchorMax = new Vector2(0.5f, 0.5f);
        rect.transform.position = tpos;
        rect.pivot = new Vector2(0.5f, 0.5f);
        rect.anchoredPosition += new Vector2(rect.sizeDelta.x / 2f, -rect.sizeDelta.y / 2f);
    }

    private static string GetName(string name)
    {
        var names = name.Split('_');
        var tempname = "";
        if (names.Length < 4)
            return name;
        for (int i = 0; i < names.Length - 4; i++)
        {
            tempname += names[i] + "_";
        }
        return tempname.Substring(0,tempname.Length-1);
    }

    private static Vector2 GetImagPos(string name)
    {
        if (!name.Contains("px"))
        {
            Debug.LogError(name + "图片不含关键字px");
            return Vector2.zero;
        }
        var shortname = name.Replace("_px", "");
        var elems = shortname.Split('_');
        if (elems.Length < 2)
            return Vector2.zero;
        var length = elems.Length;
        var x = int.Parse(elems[length - 2]);
        var y = int.Parse(elems[length - 1]);
        return new Vector2(x, -y);
    }

    private static Transform CheckPathAndAutoCreate(Transform p, string path)
    {

        var child = p.Find(path);
        if (child == null)
        {
            child = new GameObject(path, typeof(RectTransform)).transform;
            child.transform.SetParent(p);
            var rect = child.GetComponent<RectTransform>();
            rect.anchorMin = new Vector2(0, 0);
            rect.anchorMax = new Vector2(1, 1);
            rect.sizeDelta = Vector2.zero;
            rect.anchoredPosition = Vector3.zero;
        }
        return child;
    }

    public static string GetRelativePath(string source)
    {
        var firstindex = source.IndexOf("/");
        var lastindex = source.LastIndexOf("/");
        var result = source.Substring(firstindex + 1, lastindex - firstindex - 1);
        return result;
    }

    [MenuItem("Tools/SimpleUI")]
    public static void SimpleUI() {
        var select = Selection.activeGameObject;
        var images = select.GetComponentsInChildren<Image>(true);
        for (int i = 0; i < images.Length; i++)
        {
            var parent = images[i].transform.parent;
            var image = parent.GetComponent<Image>();
            if (parent.childCount == 1&& image == null) {
                var grandparent = parent.transform.parent;
                if (grandparent != null) {
                    images[i].transform.SetParent(grandparent);
                    DestroyImmediate(parent.gameObject);
                }
            
            }
        }
    }



}

该脚本共提供三个方法,在菜单栏上会出现

一:Convert->convertSprite.该方法可以把导入的texture批量转换成sprite。使用方法:

在Project面板中选中刚才导入的切图完毕的目录文件夹。点击convert->convertsprite。

二:Tools->GenerateGUI.该方法可以将导入的切图各元素根据像素位置拼好。使用方法:

先确保场景里存在一个Canvas,且名称是默认的。在project中选中刚才转换好sprite的目录文件夹,点击Tools->GenerateGUI。程序会自动生成相应UGUI。

三:Tools->SimpleUI,用于简化UI层级结构。由于PS导出的结构可能会存在多层空的父物体,故设计了该功能用于简化层级结构,可以多点几次。

至此整个自动化脚本流程结束,各位可以自行设定相应规则进行button和Toggle等组件的创建,比如上述脚本中会将以"_Btn"结尾的切图自动挂载上button组件。将“_On”结尾的切图自动匹配上没有“On”结尾的图片且成为一个Toggle。

PS:由于PS的层级结构是最上面的图层在最上面,跟UGUI相反,各位记得做相应的调整。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Unity UI切图是指将PSD等设计文件UI元素,如按钮、文本框等,割并导入到Unity,以便在游戏或应用程序使用。通常,这个过程涉及将PSD文件导入到Unity,然后将其转换为UnityUI组件,例如Canvas、Image、Button等。在转换过程,需要将PSD的各个图层分别割并导入到Unity,以便在游戏进行布局和交互。对于这个过程,有一些可用的插件和工具,如PSD2UGUI,可以帮助简化和加速切图的工作。此外,也可以根据自己的需求和经验,编写自己的脚本来实现UI切图的功能。无论使用哪种方法,都需要确保在Unity创建好相应的Canvas,并按照设计文件的布局和样式,将割好的UI元素放置在正确的位置上。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [PS自动导出切图并在Unity自动搭建UGUI](https://blog.csdn.net/qq_27772057/article/details/125143693)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [psd导入Unity快速切图全文档](https://download.csdn.net/download/qq_41836457/10771115)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值