Unity 合图插件 ==》【支持原图集更新】

39 篇文章 1 订阅
8 篇文章 0 订阅

前言:

项目需要,1.需提供给美术合图集工具,2.图集更新功能,使得新图集内的子图不必重新拖拽定位

解决思路:

1.unity调用外部TexturePacker命令行工具执行合图,unity根据TP生成的数据对图集切割出多个sprite

2.图集内的子图拖拽定位时,资源内产生对应的GUID及FileID。因此新图集更新对应的GUID与FileID即可,但是API没找到相应的接口,因此从资源本身下手通过txt方式打开,强制替换相应的ID

参考文档:

https://blog.csdn.net/pdw_jsp/article/details/83623150?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159900871219725264608797%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=159900871219725264608797&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v2~rank_blog_v1-4-83623150.pc_v2_rank_blog_v1&utm_term=TexturePacker+%E4%B8%80%E9%94%AE%E6%89%93%E5%8C%85&spm=1018.2118.3001.4187
 

https://www.xuanyusong.com/archives/4207

 

效果:

 

 

 

源码:

下面三个文件,放置于工程Assets\Editor即可

using System;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using UnityEditor;
using System.Text;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using System.Collections;
using LuaFramework;

public class TexturePackerBuild : Editor
{
    private const string BuildDir = "Assets/TexturePacker/打包该图集";

    /// <summary>
    /// TexturePacker.exe ###https://www.codeandweb.com/texturepacker/documentation/texture-settings
    /// </summary>
    private const string Paramet = " --sheet {0}.png --data {1}.xml --format sparrow --trim-mode CropKeepPos --pack-mode Best --algorithm MaxRects --max-size 4096 --size-constraints POT --disable-rotation {2}";
    /// <summary>
    /// 输出目录
    /// </summary>
    private const string OutPutDirRoot = "Assets/TPSpriteBuild/";

    private static string tpDir = "";
    private static string argument = "";

    [MenuItem(BuildDir, false)]
    public static void BuildOneTP()
    {

        if (Directory.Exists(OutPutDirRoot) == false)
        {
            Directory.CreateDirectory(OutPutDirRoot);
        }

        var assetPaths = Selection.assetGUIDs.Select(AssetDatabase.GUIDToAssetPath).ToList();

        List<string> filePathList = new List<string>();
        foreach (var assetPath in assetPaths)
        {
            if (AssetDatabase.IsValidFolder(assetPath))
            {
                string[] filesPath = Directory.GetFiles(assetPath);
                foreach (var filePath in filesPath)
                {
                    if (filePathList.Contains(filePath) == false)
                    {
                        filePathList.Add(filePath);
                    }
                    else
                    {
                        UnityEngine.Debug.LogErrorFormat("检测到相同文件 {0}", assetPath);
                    }
                }
            }
            else
            {
                filePathList.Add(assetPath);
            }
        }

        Dictionary<string, FileData> fileDic = new Dictionary<string, FileData>();
        foreach (var path in filePathList)
        {
            var dir = Path.GetFileName(Path.GetDirectoryName(path));
            var fileName = Path.GetFileName(path);
            UnityEngine.Debug.LogFormat("_TP dirName {0} _TP fileName {1} _TP fullPath {2}", dir, fileName, path);

            if (fileDic.ContainsKey(dir) == false)
            {
                FileData fileData = new FileData();
                fileData.filesList = new List<string>();
                fileDic[dir] = fileData;
                fileDic[dir].dirName = dir;
            }

            fileDic[dir].filesList.Add(path);
        }

        foreach (var data in fileDic)
        {
            TexturePackerBuild tpBuild = new TexturePackerBuild();
            tpBuild.Build(data.Value);
        }
    }

    public void Build(FileData data)
    {
        StringBuilder sb = new StringBuilder("");
        GetImageName(data.filesList, ref sb);

        string sheetName = OutPutDirRoot + data.dirName;
        argument = string.Format(Paramet, sheetName, sheetName, sb.ToString());

        tpDir = PlayerPrefs.GetString("_TPInstallDir", "");

        RunExternalApp(tpDir, argument);
    }

    private StringBuilder GetImageName(List<string> fileName, ref StringBuilder sb)
    {
        foreach (var file in fileName)
        {
            string extenstion = Path.GetExtension(file);
            if (extenstion == ".png")
            {
                sb.Append(file);
                sb.Append("  ");
            }
        }
        
        return sb;
    }


