Unity批处理生成/拆分图集

要写游戏Demo,所以临时扒了游戏资源来用,你懂的。然鹅~,非Unity开发的游戏,大多使用TexturePacker制作图集,这东东Unity无法直接使用,虽然Sprite Editor自带三种拆分图集的方法,但是会有误差,往往不能达到要求,尤其是帧动画图集对子图大小和偏移要求必须精准,稍有误差就会鬼畜。

网上找了好久图集拆分工具,要么是根据透明像素自动拆分,要么是python写的,还需要配置一堆环境,安装所需库。最终无果,一怒之下自己写个吧,毕竟也算个痛点。

图集一般会包含两个文件,一个是记录子图位置和偏移的图集配置表,通常是plist格式,另一个是合图。

一.生成图集:这里的生成图集指的是根据图集表精准还原生成子图的大小和偏移数值。

实际上就是根据图集表自动填充如图所示区域的数据:

图1

这个需求很简单,怎么实现我不管😎:

很明显只需以下两步:

1. 解析图集配置表:

很遗憾,我随手扒来的游戏资源是使用libgdx(一款开源的小众游戏引擎,我没用过的统统算小众!!!) 开发的。它使用的图集格式与plist不同,所以想要解析plist格式,那就自己个儿写解析部分吧!

libgdx所用图集格式:

img_atlas.png
format: RGBA8888
filter: Linear, Linear
repeat: none
img_0
  rotate: false
  xy: 2, 2
  size: 353, 62
  orig: 353, 62
  offset: 0, 0
  index: -1
img_1
  rotate: false
  xy: 2, 66
  size: 353, 62
  orig: 353, 62
  offset: 0, 0
  index: -1

只需要拿到每个子图的x,y和size值就OK啦。代码如下:

/// <summary>
/// 子图 信息
/// </summary>
internal class GDXAtlasElement
{
    internal string name;
    internal Rect rect;
}
/// <summary>
/// 图集 信息
/// </summary>
internal class GDXAtlas
{
    internal string name;
    internal List<GDXAtlasElement> elements;
}
/// <summary>
/// 解析图集配置表工具类
/// </summary>
internal class GDXAtlasUtility
{
    internal static bool ParseAtlas(string atlasTxtFile, out GDXAtlas atlas)
    {
        atlas = null;
        if (!File.Exists(atlasTxtFile))
        {
            return false;
        }
        var lines = new List<string>();
        foreach (var line in File.ReadLines(atlasTxtFile))
        {
            if (string.IsNullOrWhiteSpace(line))
            {
                continue;
            }
            lines.Add(line);
        }
        if (lines.Count <= 0)
        {
            return false;
        }
        string atlasName = lines[0].Trim();
        List<GDXAtlasElement> elements = new List<GDXAtlasElement>();
        for (int i = 4; i < lines.Count;)
        {
            if (i + 6 > lines.Count)
            {
                break;
            }

            string eName = lines[i];
            string[] eXyValues = lines[i + 2].Trim().Split(':')[1].Trim().Split(',');
            string[] eSizeValues = lines[i + 3].Trim().Split(':')[1].Trim().Split(',');

            GDXAtlasElement spElement = new GDXAtlasElement();
            spElement.name = eName.Trim();

            Vector2 position = new Vector2(float.Parse(eXyValues[0].Trim()), float.Parse(eXyValues[1].Trim()));
            Vector2 size = new Vector2(float.Parse(eSizeValues[0].Trim()), float.Parse(eSizeValues[1].Trim()));
            spElement.rect = new Rect(position, size);
            elements.Add(spElement);
            i += 7;
        }
        atlas = new GDXAtlas();
        atlas.name = atlasName;
        atlas.elements = elements;
        return true;
    }
}

2.填充子图数据:

也就是把第1部解析的数据填充到图1所示红框部分。

恕我直言,最大的难点就是找到哪个类中的哪个变量储存着子图信息,反正我是找了好久。划重点!!!这个存放图集子图的变量就是:TextureImporter.spritesheet

private void AutoSliceAtlas()
    {
        EditorUtility.DisplayProgressBar("Progress", "Slice Atlas...", 0);
        string[] dirs = { "Assets/MainGame/Textures/zombie_assets" };
        var assetIds = AssetDatabase.FindAssets("t:Texture", dirs);
        for (int i = 0; i < assetIds.Length; i++)
        {
            string spFileName = AssetDatabase.GUIDToAssetPath(assetIds[i]);
            var spTex = AssetDatabase.LoadAssetAtPath<Texture>(spFileName);

            string atlasTxtFile = string.Format("{0}{1}", Application.dataPath.Substring(0, Application.dataPath.Length - 6), spFileName.Substring(0, spFileName.Length - 4));
            if (!GDXAtlasUtility.ParseAtlas(atlasTxtFile, out GDXAtlas gdxAtlas))
            {
                continue;
            }
            SpriteMetaData[] spSheet = new SpriteMetaData[gdxAtlas.elements.Count];
            for (int elemIndex = 0; elemIndex < gdxAtlas.elements.Count; elemIndex++)
            {
                var eDt = gdxAtlas.elements[elemIndex];
                var spDt = new SpriteMetaData();
                var fixRect = eDt.rect;
                //libgdx图集坐标系原点是左上角, 这里需要转换到Unity坐标系(左下角)
                fixRect.y = spTex.height - fixRect.y - fixRect.size.y;
                spDt.name = eDt.name;
                spDt.rect = fixRect;
                spDt.pivot = Vector2.one * 0.5f;
                spDt.border = Vector4.zero;
                spDt.alignment = (int)SpriteAlignment.Custom;

                spSheet[elemIndex] = spDt;
            }
            var texImporter = AssetImporter.GetAtPath(spFileName) as TextureImporter;
            texImporter.spritesheet = spSheet;
            texImporter.spriteImportMode = SpriteImportMode.Multiple;
            texImporter.isReadable = true;
            //texImporter.SaveAndReimport();//这段需要注释掉,否则修改不会生效.修改完只能手动Apply,很不爽
            EditorUtility.DisplayProgressBar("Progress", "Slice Atlas...", (i + 1) / (float)assetIds.Length);
        }
        EditorUtility.ClearProgressBar();
    }

