Unity 图集Atlas功能需求实现

unity自身的Sprite Atlas介绍

Sprite Atlas的主要有以下三个功能:
1.创建、编辑图集以及设定图集参数

2.添加图集Variant(变种)

3.运行时访问图集
Unity 图集Atlas功能需求实现_第1张图片
通过图中红点标注的地方,可以添加你想添加的图片。
Unity 图集Atlas功能需求实现_第2张图片
P.S.这里可以支持多种类型,包括单个Sprite、Sliced Sprite、文件夹,以及这些类型的任意组合。
此外选择Variant(变种)可以复制原有图集的贴图,并根据一个比例系数来调整复制贴图的大小。为不同分辨率的屏幕准备不同的图集。
Unity 图集Atlas功能需求实现_第3张图片
Unity的图集可以说功能算是比较强大了,但是他不能指定图集的图片类型,让操作上还是很不舒服。所以我参考了别人的图集打包方式。

可设置图集类型的方法

先上示意图Unity 图集Atlas功能需求实现_第4张图片
下面我把代码附上,注意写好的代码要放在Unity工程下的Editor目录下。

using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System.Text;
 
public class AtlasCreate  {
    //用来保存图片中的设置信息 比如用RGBA32压缩格式等
    public class TextureImporterSettings
    {
        public bool isReadable;//纹理信息在内存中是否可读
        public TextureImporterFormat textureFormat;//纹理的格式
 
        public TextureImporterSettings(bool isReadable, TextureImporterFormat textureFormat)
        {
            this.isReadable = isReadable;
            this.textureFormat = textureFormat;
        }
    }
    //用来保存单个图片的信息
    public class SpriteInfo
    {
        public string name;//图片的名字
        public Vector4 spriteBorder;//图片的包围盒(如果有的话)
        public Vector2 spritePivot;//图片包围盒中的中心轴(如果有的话)
        public float width;
        public float height;
 
        public SpriteInfo(string name, Vector4 border, Vector2 pivot, float w, float h)
        {
            this.name = name;
            spriteBorder = border;
            spritePivot = pivot;
            width = w;
            height = h;
        }
    }
    static float matAtlasSize = 2048;//最大图集尺寸
    static float padding = 1;//每两个图片之间用多少像素来隔开
    private static List<SpriteInfo> spriteList = new List<SpriteInfo>();
    [MenuItem("Assets/AtlasCreate")]
    static public void Init()
    {
        string assetPath;
        //根据我们的选择来获取选中物体的信息
        Object[] objs = Selection.GetFiltered(typeof(Texture), SelectionMode.DeepAssets);
        //判断图片命名的合法性
        for (int i = 0; i < objs.Length; i++)
        {
            Object obj = objs[i];
            if (obj.name.StartsWith(" ") || obj.name.EndsWith(" "))
            {
                string newName = obj.name.TrimStart(' ').TrimEnd(' ');
                Debug.Log(string.Format("rename texture'name old name : {0}, new name {1}", obj.name, newName));
                AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(obj), newName);
            }
        }
        Texture2D[] texs = new Texture2D[objs.Length];//用来保存objs中的物体
        if (texs.Length <= 0)
        {
            Debug.Log("请先选择要合并的小图或小图的目录");
            return;
        }
 
        
        for (var i = 0; i < objs.Length; i++)
        {
            texs[i] = objs[i] as Texture2D;
            assetPath = AssetDatabase.GetAssetPath(texs[i]);
            AssetDatabase.ImportAsset(assetPath);//重新把图片导入内存,理论上unity工程中的资源在用到的时候,Unity会自动导入到内存,但有的时候却没有自动导入,为了以防万一,我们手动导入一次
        }
 
        //得到图片的设置信息
        TextureImporterSettings[] originalSets = GatherSettings(texs);
 