    private void RunExternalApp(string command, string argument)
    {
        UnityEngine.Debug.Log("TPSprite Recover");

        ClearOtherFiles();

        ProcessStartInfo start = new ProcessStartInfo(command);
        start.Arguments = argument;
        start.CreateNoWindow = false;
        start.ErrorDialog = true;
        start.UseShellExecute = false;
        start.RedirectStandardOutput = true;
        start.RedirectStandardError = true;
        start.RedirectStandardInput = true;
        start.StandardOutputEncoding = System.Text.UTF8Encoding.UTF8;
        start.StandardErrorEncoding = System.Text.UTF8Encoding.UTF8;

        try
        {
            Process p = Process.Start(start);
            p.WaitForExit();
            p.Close();
            AssetDatabase.Refresh();

            bool isNullFile; // 判断输出目录下文件是否为空
            string outPngPath;
            BuildTexturePacker(out isNullFile,out outPngPath);
            if (isNullFile == false)
            {
                tpDir = command;
                PlayerPrefs.SetString("_TPInstallDir", command);
                SelectAtlasUpdataWindow(outPngPath);
            }
            else
            {
                // 输出文件为空,猜测exe使用不正确,重新导入exe
                OnError("输出目录下未检测到图片,请重新导入");
            }
        }
        catch (Exception ex)
        {
            OnError(ex.Message);
        }
    }

    /// <summary>
    /// 清理输出目录下原始图
    /// </summary>
    private void ClearOtherFiles()
    {
        string[] fileName = Directory.GetFiles(OutPutDirRoot);
        if (fileName != null && fileName.Length > 0)
        {
            for (int i = 0; i < fileName.Length; i++)
            {
                string extenstion = Path.GetExtension(fileName[i]);
                if (extenstion == ".png" || extenstion == ".xml")
                {
                    File.Delete(fileName[i]);
                }
            }
        }

    }

    /// <summary>
    /// 创建Texture
    /// </summary>
    /// <param name="isNull"></param>
    /// <param name="outPngPath"></param>
    public void BuildTexturePacker(out bool isNull, out string outPngPath)
    {
        isNull = true; 
        outPngPath = "";
        string[] imagePath = Directory.GetFiles(OutPutDirRoot);
        foreach (string path in imagePath)
        {
            if (Path.GetExtension(path) == ".png" || Path.GetExtension(path) == ".PNG")
            {
                Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
                string rootPath = OutPutDirRoot + texture.name;
                string pngPath = rootPath + "/" + texture.name + "_" + DateTime.Now.ToString("hh_mm_ss") + ".png";

                outPngPath = pngPath;
                isNull = false;

                if (Directory.Exists(rootPath) == false)
                {
                    Directory.CreateDirectory(rootPath);
                }
                File.Copy(OutPutDirRoot + texture.name + ".png", pngPath);

                AssetDatabase.Refresh();

                FileStream fs = new FileStream(OutPutDirRoot + texture.name + ".xml", FileMode.Open);
                StreamReader sr = new StreamReader(fs);
                string jText = sr.ReadToEnd();
                fs.Close();
                sr.Close();

                WriteMeta(jText, pngPath);
            }
        }

        ClearOtherFiles();
        AssetDatabase.Refresh();
    }

    void SelectAtlasUpdataWindow(string pngPath)
    {
        TPSelectFileWindow win = new TPSelectFileWindow();
        win.Popup(pngPath, UpdataAssetRes);
    }

    void UpdataAssetRes(string opPath, Texture pendingTex)
    {
        var srcObj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(opPath);
        TPFindReplaceRes tpFindRep = new TPFindReplaceRes();
        tpFindRep.OnTPReplaceAssetData(pendingTex, srcObj);
    }

    //写信息到SpritesSheet里
    void WriteMeta(string jText, string path)
    {
        UnityEngine.Debug.Log("WriteMeta " + path);

        XmlDocument xml = new XmlDocument();
        xml.LoadXml(jText);
        XmlNodeList elemList = xml.GetElementsByTagName("SubTexture");

        Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
        string impPath = AssetDatabase.GetAssetPath(texture);

        TextureImporter asetImp = TextureImporter.GetAtPath(impPath) as TextureImporter;
        SpriteMetaData[] metaData = new SpriteMetaData[elemList.Count];

        for (int i = 0, size = elemList.Count; i < size; i++)
        {
            XmlElement node = (XmlElement)elemList.Item(i);

            Rect rect = new Rect();
            rect.x = int.Parse(node.GetAttribute("x"));
            rect.y = texture.height - int.Parse(node.GetAttribute("y")) - int.Parse(node.GetAttribute("height"));
            rect.width = int.Parse(node.GetAttribute("width"));
            rect.height = int.Parse(node.GetAttribute("height"));

            metaData[i].rect = rect;
            metaData[i].pivot = new Vector2(0.5f, 0.5f);
            metaData[i].name = node.GetAttribute("name");
        }

        asetImp.spritesheet = metaData;
        asetImp.textureType = TextureImporterType.Sprite;
        asetImp.spriteImportMode = SpriteImportMode.Multiple;
        asetImp.mipmapEnabled = false;
        asetImp.SaveAndReimport();
    }