最终得到这样的效果:

二.拆分图集:

就是把前面生成图集里的所有子图单独拆分成图片文件:

Talking is cheap,show you my code:

private void SplitAtlasSprites()
    {
        EditorUtility.DisplayProgressBar("Progress", "Slice Atlas...", 0);
        string[] dirs = { "Assets/MainGame/Textures/zombie_assets" };
        var assetIds = AssetDatabase.FindAssets("t:Texture", dirs);
        for (int i = 0; i < assetIds.Length; i++)
        {
            string spFileName = AssetDatabase.GUIDToAssetPath(assetIds[i]);
            var spTex = AssetDatabase.LoadAssetAtPath<Texture2D>(spFileName);
            var texImporter = AssetImporter.GetAtPath(spFileName) as TextureImporter;
            if (texImporter.spriteImportMode != SpriteImportMode.Multiple)
            {
                continue;
            }
            if (!texImporter.isReadable)
            {
                Debug.LogWarning(string.Format("图集不可读:{0}", spFileName));
                continue;
            }
            for (int spIndex = 0; spIndex < texImporter.spritesheet.Length; spIndex++)
            {
                var spDt = texImporter.spritesheet[spIndex];
                var tex = new Texture2D((int)spDt.rect.width, (int)spDt.rect.height);

                tex.SetPixels(spTex.GetPixels((int)spDt.rect.x, (int)spDt.rect.y, tex.width, tex.height));
                tex.Apply();
                FileInfo fInfo = new FileInfo(string.Format("{0}{1}", Application.dataPath.Substring(0, Application.dataPath.Length - 6), spFileName));
                string savePath = string.Format("{0}/{1}_slice", fInfo.DirectoryName, spTex.name);
                if (!Directory.Exists(savePath))
                {
                    Directory.CreateDirectory(savePath);
                }
                string fileName = string.Format("{0}/{1}.png", savePath, spDt.name);
                if (File.Exists(fileName))
                {
                    File.Delete(fileName);
                }
                File.WriteAllBytes(fileName, tex.EncodeToPNG());
            }
            AssetDatabase.Refresh();
            EditorUtility.DisplayProgressBar("Progress", "Slice Atlas...", (i + 1) / (float)assetIds.Length);
        }
        EditorUtility.ClearProgressBar();
    }

需要注意的是,导入的合图必须设置为可读。

### 关于Unity图集批量处理的方法 #### 使用Sprite Atlas API实现自动化脚本 为了应对不同版本间的差异,可以利用Unity提供的`SpriteAtlas`API来编写自定义的编辑器脚本来实现图集的批量处理。对于从旧版向新版迁移的情况,可以通过读取带有相同`PackingTag`标签的纹理资源并将其分配给新的`SpriteAtlas`对象。 ```csharp using UnityEngine; using UnityEditor; public class SpriteAtlasBatchProcessor : EditorWindow { private string atlasName = "NewAtlas"; [MenuItem("Tools/Create Sprite Atlas")] public static void CreateWindow() { GetWindow<SpriteAtlasBatchProcessor>("Sprite Atlas Batch Processor"); } void OnGUI() { GUILayout.Label("Enter the name of your new sprite atlas:", EditorStyles.boldLabel); atlasName = EditorGUILayout.TextField(atlasName); if (GUILayout.Button("Generate")) Generate(); } void Generate(){ Object[] textures = Selection.objects; // 获取当前选择的对象 var assetPath = AssetDatabase.GenerateUniqueAssetPath($"Assets/Resources/{atlasName}.spriteatlas"); SpriteAtlas spriteAtlas = ScriptableObject.CreateInstance<SpriteAtlas>(); foreach(var texture in textures){ TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath(AssetDatabase.GetAssetPath(texture)); if(importer != null && !string.IsNullOrEmpty(importer.spriteID)){ spriteAtlas.Add(new[]{texture}); } } AssetDatabase.CreateAsset(spriteAtlas, assetPath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } } ``` 此段代码展示了如何创建一个简单的编辑器窗口用于生成指定名称的新图集,并允许用户选取多个材质贴图作为输入源[^1]。 #### 利用第三方插件加速工作流程 除了官方API外,还有许多社区开发的工具可以帮助更高效地管理图集。例如有开发者分享了一款功能丰富的图集编辑器,它不仅能够支持基本的增删查改操作,还特别加入了针对大规模项目的优化特性,比如按名字筛选特定条目等功能[^3]。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

异趣游戏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值