ugui图集 unity3d

2018-05-23 10:24:37 weixin_41843959 阅读数 5077
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

参考文献 :雨松MOMO UGUI研究院之全面理解图集与使用(三)

启用的方式:在Edit->Project Settings->Editor->Sprite Packer->Mode中进行设置:
1.Disabled:不启用。
2.Enabled For Builds:打包发布时启用。
3.Always Enabled:一直启用,在开发和打包发布阶段都启用,方便开发人员在开发阶段去查看unity自动生成的图集信息,默认推荐使用这种方式。

这里写图片描述
设置tag Ugui图集打包,unity自动会将这些小图按照tag名字打到图集里面去,

查看打包 window->sprite packer

打图集策略:
1.DefaultPackerPolicy:是默认的打包方式,也是矩形打包方式。他会把所有的小图按照矩形的方式来排列,如果宽高不一样的图片,它们会自动补齐,使用方式就是tag设置时加上”[RECT]图集名”来设置。
2.TightPackerPolicy:是紧密打包方式,也就是尽可能的把图片都打包在图集上,这种方式要比DefaultPackerPolicy打包的图片更多一些,也就是更省空间,使用方式就是tag设置时加上”[TIGHT]图集名”来设置。
3.TightRotateEnabledPackerPolicy:是紧密可旋转打包方式,也就是使用紧密打包,并且允许精灵进行旋转。
注意打包的图片不要放在Rescore下。

到上面图集已经打包完,但如果需要动态加载还需要自己写代码
Atlas 和 AutoSetTextureUISprite 包含using UnityEditor;  需放在 Editor 下否则发布会报错
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;

/// <summary>
/// 导入图片资源到Unity时,自动修改为UI 2D Sprite,自动设置打包tag 为文件夹名字;
/// </summary>
public class AutoSetTextureUISprite : AssetPostprocessor
{
    void OnPostprocessTexture(Texture2D texture)
    {
        string path = Path.GetDirectoryName(assetPath);
        string[] strlist= path.Split('/');

        if (strlist[1]!= "Atlas")
        {//忽略其他文件夹自动设置
            return;
        }
        //自动设置打包tag;  
        string AtlasName = new DirectoryInfo(path).Name;
        TextureImporter textureImporter = assetImporter as TextureImporter;
        //自动设置类型;  
        textureImporter.textureType = TextureImporterType.Sprite;
        textureImporter.spritePackingTag = AtlasName;
        //textureImporter.mipmapEnabled = false;

    }
}


我是按照文件加打包图集的,
制作思路:将Atlas每个文件夹 对应生成一个预制,再在预制下对应文件夹图片生成对应的sprite,我这时用Image 存储sprite的,
有更好的可以使用其他存储,所有sprite生成完后将预制保存在Resourses/Texture2D 下

using UnityEngine;
using UnityEditor;
using System.IO;

 /// <summary>
 ///1.遍历“Atlas”下的每个文件夹,。 
 ///2.创建一个go,添加AtlasMap脚本,把文件夹下所有的Sprite都添加到go上, 
 /// 3.将go创建为对应预设,并放置到“Resources/Texture2D”下的对应文件夹处,预设名字为“文件夹名”,然后将go删除。
 /// </summary>
public class Atlas
{
    [MenuItem("Edit Tools/ AtlasMaker")]
    public static void MakeAtlas()
    {
        string targetDir = Application.dataPath + "/Atlas";
        if (!Directory.Exists(targetDir))
        {
            Directory.CreateDirectory(targetDir);
        }
        DirectoryInfo fromDirInfo = new DirectoryInfo(targetDir);
        foreach (DirectoryInfo dirInfo in fromDirInfo.GetDirectories())
        {
            MakeOnePrefab(dirInfo, fromDirInfo);
            //if (dirInfo.Name == "MainUI")
            //// 因为MainUI下面还有很多子级别,子级别也有对应的动态图集列表// 
            //{
            //    foreach (DirectoryInfo mainUIChildDir in dirInfo.GetDirectories())
            //    {
            //        MakeOnePrefab(mainUIChildDir, fromDirInfo, targetDir + "/MainUI");
            //    }
            //}
            //else
            //{
            //    MakeOnePrefab(dirInfo, fromDirInfo, targetDir);
            //}
        }
    }