    private string OpenTPEXE()
    {
        string path = EditorUtility.OpenFilePanel("请定位TexturePacker.exe程序,用于后续操作", "", "exe");
        return path;
    }

    private void OnError(string err)
    {
        UnityEngine.Debug.LogError("TPBuild Err:" + err);
        EditorUtility.DisplayDialog("请重新定位TexturePacker.exe程序!", "Err:" + err, "确定");
        var path = OpenTPEXE();
        if (path.Length != 0)
        {
            RunExternalApp(path, argument);
        }
    }

    public class FileData
    {
        public string dirName;
        public List<string> filesList;
    }
}

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

/// <summary>
/// 编辑器主界面
/// </summary>
public class TPSelectFileWindow : EditorWindow {

    private Action<string, Texture> _callBack;
    private string _opPath;
    private Texture _selectTex;

    public void Popup(string opPath, Action<string, Texture> cb)
    {
        _callBack = cb;
        _opPath = opPath;

        var isSeek = EditorUtility.DisplayDialog("等待执行...", "是否更新原有的图集, 目前仅支持更新单个图集", "确定", "取消");
        if (isSeek == true)
        {
            TPSelectFileWindow window = EditorWindow.GetWindow(typeof(TPSelectFileWindow), true, "等待执行...") as TPSelectFileWindow;
            window.minSize = new Vector2(450, 300);
            window.Show();
        }
        else
        {
            if (_callBack != null)
            {
                _callBack.Invoke(_opPath, _selectTex);
            }
            _callBack = null;
        }
    }

    private void OnGUI()
    {
        ShowEditorGUI();
    }

    private void ShowEditorGUI()
    {
        _selectTex = EditorGUILayout.ObjectField("添加原绑定的Texture", _selectTex, typeof(Texture), true) as Texture;
        if (_selectTex != null && string.IsNullOrEmpty(_selectTex.name) == false)
        {
             EditorGUILayout.TextField(AssetDatabase.GetAssetOrScenePath(_selectTex));
        }
        
        if (GUILayout.Button("关闭窗口并继续执行"))
        {
            //关闭窗口
            this.Close();
        }
    }


    private void OnDestroy()
    {    
        if (_callBack != null)
        {
            _callBack.Invoke(_opPath, _selectTex);
        }
        _callBack = null;
        _selectTex = null;
    }
}
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using System.Collections;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;

public class TPFindReplaceRes {

    Dictionary<string, AssetData> assetDic;
    string srcGUID;
    string tarGUID;

    /// <summary>
    /// 更新Asset数据
    /// </summary>
    /// <param name="srcObj">检索已定位过的OBJ,操作后可废弃</param>
    /// <param name="tarObj">等待更新数据干净的OBJ</param>
    public void OnTPReplaceAssetData(Object srcObj, Object tarObj) {
        AssetDatabase.Refresh();

        EditorSettings.serializationMode = SerializationMode.ForceText;

        string srcPath = AssetDatabase.GetAssetPath(srcObj);
        string tarPath = AssetDatabase.GetAssetPath(tarObj);

        if (string.IsNullOrEmpty(tarPath) || string.IsNullOrEmpty(srcPath))
        {
            Debug.Log("srcObj = null || tarObj = null");
            return;
        }

        Debug.LogFormat("srcPath:{0} , tarPath:{1}", srcPath, tarPath);

        srcGUID = AssetDatabase.AssetPathToGUID(srcPath);
        tarGUID = AssetDatabase.AssetPathToGUID(tarPath);

        OnCreateMetaData(srcPath, tarPath);

        var withoutExtensions = new List<string>() { ".prefab", ".unity", ".mat", ".asset" };
        string[] files = Directory.GetFiles(Application.dataPath, "*.*", SearchOption.AllDirectories).Where(s => withoutExtensions.Contains(Path.GetExtension(s).ToLower())).ToArray();

        int startIndex = 0;
        EditorApplication.update = delegate () {
            string file = files[startIndex];

            bool isCancel = EditorUtility.DisplayCancelableProgressBar("匹配资源中", file, (float)startIndex / (float)files.Length);

            if (Regex.IsMatch(File.ReadAllText(file), srcGUID))
            {
                OnUpdateData(file);
                var obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(GetRelativeAssetsPath(file));
                Debug.Log("替换的对象 " + file, obj);
            }

            startIndex++;
            if (isCancel || startIndex >= files.Length)
            {
                EditorUtility.ClearProgressBar();
                EditorApplication.update = null;
                startIndex = 0;
                Debug.Log("更新数据结束");
            }
        };
    }