        //根据我们的需求 设置图片的一些信息.
        for (int i = 0; i < texs.Length; i++)
        {
            SetupTexture(texs[i], true, TextureImporterFormat.RGBA32);
        }
        //最终打成的图集路径,包括名字
        assetPath = "Assets/Atlas.png";
        string outputPath = Application.dataPath + "/../" + assetPath;
        //主要的打图集代码
        PackAndOutputSprites(texs, assetPath, outputPath);
        //打出图集后在Unity选中它
        EditorGUIUtility.PingObject(AssetDatabase.LoadAssetAtPath(assetPath, typeof(Texture)));
    }
    //得到图片的设置信息
    static public TextureImporterSettings[] GatherSettings(Texture2D[] texs)
    {
        TextureImporterSettings[] sets = new TextureImporterSettings[texs.Length];
        for (var i = 0; i < texs.Length; i++)
        {
            var tex = texs[i];
            var assetPath = AssetDatabase.GetAssetPath(tex);
            TextureImporter imp = AssetImporter.GetAtPath(assetPath) as TextureImporter;
            sets[i] = new TextureImporterSettings(imp.isReadable, imp.textureFormat);
            //如果图片由包围盒的话 记录包围盒信息
            if (imp.textureType == TextureImporterType.Sprite && imp.spriteBorder != Vector4.zero)
            {
                var spriteInfo = new SpriteInfo(tex.name, imp.spriteBorder, imp.spritePivot, tex.width, tex.height);
                spriteList.Add(spriteInfo);
            }
        }
        return sets;
    }
    //根据我们的需求 设置图片的一些信息.
    static public void SetupTexture(Texture2D tex, bool isReadable, TextureImporterFormat textureFormat)
    {
        var assetPath = AssetDatabase.GetAssetPath(tex);
        TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
        importer.isReadable = isReadable;//图片是否可读取它的内存信息
        importer.textureFormat = textureFormat;//图片的格式
        importer.mipmapEnabled = false;//是否生成mipmap文件
        importer.npotScale = TextureImporterNPOTScale.None;//用于非二次幂纹理的缩放模式
        importer.SaveAndReimport();//刷新图片
    }
 
    static public void PackAndOutputSprites(Texture2D[] texs, string atlasAssetPath, string outputPath)
    {
        Texture2D atlas = new Texture2D(1, 1);
        Rect[] rs = atlas.PackTextures(texs, (int)padding, (int)matAtlasSize);//添加多个图片到一个图集中,返回值是每个图片在图集(大图片)中的U坐标等信息
        // 把图集写入到磁盘文件,最终在磁盘上会有一个图片生成,这个图片包含了很多小图片
        File.WriteAllBytes(outputPath, atlas.EncodeToPNG());
        RefreshAsset(atlasAssetPath);//刷新图片
 
        //记录图片的名字,只是用于输出日志用;
        StringBuilder names = new StringBuilder();
        //SpriteMetaData结构可以让我们编辑图片的一些信息,想图片的name,包围盒border,在图集中的区域rect等
        SpriteMetaData[] sheet = new SpriteMetaData[rs.Length];
        for (var i = 0; i < sheet.Length; i++)
        {
            SpriteMetaData meta = new SpriteMetaData();
            meta.name = texs[i].name;
            meta.rect = rs[i];//这里的rect记录的是单个图片在图集中的uv坐标值
            //因为rect最终需要记录单个图片在大图片图集中所在的区域rect,所以我们做如下的处理
            meta.rect.Set(
                meta.rect.x * atlas.width,
                meta.rect.y * atlas.height,
                meta.rect.width * atlas.width,
                meta.rect.height * atlas.height
            );
            //如果图片有包围盒信息的话
            var spriteInfo = GetSpriteMetaData(meta.name);
            if (spriteInfo != null)
            {
                meta.border = spriteInfo.spriteBorder;
                meta.pivot = spriteInfo.spritePivot;
            }
            sheet[i] = meta;
            //打印日志用
            names.Append(meta.name);
            if (i < sheet.Length - 1)
                names.Append(",");
        }
 
        //设置图集的信息
        TextureImporter imp = TextureImporter.GetAtPath(atlasAssetPath) as TextureImporter;
        imp.textureType = TextureImporterType.Sprite;//图集的类型
        imp.textureFormat = TextureImporterFormat.AutomaticCompressed;//图集的格式
        imp.spriteImportMode = SpriteImportMode.Multiple;//Multiple表示我们这个大图片(图集)中包含很多小图片
        imp.mipmapEnabled = false;//是否开启mipmap
        imp.spritesheet = sheet;//设置图集中小图片的信息(每个图片所在的区域rect等)
        // 保存并刷新
        imp.SaveAndReimport();
        spriteList.Clear();
        //输出日志
        Debug.Log("Atlas create ok. " + names.ToString());
    }
    //刷新图片
    static public void RefreshAsset(string assetPath)
    {
        AssetDatabase.Refresh();
        AssetDatabase.ImportAsset(assetPath);
    }
    //得到图片的信息
    static public SpriteInfo GetSpriteMetaData(string texName)
    {
        for (int i = 0; i < spriteList.Count; i++)
        {
            if (spriteList[i].name == texName)
            {
                return spriteList[i];
            }
        }
        //Debug.Log("Can not find texture metadata : " + texName);
        return null;
    }
 
}