    /// <summary>
    /// 遍历文件夹下所有图片进行节点生成
    /// </summary>
    /// <param name="dirInfo">文件夹</param>
    /// <param name="fromDirInfo">起始文件夹</param>
    static void MakeOnePrefab(DirectoryInfo dirInfo, DirectoryInfo fromDirInfo)
    {
        string fieldName = dirInfo.Name;
        FileInfo[] allPngFiles = null;
        //找到所有.png文件
        allPngFiles = dirInfo.GetFiles("*.png", SearchOption.AllDirectories);

        if (allPngFiles.Length <= 0)
        {
            string shortPath = fromDirInfo.FullName.Substring(fromDirInfo.FullName.IndexOf("Assets"));
            Debug.LogWarning(string.Format("There is no sprite where path is {0}/{1}.Do you forget to add needed sprite there?", shortPath, fieldName));
        }
        else
        {
            GameObject gameObject = new GameObject(fieldName);
            AtlasMap atlasMap = gameObject.AddComponent<AtlasMap>();
            // 如果“Resources/UI”下没有和“ResourcesHW/UI对应的文件夹”则创建一个
            // 
            string prefabParentFieldPath = string.Format(Application.dataPath + "/Resources/Texture2D");
            if (!Directory.Exists(prefabParentFieldPath))
            {
                Directory.CreateDirectory(prefabParentFieldPath);
            }

            // 将Sprite存入AtlasMap脚本中
            foreach (FileInfo pngFile in allPngFiles)
            {
                string assetPath = pngFile.FullName.Substring(pngFile.FullName.IndexOf("Assets"));
                Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
                if (sprite == null)
                {
                    Debug.LogWarning(string.Format("It’s not a sprite which path is {0}, and don’t move it to DynSprite field.", assetPath));
                    continue;
                }
                atlasMap.AddSprite(sprite);
            }
            // 在对应文件夹上生成预设
            string prefabAllPath = string.Format("{0}/{1}.prefab", prefabParentFieldPath, fieldName);
            string prefabPath = prefabAllPath.Substring(prefabAllPath.IndexOf("Assets"));
            PrefabUtility.CreatePrefab(prefabPath, gameObject);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            // 销毁go// 
            GameObject.DestroyImmediate(gameObject);
        }
    }
}


using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 用于存储制作好的动态图预设上的sprite信息
/// </summary>
public class AtlasMap : MonoBehaviour
{
    public Sprite GetSpriteByName(string name)
    {
        if (transform.Find(name))
        {
            return transform.Find(name).GetComponent<SpriteRenderer>().sprite;
        }
        return null;
    }
    public void AddSprite(Sprite sp)
    {
        GameObject gameObject = new GameObject();
        gameObject.name = sp.name;
        SpriteRenderer image =gameObject.AddComponent<SpriteRenderer>();
        image.sprite = sp;
        gameObject.transform.SetParent(transform);
    }
}
最后使用时先Edit Tools -- >AtlasMaker 下,就能生成了对应预制
查找sprite
    /// <summary>
    /// 根据文件和路径名,找到对应sprite,
    /// 注意使用时先Edit Tools -- AtlasMaker 下会在Resources/Texture2D下生成对应动态加载预制
    /// </summary>
    /// <param name="type">对应存放节点的预制</param>
    /// <param name="spriteName"> 名字</param>
    /// <returns>返回搜索到的精灵</returns>
    public static Sprite GetSpriteByTypeAndName(string prefab, string spriteName)
    {
        GameObject go = Resources.Load(prefab) as GameObject;
        if (go == null)
        {
            Debug.LogError(string.Format("There is no prefab of {0}. Be sure that type is right and you had make atlas of type", prefab));
            return null;
        }
        AtlasMap am = go.GetComponent<AtlasMap>();
        if (am == null)
        {
            Debug.LogError(string.Format("There is no AtlasMap on prefab of {0}.Be sure MakeAtlasMaker work", prefab));
            return null;
        }
        return am.GetSpriteByName(spriteName);
    }
}
效果图
用了几天发现,每次导图集的引用都会全部导一遍,耗时慢,而且多人开发的化容易出现冲突,所以对操作进行修改了下

将Atlas 修改了下

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

/// <summary>
///1.遍历“Atlas”下的每个文件夹,。 
///2.创建一个go,添加AtlasMap脚本,把文件夹下所有的Sprite都添加到go上, 
/// 3.将go创建为对应预设,并放置到“Resources/Texture2D”下的对应文件夹处,预设名字为“文件夹名”,然后将go删除。
/// </summary>
public class Atlas
{
    [MenuItem("Assets/导出图集引用")]
    public static void MakeAtlas()
    {
        Object[] objects = Selection.GetFiltered(typeof(object), SelectionMode.Assets);
        List<string> directoryName = GetDirectoryName(objects);

        for (int i = 0; i < directoryName.Count; i++)
        {
            string targetDir = Application.dataPath + "/Atlas/"+ directoryName[i];
            if (!Directory.Exists(targetDir))
            {
                //选错文件夹跳过
                continue;
            }
            DirectoryInfo fromDirInfo = new DirectoryInfo(targetDir);
            MakeOnePrefab(fromDirInfo, fromDirInfo);
            //if (dirInfo.Name == "MainUI")
            //// 因为MainUI下面还有很多子级别,子级别也有对应的动态图集列表// 
            //{
            //    foreach (DirectoryInfo mainUIChildDir in dirInfo.GetDirectories())
            //    {
            //        MakeOnePrefab(mainUIChildDir, fromDirInfo, targetDir + "/MainUI");
            //    }
            //}
            //else
            //{
            //    MakeOnePrefab(dirInfo, fromDirInfo, targetDir);
            //}
        }
    }