    void OnCreateMetaData(string srcPath, string tarPath)
    {
        string srcMeta = AssetDatabase.GetTextMetaFilePathFromAssetPath(srcPath);
        string tarMeta = AssetDatabase.GetTextMetaFilePathFromAssetPath(tarPath);

        string[] srcFile = File.ReadAllLines(srcMeta);
        string[] tarFile = File.ReadAllLines(tarMeta);

        List<string> smList = new List<string>();
        List<string> tempList = new List<string>();
        
        TextureImporter srcImp = TextureImporter.GetAtPath(srcPath) as TextureImporter;
        TextureImporter tarImp = TextureImporter.GetAtPath(tarPath) as TextureImporter;

        foreach (var spMD in tarImp.spritesheet)
        {
            tempList.Add(spMD.name);
        }

        foreach (var spMD in srcImp.spritesheet)
        {
            if (tempList.Contains(spMD.name))
            {
                smList.Add(spMD.name);
                //Debug.Log("匹配SpriteSheetMeta " + spMeta.name);
            }
        }

        assetDic = new Dictionary<string, AssetData>();
        foreach (var key in smList)
        {
            AssetData data = new AssetData();
            data.name = key;
            assetDic.Add(key, data);

            foreach (var srcStr in srcFile)
            {
                if (srcStr.IndexOf(key) >= 0)
                {
                    var str = srcStr.Trim();
                    var spMetaId = str.Substring(0, str.IndexOf(":"));
                    //Debug.LogFormat("src ==> {0} {1}", key, spMetaId);

                    if (string.IsNullOrEmpty(assetDic[key].src))
                    {
                        assetDic[key].src = spMetaId;
                    }
                }
            }

            foreach (var tarStr in tarFile)
            {
                if (tarStr.IndexOf(key) >= 0)
                {
                    var str = tarStr.Trim();
                    var spMetaId = str.Substring(0, str.IndexOf(":"));
                    //Debug.LogFormat("tar ==> {0} {1}", key, spMetaId);

                    if (string.IsNullOrEmpty(assetDic[key].tar))
                    {
                        assetDic[key].tar = spMetaId;
                    }
                }
            }

            if (string.IsNullOrEmpty(assetDic[key].src) || string.IsNullOrEmpty(assetDic[key].tar))
            {
                assetDic.Remove(key);
            }
        }

        //foreach (var meta in assetDic)
        //{
        //    var data = meta.Value;
        //    Debug.LogFormat("meta ==> name:{0} src:{1} tar:{2}", data.name, data.src, data.tar);
        //}
    }

    void OnUpdateData(string file)
    {

        var allInfo = File.ReadAllLines(file);

        for (int i = 0; i < allInfo.Length; i++)
        {
            var str = allInfo[i];
            if (str.IndexOf(srcGUID) >= 0)
            {
                if (str.IndexOf("guid") >= 0)
                {
                    // 替换旧的GUID
                    str = str.Replace(srcGUID, tarGUID);

                    // 替换旧的fileID
                    if (str.IndexOf("fileID") >= 0)
                    {
                        foreach (var meta in assetDic)
                        {
                            if (str.IndexOf(meta.Value.src) >= 0)
                            {
                                str = str.Replace(meta.Value.src, meta.Value.tar);
                            }
                        }
                    }
                }

                allInfo[i] = str;
            }
        }

        File.WriteAllLines(file, allInfo);
    }

    static string GetRelativeAssetsPath(string path) {
        return "Assets" + Path.GetFullPath(path).Replace(Path.GetFullPath(Application.dataPath), "").Replace('\\', '/');
    }

    public class AssetData
    {
        public string name;
        public string src;
        public string tar;
    }
}



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值