代码写好以后,选中你想打包在一起的图片右,右键选择图中的选项就可以打包成功。Unity 图集Atlas功能需求实现_第5张图片
同时为了方便调用UGUI的sprite,我们也同样需要为其创建一个asset文件。

通过Asset文件查看图集信息

1.为我们准备创建的asset文件写一个脚本,他需要继承ScriptableObject,同时我们需要存储一些其他信息,所以需要一个信息脚本,这里需要将信息转为一个资源文件,所以需要使用[Serializable],大概目的就是序列化使其成为两者都能识别的信息,网上查了一个序列化的解释:[Serializable]在C#中的作用—序列化与反序列化详解
SpriteAsset脚本:

using UnityEngine;
using System.Collections.Generic;

public class UGUISpriteAsset : ScriptableObject
{
    /// 
    /// 图片资源
    /// 
    public Texture texSource;
    /// 
    /// 所有sprite信息 SpriteAssetInfor类为具体的信息类
    /// 
    public List<SpriteAssetInfor> listSpriteAssetInfor;
}

Sprite脚本:

using UnityEngine;
using System;

[Serializable]
public class SpriteAssetInfor
{
    /// 
    /// ID
    /// 
    public int ID;
    /// 
    /// 名称
    /// 
    public string name;
    /// 
    /// 中心点
    /// 
    public Vector2 pivot;
    /// 
    ///坐标&宽高
    /// 
    public Rect rect;
    /// 
    /// 精灵
    /// 
    public Sprite sprite;

}

2.然后写两个编辑类,都需要放在Editor目录下,通过编辑类的操作可以实现创建.asset文件。选中上面我们打好的图集,右键然后点击"Create/UGUI Sprite Asset",即可以创建一个同图片同名的.asset文件
UGUICreateSpriteAsset类:

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

public static class UGUICreateSpriteAsset
{
    [MenuItem("Assets/Create/UGUI Sprite Asset", false, 10)]
    static void main()
    {
        Object target = Selection.activeObject;
        if (target == null || target.GetType() != typeof(Texture2D))
            return;

        Texture2D sourceTex = target as Texture2D;
        //整体路径
        string filePathWithName = AssetDatabase.GetAssetPath(sourceTex);
        //带后缀的文件名
        string fileNameWithExtension = Path.GetFileName(filePathWithName);
        //不带后缀的文件名
        string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePathWithName);
        //不带文件名的路径
        string filePath = filePathWithName.Replace(fileNameWithExtension, "");