    /// <summary>
    /// 遍历文件夹下所有图片进行节点生成
    /// </summary>
    /// <param name="dirInfo">文件夹</param>
    /// <param name="fromDirInfo">起始文件夹</param>
    static void MakeOnePrefab(DirectoryInfo dirInfo, DirectoryInfo fromDirInfo)
    {
        string fieldName = dirInfo.Name;
        FileInfo[] allPngFiles = null;
        //找到所有.png文件
        allPngFiles = dirInfo.GetFiles("*.png", SearchOption.AllDirectories);

        if (allPngFiles.Length <= 0)
        {
            string shortPath = fromDirInfo.FullName.Substring(fromDirInfo.FullName.IndexOf("Assets"));
            Debug.LogWarning(string.Format("There is no sprite where path is {0}/{1}.Do you forget to add needed sprite there?", shortPath, fieldName));
        }
        else
        {
            GameObject gameObject = new GameObject(fieldName);
            AtlasMap atlasMap = gameObject.AddComponent<AtlasMap>();
            // 如果“Resources/UI”下没有和“ResourcesHW/UI对应的文件夹”则创建一个
            // 
            string prefabParentFieldPath = string.Format(Application.dataPath + "/Resources/Texture2D");
            if (!Directory.Exists(prefabParentFieldPath))
            {
                Directory.CreateDirectory(prefabParentFieldPath);
            }

            // 将Sprite存入AtlasMap脚本中
            foreach (FileInfo pngFile in allPngFiles)
            {
                string assetPath = pngFile.FullName.Substring(pngFile.FullName.IndexOf("Assets"));
                Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
                if (sprite == null)
                {
                    Debug.LogWarning(string.Format("It’s not a sprite which path is {0}, and don’t move it to DynSprite field.", assetPath));
                    continue;
                }
                atlasMap.AddSprite(sprite);
            }
            // 在对应文件夹上生成预设
            string prefabAllPath = string.Format("{0}/{1}.prefab", prefabParentFieldPath, fieldName);
            string prefabPath = prefabAllPath.Substring(prefabAllPath.IndexOf("Assets"));
            PrefabUtility.CreatePrefab(prefabPath, gameObject);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            Debug.Log(fieldName+ ".prefab 生成成功" );
            // 销毁go// 
            GameObject.DestroyImmediate(gameObject);

        }
    }

    /// <summary>
    /// 获取所选文件夹的名字
    /// </summary>
    /// <param name="objects"></param>
    /// <returns></returns>
    static List<string> GetDirectoryName(Object[] objects)
    {
        Dictionary<string, bool> directoryName = new Dictionary<string, bool>();

        for (int i = 0; i < objects.Length; i++)
        {
            string direcName = AssetDatabase.GetAssetPath(objects[i]);
            string curDirectoryName = new DirectoryInfo(direcName).Name;
            if (!directoryName.ContainsKey(curDirectoryName))
            {
                Debug.Log("文件夹名=" + curDirectoryName);
            }
            directoryName[curDirectoryName] = true;
        }
        List<string> vs = new List<string>(directoryName.Keys);
        return vs;
    }
}


操作改为,选中你要导出的图集的文件夹-》右键

demo 我就不更新了,将代码替换掉就行


demo:

2017版本 有新方法,可以使用SpriteAtlas 打包,

2018-09-28 14:52:23 weixin_42700828 阅读数 0
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

本站文章转载务必在明显处注明:原文链接 http://blog.csdn.net/cjsen/article/details/52487706

 

前言

在用UGUI时,我们也需要将一个个小图打包成图集,以减小Drawcall(类似coco2d-x一样,打包成图集一次性加载以内存换取图片读取效率),UGUI打包并使用图集有两种方法:一种是使用系统自带的打包工具SpritePacker;一种是使用TexturePacker打包图片并使用;

关于第一种方法可以参见另一文章,里面有详细的解释与示例使用(文章链接:http://www.xuanyusong.com/archives/3304),这种方法限制比较多,转而用另一种熟悉的方法用TexturePacker工具打包,也是本文下面要讲解的

正文

本文所使用的是Unity3d 5.4版本,TexturePacker 4.2.3版本

1,先用TexturePacker打小图打包成我们所需要的图集,打包的格式要注意是"Unity - Texture2D sprite sheet"(有一些低版本的TP是没有这个格式的),

打包之后会有一个.png和一个.tpsheet,不用作其他修改,将这两个文件放在工程资源中,这时从工程看这只是一张大图,并不能算是一个图集,使用里面的小图(这时虽然可以用unity3d自带功能,手动对图片进行裁剪,但裁剪的小图大小基本是不对的)

2,接下来需要下载并导入一个Unity3d的插件,TexturePacker自己出的的一个插件(TexturePacker Importer),插件链接https://www.assetstore.unity3d.com/en/#!/content/16641,下载并成功导入之后,不用写任何代码,作任何操作,插件会自己根据.tpsheet,将刚才打包好放进入工程的大图自动裁剪成小图,如下图,打图集点开

我们只需像使用单独小图一样,将图集里的小图拖进Source Image里即可。这时我们还只能在编辑器里设置使用图集。

3,我们还需要在程序中 动态加载图集并使用图集里的小图,才算是完整的。unity3d 并没有明确api说明我们如何用这种图集,而常用Resources.Load()加载只能返回单独的一个图片纹理,所以我们用另一个方法 Resources.LoadAll();加载整一张图集,此方法会返回一个Object[],里面包含了图集的纹理 Texture2D和图集下的全部Sprite,所以我们就可以根据object 的类型和名字找到我们需要的某张小图片。

4.下面写了一个图集纹理的管理类,去统一管理加载,是一个单例类,找个不被销毁的GameObject绑定就行, 代码比较简单,用一个Dictionary按图集的路径过key将加载过的图集缓存起来,需要时再由外部删除掉,下面是代码

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
//纹理图集加载管理
public class PPTextureManage : MonoBehaviour {
	private static GameObject m_pMainObject;
	private static PPTextureManage m_pContainer = null;
	public static PPTextureManage getInstance(){
		if(m_pContainer == null){
			m_pContainer = m_pMainObject.GetComponent<PPTextureManage> ();
		}
		return m_pContainer;
	}
	private Dictionary<string, Object[]> m_pAtlasDic;//图集的集合
	void Awake(){
		initData ();
	}
	private void initData(){
		PPTextureManage.m_pMainObject = gameObject;
		m_pAtlasDic = new Dictionary<string, Object[]> ();
	}
	// Use this for initialization
	void Start () {
	}
	//加载图集上的一个精灵
	public Sprite LoadAtlasSprite(string _spriteAtlasPath,string _spriteName){
		Sprite _sprite = FindSpriteFormBuffer (_spriteAtlasPath,_spriteName);
		if (_sprite == null) {
			Object[] _atlas = Resources.LoadAll (_spriteAtlasPath);
			m_pAtlasDic.Add (_spriteAtlasPath,_atlas);
			_sprite = SpriteFormAtlas (_atlas,_spriteName);
		}
		return _sprite;
	}
	//删除图集缓存
	public void DeleteAtlas(string _spriteAtlasPath){
		if (m_pAtlasDic.ContainsKey (_spriteAtlasPath)) {
			m_pAtlasDic.Remove (_spriteAtlasPath);
		}
	}
	//从缓存中查找图集,并找出sprite
	private Sprite FindSpriteFormBuffer(string _spriteAtlasPath,string _spriteName){
		if (m_pAtlasDic.ContainsKey (_spriteAtlasPath)) {
			Object[] _atlas = m_pAtlasDic[_spriteAtlasPath];
			Sprite _sprite = SpriteFormAtlas(_atlas,_spriteName);
			return _sprite;
		}
		return null;
	}
	//从图集中,并找出sprite
	private Sprite SpriteFormAtlas(Object[] _atlas,string _spriteName){
		for (int i = 0; i < _atlas.Length; i++) {
			if (_atlas [i].GetType () == typeof(UnityEngine.Sprite)) {
				if(_atlas [i].name == _spriteName){
					return (Sprite)_atlas [i];
				}
			}
		}
		Debug.LogWarning ("图片名:"+_spriteName+";在图集中找不到");
		return null;
	}
}

 


5,代码使用示例

 Sprite _sprite = PPTextureManage.getInstance().LoadAtlasSprite("common/game/CommPackAl

--------------------- 本文来自 cjsen 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/cjsen/article/details/52487706?utm_source=copy

2017-09-14 22:13:08 liang_704959721 阅读数 16574
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

现在Unity中使用UGUI实现UI的越来越多,我们项目也才4.6.1升级到5.6.3对项目全面升级。5.xUGUI已经非常成熟,各种资料各种效果都非常多。由于之前一直习惯用NGUI在使用UGUI还是比较顺利的,都是一个作者写的都是控件式开发,唯一不爽的一点就是UGUI的图集管理。

习惯了NGUI的图集管理,在使用UGUI的时候也想把他们打包到一张图集上,查看了UGUI的打包方式,本以为和NGUI一样可以可以把分散的小图打到一张大图上。一直是这么理解的打包图集。




在打包工具中是一张图片,UGUI这样打包的优势是在使用这些图片的时候他们的DrawCalls就不会在增加了,我看别的有可以打到一张大图的,不知道为什么我这里没有成功,我unity版本是5.6.3f

自带的打包工具没有满足要求,寻找其他解决方案,在网上找到了TexturePackerGUI这个工具,看到TexturePackerGUI支持Unity


可以直接把需要打包的图片放到软件中进行打包组合,这里不涉及效率问题单纯只是进行打包。



这样就可以打包到一张图片上来,当然你也可以进行一下其他的设置,图片格式等等。

NGUI我们可以直接遍历图集获取到里面的全部子图片,直接支持图集类获取到。使用UGUI也可以获取到全部子图片但是需要自己写一个方案实现。

    public Image btn;
  
    void Start () {
        string str = "text";//Resources文件夹下面的名字
        //SpriteRenderer[] sp = Resources.LoadAll<SpriteRenderer>(str);
        Sprite[] sprite = Resources.LoadAll<Sprite>(str);
        foreach (var item in sprite)
        {
            print(item.name);
            
        }
        btn.sprite = sprite[2];
    }
这样就和NGUI比较相似了,可以进行图集打包和获取全部图集,这样做还可以优化图集的大小。


2018-08-09 17:57:21 cyf649669121 阅读数 3480
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

前言:

之前看到一个 小米超神写的关于Moba UI的优化(地址:https://zhuanlan.zhihu.com/p/38004837),由于自己项目也是Moba,觉得很实用, 所以决定偷过来试试。首先 肯定是要打动态图集,把各种散图合在一起肯定是减少DrawCall的重要途径。下面就开始慢慢学习吧。

涉及到一个开源项目:https://github.com/DaVikingCode/UnityRuntimeSpriteSheetsGenerator

 

正文:

1、下载项目

看起来需要阅读的部分不很多。

在他的Demo里面有个RectanglePacking的场景,打开来就是所需要的动态图集的打包算法;而AssetPacker的场景则是实现的效果。在 运行之后可以看到, 程序自动把多张图片进行了合并并用于UGUI,正是我们 想要的效果:

 

2、API使用说明

在AssetPacker的场景里面,在Demo的GameObject上挂载的一个 脚本AssetPacker就是打动态图集的脚本了。

这个打包 脚本的工作流程,先是用代码赋值需要打包的图片(比如以.png结尾的文件),由代码进行打包。可以在 属性面板上赋值打包完成的回调函数,也可以在代码里面写。

UseChahe:勾选之后会在项目的persistentDataPath下生成图集的缓存,包含一张大图集和对应的序列化文件(存储各个图片的UV,以Json格式存储),在下次使用的时候,如果ChcheName 和 CacheVersion一样的,则会使用老图,不会新生成。不勾选则不会生成,可以理解为释放掉之后就没有了。

CacheName:缓存的名字;

ChcheVersion:缓存的版本;

DeletePreviousCacheVersion: 是否删除旧版本的缓存。

其示例的动态图集的打包方法如下:

        //将指定文件夹的图片复制到项目文件夹下面;
        CopyPasteFoldersAndPNG(Application.dataPath + "/RuntimeSpriteSheetsGenerator/Demos/Sprites", Application.persistentDataPath);
        //获取所有需要打图集的文件路径;
        string[] files = Directory.GetFiles(Application.persistentDataPath + "/Textures", "*.png");
        //动态图集工具
        assetPacker = GetComponent<AssetPacker>();
        //设置回调;
        assetPacker.OnProcessCompleted.AddListener(LaunchAnimations);
        //设置需要打包的图片;
        assetPacker.AddTexturesToPack(files);
        //开始打图集;
        assetPacker.Process();

在其图集打完之后,会自动调用回调函数;关于打完图集之后,精灵图片的获取有两种方式:


        //获取以 "walking" 开始的图片数组;
        Sprite[] sprites = assetPacker.GetSprites("walking");
        //获取名字为 "waling0001" 的单张图片;
        Sprite sprite = assetPacker.GetSprite("waling0001");

当动态图集使用完毕之后,就可以将图集清空,直接将AssetPacker挂载的GameObject删除,他会自己调用其Dispose接口进行释放。当然, 我们也可以自己写个方法进行动态图集的释放。

 

3、插件移植

将以下6个脚本复制到项目中的一个新文件夹就可以使用了;

 

4、准备工作;

在使用动态图集之前,先要有一些准备工作。因为我现在的项目已经有UI界面了,引用了 各种各样的图片,现在需要把这些图片都标记出来,在运行时进行打包。这个自动打包的脚本需要的是文件路径,所以需要获得所有需要的文件路径。当然一个一个写太费劲了,所以我们需要用脚本来处理。

首先写 一个脚本挂载在所有的Image上,标记他们,设置为目标图片。他们身上挂载的Sprite会在程序设定 的时间运行后达成一张图,之后 再返回赋值到这些对应的Image上,从而达到减少DrawCall的目的。

    using UnityEngine;
    using UnityEngine.UI;

    /// <summary>
    /// 动态图集的目标图片;
    /// </summary>
    [RequireComponent(typeof(Image))]
    public class AssetPackerTargetImage : MonoBehaviour
    {
        /// <summary>
        /// 目标指引的图片;
        /// </summary>
        internal Image TargetImage;

        /// <summary>
        /// 在动态图集中的机灵图片;
        /// </summary>
        [HideInInspector]
        public Sprite AssetSprite;

        /// <summary>
        /// 图集标签名字;
        /// </summary>
        public string PakerTagName="Default";

        /// <summary>
        /// 精灵图片名字;
        /// </summary>
        public string SpriteName;

        /// <summary>
        /// 精灵图片路径;
        /// </summary>
        public string SpritePath;

        private void Start()
        {
            Init();
        }

        bool IsInited = false;

        /// <summary>
        /// 初始化
        /// </summary>
        public void Init()
        {
            if (IsInited) return;
            IsInited = true;
            //属性获取;
            TargetImage = GetComponent<Image>();
            SpriteName = TargetImage.sprite != null ? TargetImage.sprite.name : "White";
        }

        /// <summary>
        /// 设置成一张新的图片;
        /// </summary>
        /// <param name="aSp"></param>
        public void SetNewSprite(Sprite aSp)
        {
            Init();
            AssetSprite = aSp;
            TargetImage.sprite = aSp;
            SpriteName = aSp != null ? aSp.name : "";
        }


#if UNITY_EDITOR

        /// <summary>
        /// 在编辑器下的路径位置;
        /// </summary>
        [ContextMenuItem("初始化", "InitInEditorMode")]
        public string InEditorPath;

        /// <summary>
        /// 在编辑器模式下获取文件路径;
        /// </summary>
        [ExecuteInEditMode]
        public void InitInEditorMode()
        {
            //获取路径;
            Sprite mSp = GetComponent<Image>().sprite;
            InEditorPath = UnityEditor.AssetDatabase.GetAssetPath(mSp).Substring(6);
            SpritePath = InEditorPath;
            InEditorPath = Application.dataPath + InEditorPath;
            Debug.Log("获取到路径:" + InEditorPath);
            SpriteName = mSp.name;
        }

#endif
    }

这个脚本可以针对某个Image单独编辑,但是这样还是太麻烦了。所以需要一个工具来对所有的这些目标图片进行统一编辑。值得注意的是,有的作为背景使用的Image其本身没有图片,其Sprite设置为null,但是这仍然让Unity认为是使用了一个新的图集,从而增加DrawCall,所以需要在设置的时候将所有设置为Null的Image的Sprite设置为一张白图。这种大家自己随便用 画图软件框几个像素的白色图片就可以了。

    using UnityEngine;
    using UnityEngine.UI;


    /// <summary>
    /// 动态图集的辅助工具;
    /// </summary>
    public class AssetPackerInEditorHelper : MonoBehaviour
    {
#if UNITY_EDITOR

        /// <summary>
        /// 默认图片;
        /// </summary>
        [ContextMenuItem("自动设置", "AutoSetAllTargetImageAndInit")]
        public Sprite DefaultSprite;

        /// <summary>
        /// 图集标签名字;
        /// </summary>
        [ContextMenuItem("设置子对象名字", "SetAllName")]
        public string PakerTagName = "Default";

        /// <summary>
        /// 所有的指向图片;
        /// </summary>
        [ContextMenuItem("初始化所有", "InitAllTargetImage")]
        public AssetPackerTargetImage[] ArrAllTargetImage;

        /// <summary>
        /// 初始化所有的目标图片;
        /// </summary>
        [ExecuteInEditMode]
        void InitAllTargetImage()
        {
            //搜索中包含未激活的物品;
            ArrAllTargetImage = transform.GetComponentsInChildren<AssetPackerTargetImage>(true);
            for (int i = 0; i < ArrAllTargetImage.Length; i++)
            {
                //初始化所有;
                ArrAllTargetImage[i].InitInEditorMode();
            }
        }

        /// <summary>
        /// 自动设置所有的目标图片,并且初始化;
        /// </summary>
        [ExecuteInEditMode]
        void AutoSetAllTargetImageAndInit()
        {
            //获取所有图片;
            Image[] mArrImage = transform.GetComponentsInChildren<Image>(true);
            for (int i = 0; i < mArrImage.Length; i++)
            {
                //判定图片,如果是空,则设置为默认图片;
                if (mArrImage[i].sprite == null) mArrImage[i].sprite = DefaultSprite;
                //之后进行组件处理;
                if (mArrImage[i].GetComponent<AssetPackerTargetImage>() != null) continue;
                mArrImage[i].gameObject.AddComponent<AssetPackerTargetImage>();
            }
            //搜索中包含未激活的物品;
            ArrAllTargetImage = transform.GetComponentsInChildren<AssetPackerTargetImage>(true);
            for (int i = 0; i < ArrAllTargetImage.Length; i++)
            {
                //初始化所有;
                ArrAllTargetImage[i].InitInEditorMode();
                ArrAllTargetImage[i].PakerTagName = PakerTagName;
            }
        }

        /// <summary>
        /// 一次性设置所目标图片的名字;
        /// </summary>
        [ExecuteInEditMode]
        void SetAllName()
        {
            for (int i = 0; i < ArrAllTargetImage.Length; i++)
            {
                //初始化所有;
                ArrAllTargetImage[i].PakerTagName = PakerTagName;
            }
        }
#endif
    }

好了,有了这两个工具, 就可以进行图集打包了。

我们在编辑器下,在需要管理的图片根节点挂载脚本:AssetPackerInEditorHelper,选定好 默认图片之,在默认图片上右击自动设置就可以进行目标图片的标记了:

 

可以看到还是有很多图片需要设置的。到这里我们的准备工作就完成了。

 

5、替换成单图;

此外,有的项目中(比如我现在的项目)的UI资源并不是以一张一张的散图的形式存在在,而是在一开始就用如TexturePaker这样的第三方 工具打包成了一个完成的图集。所以在真正开始之前,还需要将在图集中的图换回散图。

同样,我在AssetPackerInEditorHelper中写一段代码进行批量替换。由于是在编辑器下运行,所以代码可以随意一点,不用考虑一些性能问题了。

        #region 图集替换成单图;

        /// <summary>
        /// 目标单图的文件夹;
        /// </summary>
        [ContextMenuItem("替换成单图", "ReplaceSpriteInPackerBySinglePacke")]
        public string TargetSingleSpriteFloder = "Resources/";

        /// <summary>
        /// 进行替换:
        /// </summary>
        [ExecuteInEditMode]
        void ReplaceSpriteInPackerBySinglePacke()
        {
            for (int i = 0; i < ArrAllTargetImage.Length; i++)
            {
                //获取用来替换的Sprite;
                Image tempImg = ArrAllTargetImage[i].GetComponent<Image>();
                string name = tempImg.sprite.name;
                string Path = TargetSingleSpriteFloder + name;
                Sprite SingleSP = Resources.Load<Sprite>(Path);
                //替换;
                if (SingleSP == null)
                {
                    Debug.LogError("找不到单图:" + Path );
                    Debug.LogError("在:" + ArrAllTargetImage[i].gameObject.name);
                    continue;
                }
                tempImg.sprite = SingleSP;
                ArrAllTargetImage[i].InitInEditorMode();
            }
        }

        #endregion

 

6、使用动态图集;

准备工作完成了,终于到了打包了。我在某个特定时间,比如开始战斗场景的时候触发打包操作,代码如下: 

        /// <summary>
        /// 所有的已经打包的图集;
        /// </summary>
        Dictionary<string, AssetPacker> mDicAllAssetPacker = new Dictionary<string, AssetPacker>();

        /// <summary>
        /// 所有的路径;
        /// </summary>
        Dictionary<string, List<string>> mDicAllPath = new Dictionary<string, List<string>>();
        
        /// <summary>
        /// 获取一个路径列表;
        /// </summary>
        /// <param name="tag"></param>
        /// <returns></returns>
        List<string> GetPathList(string tag)
        {
            if (mDicAllPath.ContainsKey(tag))
            {
                return mDicAllPath[tag];
            }
            List<string> mList = new List<string>();
            mDicAllPath.Add(tag, mList);
            return mList;
        }

        /// <summary>
        /// 存路径,同时去重;
        /// </summary>
        /// <param name="mList"></param>
        /// <param name="NewPath"></param>
        void SavePathToListWithoutRepeat(List<string> mList, string NewPath)
        {
            //跳过重复;
            for (int i = 0; i < mList.Count; i++)
            {
                if (mList[i] == NewPath) return;
            }
            mList.Add(NewPath);
        }

        /// <summary>
        /// 打包动态图集,只有Tag相同才会被打包;
        /// </summary>
        /// <param name="mList"></param>
        /// <param name="tag">筛选参数</param>
        public void CreateAssetPacker(List< AssetPackerTargetImage> mList, string tag)
        {
            if (mList.Count == 0) return;
            //获取所有的路径;
            List<AssetPackerTargetImage> mListTarget = new List<AssetPackerTargetImage>();
            List<string> mListPath = GetPathList(tag);
            for (int i = 0; i < mList.Count; i++)
            {
                if (!string.IsNullOrEmpty(mList[i].SpritePath) && mList[i].PakerTagName == tag)
                {
                    //替换为绝对路径;
                    SavePathToListWithoutRepeat(mListPath, Application.dataPath + mList[i].SpritePath);
                    mListTarget.Add(mList[i]);
                }
            }
            if (mListPath.Count == 0) return;
            //之后开始打包图集;
            AssetPacker ap = new GameObject("AssetPacker:" + tag).AddComponent<AssetPacker>();
            ap.useCache = false;
            ap.cacheName = tag;
            ap.cacheVersion = 1;
            //设定参数;
            mDicAllAssetPacker.Add(tag, ap);
            ap.OnProcessCompleted.AddListener(delegate ()
            {
                //设置图片;
                for (int i = 0; i < mListTarget.Count; i++)
                {
                    mListTarget[i].SetNewSprite(ap.GetSprite(mListTarget[i].SpriteName));
                }
#if UNITY_EDITOR
                Debug.Log("动态图集"+ tag + "更换完成。" + "数量:" + mListTarget.Count);
#endif
                OneTagPackEnd(tag);
            });
            ap.AddTexturesToPack(mListPath);
            //开始打图集;
            ap.Process();
            Debug.Log("开始打图集:" + tag + "数量:" + mListPath.Count);
        }

        /// <summary>
        /// 一个图集打包完成;
        /// </summary>
        /// <param name="tag"></param>
        void OneTagPackEnd(string tag)
        {
            mListAllTagNotEnd.Remove(tag);
            if (mListAllTagNotEnd.Count == 0)
            {
                //此时已经全部打包完成了;
                Debug.Log("全部图集打包完成!");
            }
        }

        /// <summary>
        /// 获取一张图片;
        /// </summary>
        /// <param name="tag"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public Sprite GetSprite(string tag,string name)
        {
            if (mDicAllAssetPacker.ContainsKey(tag))
            {
                var Pa = mDicAllAssetPacker[tag];
                return Pa.GetSprite(name);
            }
            return null;
        }

这样就能让打包 完成的时候自动替换,接下来只要在项目需要的地方一步一步替换,那么项目就会优化很多。当然,这样优化的代价是以牺牲加载速度为前提的。不过考虑到现在的项目以效率、性能优先,所以加载耗时稍微长一点也是可以接受的。反正优化是永无止境的工作,这仅仅是一个开始。

 

 后记:

除了正文的工作之外,发现原来DaVikingCode.AssetPacker的代码也还有几处需要修改。其中一处是在AssetPacker的OnProcessCompleted属性,需要修改如下:

 public UnityEvent OnProcessCompleted = new UnityEvent();

以前是赋值为空,会导致用代码创建的时候出现空指针;

另外一处在createPack方法,将其中的  foreach (TextureToPack itemToRaster in itemsToRaster)循环语句修改如下:

            //改成For循环减少GC;
            for (int i = 0; i < itemsToRaster.Count; i++)
            {
                var itemToRaster = itemsToRaster[i];

                WWW loader = new WWW("file:///" + itemToRaster.file);
                yield return loader;
                //打印路径以方便错误排查;
                if (string.IsNullOrEmpty(loader.error))
                {
                    textures.Add(loader.texture);
                    images.Add(itemToRaster.id);
                }
                else
                {
                    Debug.LogWarning("路径有误:" + loader.url);
                }
            }

打印其加载出错的路径,方便进行错误排查;