        UGUISpriteAsset spriteAsset = AssetDatabase.LoadAssetAtPath(filePath + fileNameWithoutExtension + ".asset", typeof(UGUISpriteAsset)) as UGUISpriteAsset;
        bool isNewAsset = spriteAsset == null ? true : false;
        if (isNewAsset)
        {
            spriteAsset = ScriptableObject.CreateInstance<UGUISpriteAsset>();
            spriteAsset.texSource = sourceTex;
            spriteAsset.listSpriteAssetInfor = GetSpritesInfor(sourceTex);
            AssetDatabase.CreateAsset(spriteAsset, filePath + fileNameWithoutExtension + ".asset");
        }
    }

    public static List<SpriteAssetInfor> GetSpritesInfor(Texture2D tex)
    {
        List<SpriteAssetInfor> m_sprites = new List<SpriteAssetInfor>();

        string filePath = UnityEditor.AssetDatabase.GetAssetPath(tex);

        Object[] objects = UnityEditor.AssetDatabase.LoadAllAssetsAtPath(filePath);

        for (int i = 0; i < objects.Length; i++)
        {
            if (objects[i].GetType() == typeof(Sprite))
            {
                SpriteAssetInfor temp = new SpriteAssetInfor();
                Sprite sprite = objects[i] as Sprite;
                temp.ID = i;
                temp.name = sprite.name;
                temp.pivot = sprite.pivot;
                temp.rect = sprite.rect;
                temp.sprite = sprite;
                m_sprites.Add(temp);
            }
        }
        return m_sprites;
    }
}

3.我们可以通过一个[CustomEditor]来自定义上面编辑类的属性面板,他需要继承Editor,并在类名上声明他为哪个类自定义属性表,需要使用OnInspectorGUI函数来绘制,使用方法与OnGUI一样,注意关键字override。
UGUISpriteAssetEditor类:

using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor(typeof(UGUISpriteAsset))]
public class UGUISpriteAssetEditor : Editor {

    UGUISpriteAsset spriteAsset;

    public void OnEnable()
    {
        spriteAsset = (UGUISpriteAsset)target;
    }
    private Vector2 ve2ScorllView;
    public override void OnInspectorGUI()
    {
        ve2ScorllView = GUILayout.BeginScrollView(ve2ScorllView);
        GUILayout.Label("UGUI Sprite Asset");
        if (spriteAsset.listSpriteAssetInfor == null)
            return;
        for (int i = 0; i < spriteAsset.listSpriteAssetInfor.Count; i++)
        {
            GUILayout.Label("\n");
            EditorGUILayout.ObjectField("",spriteAsset.listSpriteAssetInfor[i].sprite, typeof(Sprite));
            EditorGUILayout.IntField("ID:", spriteAsset.listSpriteAssetInfor[i].ID);
            EditorGUILayout.LabelField("name:", spriteAsset.listSpriteAssetInfor[i].name);
            EditorGUILayout.Vector2Field("povit:", spriteAsset.listSpriteAssetInfor[i].pivot);
            EditorGUILayout.RectField("rect:", spriteAsset.listSpriteAssetInfor[i].rect);
            GUILayout.Label("\n");
        }
        GUILayout.EndScrollView();
    }

}

4.asset文件:
Unity 图集Atlas功能需求实现_第6张图片
6.写一个测试脚本,不停的切换图片,后面可以封装一下,通过名称或者ID来索引sprite

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class ChangeUGUISprite : MonoBehaviour {
    
    public UGUISpriteAsset usa;
    private float fTime = 0.0f;
    
	// Update is called once per frame
	void Update () {
        fTime += Time.deltaTime;
        if (fTime >= 0.3f)
        {
            GetComponent<Image>().sprite = usa.listSpriteAssetInfor[Random.Range(0, usa.listSpriteAssetInfor.Count)].sprite;
            fTime = 0.0f;
        }
	}
}

参考文章:unity UGUI实现类似NGUI切换Sprite的方式

你可能感兴趣的:(UGUI,unity)