上篇文章说了UGUI上图集的使用,这一篇继续看看SpritePacker怎么打包图集。我觉得我们有必要对比一下NGUI的图集,NGUI在打包图集的时候图集的默认格式是RGBA32,也就是支持带透明通道的图片,这样一张1024的图集也就是4M内存。为了优化图集,我们可以选择把带透明通道图片 和 不带透明通道的图片分开打图集,这样可以减少内存的占用量。
然而着一切的一切在NGUI上都需要手动操作,而SpritePacker则全自动完成。Sprite上的Packing Tag 同一标识的图片UGUI会把相同图片格式的图片打包成同一图集。如下图所示,MomoAtals和RUORUOAtlas就是Packing Tag的标识符,那么此时根据这两个标识符SpritePacker将打出两个图集出来。 因为MomoAtlas这些图片中,一部分是RGBA32格式,还有一部分是ETC 4bits格式,那么MomoAtlas将被在分成两个图集,就是尾缀带Group的。
打包Sprite Packer有两个打包模式,如下图所示分别是DefaultPackerPolicy和TightPackerPolicy。
DefaultPackerPolicy:是默认的打包方式,也是矩形打包方式。他会把所有的小图按照矩形的方式来排列,如果宽高不一样的图片,它们会自动补起。
TightPackerPolicy:是紧密打包方式,也就是尽可能的把图片都打包在图集上,这种方式要比DefaultPackerPolicy打包的图片更多一些,也就是更省空间。
根据图集的布局可以清晰的看到TightPackerPolicy图集更加紧密。
DefaultPackerPolicy模式打包是unity所推荐的,理论上所有图集都可以使用DefaultPackerPolicy来完成打包。还有一个特性就是可以让图集中某几张图片单独采取DefaultPackerPolicy或者TightPackerPolicy的方式。
如下图所示,比如当前打包图集是DefaultPackerPolicy 那么小图中[TIGHT]开头的就表示单独这张图采用TightPackerPolicy打包模式。
如下图所示,比如当前打包图集是TightPackerPolicy 那么小图中[RECT]开头的就表示单独这张图采用DefaultPackerPolicy打包模式。
Unity只提供了这两种图集打包方法。假如我想自定义打包方式咋办?比如我想设置图片打包格式,或者图集大小等等怎么办?把如下代码放在Editor文件夹下, 在代码里面就可以设置图集的属性了。
using System;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.Sprites;
using System.Collections.Generic;
// DefaultPackerPolicy will pack rectangles no matter what Sprite mesh type is unless their packing tag contains "[TIGHT]".
class DefaultPackerPolicySample : UnityEditor.Sprites.IPackerPolicy
{
protected class Entry
{
public Sprite sprite;
public AtlasSettings settings;
public string atlasName;
public SpritePackingMode packingMode;
}
public virtual int GetVersion() { return 1; }
protected virtual string TagPrefix { get { return "[TIGHT]"; } }
protected virtual bool AllowTightWhenTagged { get { return true; } }
public void OnGroupAtlases(BuildTarget target, PackerJob job, int[] textureImporterInstanceIDs)
{
List<Entry> entries = new List<Entry>();
foreach (int instanceID in textureImporterInstanceIDs)
{
TextureImporter ti = EditorUtility.InstanceIDToObject(instanceID) as TextureImporter;
TextureImportInstructions ins = new TextureImportInstructions();
ti.ReadTextureImportInstructions(ins, target);
TextureImporterSettings tis = new TextureImporterSettings();
ti.ReadTextureSettings(tis);
Sprite[] sprites = AssetDatabase.LoadAllAssetRepresentationsAtPath(ti.assetPath).Select(x => x as Sprite).Where(x => x != null).ToArray();
foreach (Sprite sprite in sprites)
{
//在这里设置每个图集的参数
Entry entry = new Entry();
entry.sprite = sprite;
entry.settings.format = ins.desiredFormat;
entry.settings.usageMode = ins.usageMode;
entry.settings.colorSpace = ins.colorSpace;
entry.settings.compressionQuality = ins.compressionQuality;
entry.settings.filterMode = Enum.IsDefined(typeof(FilterMode), ti.filterMode) ? ti.filterMode : FilterMode.Bilinear;
entry.settings.maxWidth = 1024;
entry.settings.maxHeight = 1024;
entry.atlasName = ParseAtlasName(ti.spritePackingTag);
entry.packingMode = GetPackingMode(ti.spritePackingTag, tis.spriteMeshType);
entries.Add(entry);
}
Resources.UnloadAsset(ti);
}
// First split sprites into groups based on atlas name
var atlasGroups =
from e in entries
group e by e.atlasName;
foreach (var atlasGroup in atlasGroups)
{
int page = 0;
// Then split those groups into smaller groups based on texture settings
var settingsGroups =
from t in atlasGroup
group t by t.settings;
foreach (var settingsGroup in settingsGroups)
{
string atlasName = atlasGroup.Key;
if (settingsGroups.Count() > 1)
atlasName += string.Format(" (Group {0})", page);
job.AddAtlas(atlasName, settingsGroup.Key);
foreach (Entry entry in settingsGroup)
{
job.AssignToAtlas(atlasName, entry.sprite, entry.packingMode, SpritePackingRotation.None);
}
++page;
}
}
}
protected bool IsTagPrefixed(string packingTag)
{
packingTag = packingTag.Trim();
if (packingTag.Length < TagPrefix.Length)
return false;
return (packingTag.Substring(0, TagPrefix.Length) == TagPrefix);
}
private string ParseAtlasName(string packingTag)
{
string name = packingTag.Trim();
if (IsTagPrefixed(name))
name = name.Substring(TagPrefix.Length).Trim();
return (name.Length == 0) ? "(unnamed)" : name;
}
private SpritePackingMode GetPackingMode(string packingTag, SpriteMeshType meshType)
{
if (meshType == SpriteMeshType.Tight)
if (IsTagPrefixed(packingTag) == AllowTightWhenTagged)
return SpritePackingMode.Tight;
return SpritePackingMode.Rectangle;
}
}
如下图所示,SpritePacker就多出了一个打包图集的选项。
有可能我们会同时把很多图片都拖入unity中,虽然可以全选在设置图片的pack tag,但是我觉得最好全自动完成,比如我们把图片放在不同的文件夹下,那么文件夹的名子就可以用做Atals的名子。最后在分享一条这样的脚本。
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;
public class Post : AssetPostprocessor
{
void OnPostprocessTexture (Texture2D texture)
{
string AtlasName = new DirectoryInfo(Path.GetDirectoryName(assetPath)).Name;
TextureImporter textureImporter = assetImporter as TextureImporter;
textureImporter.textureType = TextureImporterType.Sprite;
textureImporter.spritePackingTag = AtlasName;
textureImporter.mipmapEnabled = false;
}
}
好了,今天比较高兴,入手了ipad air2 嘿嘿嘿~~ 欢迎大家在下面给我留言我们一起讨论。
- 本文固定链接: https://www.xuanyusong.com/archives/3315
- 转载请注明: 雨松MOMO于 雨松MOMO程序研究院 发表








请问打包后,整体的大图去哪儿了。。。。
在一个我们不能读取的目录下。
原来如此,那是不是我们还是用原来的碎土,但是其实drawcall已经减少了,另外drawcall是不是已经改成batches了?
请问下,用spritepacker 打包出来的 atlas ,我在哪里能修改它的shader? 比如我想换一种 支持alphablend 的shader
MOMO大大,如何让图集只根据packingTag来打包,忽略其它影响如:格式等
可以用文件夹名字。。
DefaultPackerPolicy 模式打包成2个图集,用你这个DefaultPackerPolicySample 可以打包成1个图集,但是drawcall和前面打包方式形成的一样。。。请问这是为什么啊
是我的错,1个图集真的 能减少drawcal。。。请问如何自定义图集的大小啊,entry.settings.maxWidth = 2048;entry.settings.maxHeight = 2048;写完之后没反应,求解
是这么写, 但是有反应的。。。
TextureImportInstructions 在5.0的时候被干掉了,不知道有没有其他方法代替
注释掉,,entry.settings.format = TextureFormat.ARGB32; entry.settings.colorSpace = ColorSpace.Linear;该删的就删,看着办
话说 这个类 在哪 TextureImportInstructions
图集预览中,那些拖得很长的像素,是啥?这些东西貌似是会让图集很大,是否做图时候严格要求,周边必须留出空白才能避免??
每張圖的filter mode設為point就不會了
hi,我遇到一个问题,最大打包图片是2048*2048,我打包64张256*256的小图,结果,打包出两张图,而有些图的边缘像素拉伸了,你这篇文章的截图,也出现这个问题,觉得很困惑,求解
關於图的边缘像素拉伸,只要每張圖的filter mode設為point就不會了
momo大大,关于sprite打包成图集的问题。我这边的测试中发现搜索一张sprite贴图的时候没有搜到其它相同tag的依赖,那样如果我想热更部分sprite贴图的话,感觉好像会有问题
MOMO大大问一下喔当我将单张图设定为16BIT…内存下方显是占1.5M.而后组图此时下方改成显示2.9M(下方图解析变32BIT.但组图是16BIT)请问到底在实际游玩是占多少?
MOMO大大问一下喔UGUI的DrawCall是一个UI对象一个,这样太大了啦.不知是否有像NGUI一样算法依图集来算。另外为何打包ㄉ图集将我想打包的图分2张(图的Packing tag都同样.一张图集打出来大小512*512).是否我有办法调整图集打包ㄉ大小.试或将图包在一包
你的sprite没有设置标签(Packing Tag)每张图打一个图集,当然一个UI对象一个DC了……相邻的元素如果是同一个图集(且中间没有某些必须提升DC的组件比如Mask),是只占一个DC的。文本也算图集。
文本的字体也算图集,打错。
MOMO大大看完了但我还是不知道怎么打包UGUI图集,请问MOMO大大是否有教如何打包UGUI图集的教学参考数据………..谢谢…我的QQ:1453701424
终于会拼了.指是拼好的图集在哪ㄋ
博主你好,有个问题请教一下。你博文最后分享的代码(处理Texture2D为Sprite)是正常的吗?因为我这里启用这段代码导入的Sprite是显示不正常的。Sprite的大小被调整成了2的幂。请问这个要怎么解决呢??
sprite 默认是2的幂 最好2的幂 因为只有 2的幂 图片才能压缩。。。
既然TightPackerPolicy更省空间,为何还需要DefaultPakcerPolicy呢?
博主 能不能分享一下你这个讲解点的Package呀 445601465@qq.com 谢谢
请问博主有没有遇到过同一图集渲染时产生多个drawcall的情况?在window sprite packer中看到是在同一图集内,没在不同的group中,图片也没在resource目录下
我用的是mutiple
好吧 渲染顺序对drawcall有影响 打包之后内存会*2 是正常现象吗?
不同图集 如果aba 叠层就会增加drawcall
嗯 那内存中除了有图集的内存还有原始图片的内存 这种能不能避免 还是只有认了?
如果要在游戏中频繁更改图片sprite,只能把sprite放在resource底下,但是resource底下的sprite,packer是不会进行打包的,雨哥这样怎么办?
参另一篇博客,在Resources中创建一个预设引用外面的图片。这是我写的,仅供参考。—————————————————————————————————–预设制作脚本using UnityEngine;using UnityEditor;using System.IO;/* 本功能逻辑: * 1.遍历“ResourcesHW/UI”下的每个文件夹(如果是MainUI则再遍历其子目录),如果存在DynSprite文件夹, * 则进入2,否则提示没有DynSprite文件夹。 * 2.创建一个go,添加AtlasMap脚本,把DynSprite文件夹下所有的Sprite都添加到AtlasMap的spriteList中, * 若DynSprite下没有任何Sprite,则警告对应信息(是不是忘了添加Sprite进去?)。 * 3.将go创建为对应预设,并放置到“Resources/UI”下的对应文件夹处,预设名字为“文件夹名+DynSprite”,然后将go删除。 */ [MenuItem(“HW Tools/AtlasMaker”)] static void MakeAtlas() { string targetDir = Application.dataPath + “/Resources/UI”; if(!Directory.Exists(targetDir)) { Directory.CreateDirectory(targetDir); } DirectoryInfo fromDirInfo = new DirectoryInfo(Application.dataPath + “/ResourcesM/UI”); foreach (DirectoryInfo dirInfo in fromDirInfo.GetDirectories()) { if (dirInfo.Name == “MainUI”)// 因为MainUI下面还有很多子级别,子级别也有对应的动态图集列表// { foreach(DirectoryInfo mainUIChildDir in dirInfo.GetDirectories()) { MakeOnePrefab(mainUIChildDir, fromDirInfo, targetDir + “/MainUI”); } } else { MakeOnePrefab(dirInfo, fromDirInfo, targetDir); } } } static void MakeOnePrefab(DirectoryInfo dirInfo, DirectoryInfo fromDirInfo, string targetDir) { string fieldName = dirInfo.Name; FileInfo[] allPngFiles = null; bool hasFindDynSpriteField = false; // 遍历子文件夹找到DynSprite文件夹// foreach (DirectoryInfo childDir in dirInfo.GetDirectories()) { if (childDir.Name == “Sprite_Dyn”) { allPngFiles = childDir.GetFiles(“*.png”, SearchOption.AllDirectories); hasFindDynSpriteField = true; break; } } if (hasFindDynSpriteField) { 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}/DynSprite”.Do you forget to add needed sprite there?”, shortPath, fieldName)); } else { GameObject go = new GameObject(fieldName); AtlasMap am = go.AddComponent(); // 如果“Resources/UI”下没有和“ResourcesHW/UI对应的文件夹”则创建一个// string prefabParentFieldPath = string.Format(“{0}/{1}”, targetDir, fieldName); 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(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; } am.AddSprite(sprite); } // 在对应文件夹上生成预设// string prefabAllPath = string.Format(“{0}/{1}DynSprite.prefab”, prefabParentFieldPath, fieldName); string prefabPath = prefabAllPath.Substring(prefabAllPath.IndexOf(“Assets”)); PrefabUtility.CreatePrefab(prefabPath, go); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); // 销毁go// GameObject.DestroyImmediate(go); } } else { string shortPath = fromDirInfo.FullName.Substring(fromDirInfo.FullName.IndexOf(“Assets”)); Debug.Log(string.Format(“There is no DynSprite field where path is “{0}/{1}”.Are you sure this UI needn’t DynSprite?”, shortPath, fieldName)); } }}——————————————————————————————————-AtlasMap脚本:using UnityEngine;using System.Collections;using System.Collections.Generic;/* * 用于存储制作好的动态图预设上的sprite信息 */public class AtlasMap : MonoBehaviour{ [HideInInspector][SerializeField] List spriteList = new List(); public Sprite GetSpriteByName(string name) { return spriteList.Find((Sprite sp) => { return sp.name == name; }); } public void AddSprite(Sprite sp) { spriteList.Add(sp); }}使用方法:/* * type指的是该UI所在界面对应在Scripts或ResourcesHW下的文件夹名字,比如创角选角的CSHero * name是所需sprite的名字 * extendedStr是路径扩展字符,比如MainUI路径下各个动态图集预设是在其各个子目录下的,所以需要额外的扩展路径MainUI */ public static Sprite GetSpriteByTypeAndName(string type, string spriteName, string extendedStr = null) { string goPath = “”; if(string.IsNullOrEmpty(extendedStr)) { goPath = string.Format(“UI/{0}/{0}DynSprite”, type); } else { goPath = string.Format(“UI/{1}/{0}/{0}DynSprite”, type, extendedStr); } GameObject go = Resources.Load(goPath); 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”, type)); return null; } AtlasMap am = go.GetComponent(); if(am == null) { Debug.LogError(string.Format(“There is no AtlasMap on prefab of {0}.Be sure MakeAtlasMaker work”, type)); return null; } return am.GetSpriteByName(spriteName); }
参另一篇博客,在Resources中创建一个预设引用外面的图片。这是我写的,仅供参考。—————————————————————————————————–预设制作脚本using UnityEngine;using UnityEditor;using System.IO;/* 本功能逻辑: * 1.遍历“ResourcesHW/UI”下的每个文件夹(如果是MainUI则再遍历其子目录),如果存在DynSprite文件夹, * 则进入2,否则提示没有DynSprite文件夹。 * 2.创建一个go,添加AtlasMap脚本,把DynSprite文件夹下所有的Sprite都添加到AtlasMap的spriteList中, * 若DynSprite下没有任何Sprite,则警告对应信息(是不是忘了添加Sprite进去?)。 * 3.将go创建为对应预设,并放置到“Resources/UI”下的对应文件夹处,预设名字为“文件夹名 DynSprite”,然后将go删除。 */ [MenuItem(“HW Tools/AtlasMaker”)] static void MakeAtlas() { string targetDir = Application.dataPath “/Resources/UI”; if(!Directory.Exists(targetDir)) { Directory.CreateDirectory(targetDir); } DirectoryInfo fromDirInfo = new DirectoryInfo(Application.dataPath “/ResourcesM/UI”); foreach (DirectoryInfo dirInfo in fromDirInfo.GetDirectories()) { if (dirInfo.Name == “MainUI”)// 因为MainUI下面还有很多子级别,子级别也有对应的动态图集列表// { foreach(DirectoryInfo mainUIChildDir in dirInfo.GetDirectories()) { MakeOnePrefab(mainUIChildDir, fromDirInfo, targetDir “/MainUI”); } } else { MakeOnePrefab(dirInfo, fromDirInfo, targetDir); } } } static void MakeOnePrefab(DirectoryInfo dirInfo, DirectoryInfo fromDirInfo, string targetDir) { string fieldName = dirInfo.Name; FileInfo[] allPngFiles = null; bool hasFindDynSpriteField = false; // 遍历子文件夹找到DynSprite文件夹// foreach (DirectoryInfo childDir in dirInfo.GetDirectories()) { if (childDir.Name == “Sprite_Dyn”) { allPngFiles = childDir.GetFiles(“*.png”, SearchOption.AllDirectories); hasFindDynSpriteField = true; break; } } if (hasFindDynSpriteField) { 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}/DynSprite”.Do you forget to add needed sprite there?”, shortPath, fieldName)); } else { GameObject go = new GameObject(fieldName); AtlasMap am = go.AddComponent(); // 如果“Resources/UI”下没有和“ResourcesHW/UI对应的文件夹”则创建一个// string prefabParentFieldPath = string.Format(“{0}/{1}”, targetDir, fieldName); 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(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; } am.AddSprite(sprite); } // 在对应文件夹上生成预设// string prefabAllPath = string.Format(“{0}/{1}DynSprite.prefab”, prefabParentFieldPath, fieldName); string prefabPath = prefabAllPath.Substring(prefabAllPath.IndexOf(“Assets”)); PrefabUtility.CreatePrefab(prefabPath, go); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); // 销毁go// GameObject.DestroyImmediate(go); } } else { string shortPath = fromDirInfo.FullName.Substring(fromDirInfo.FullName.IndexOf(“Assets”)); Debug.Log(string.Format(“There is no DynSprite field where path is “{0}/{1}”.Are you sure this UI needn’t DynSprite?”, shortPath, fieldName)); } }}——————————————————————————————————-AtlasMap脚本:using UnityEngine;using System.Collections;using System.Collections.Generic;/* * 用于存储制作好的动态图预设上的sprite信息 */public class AtlasMap : MonoBehaviour{ [HideInInspector][SerializeField] List spriteList = new List(); public Sprite GetSpriteByName(string name) { return spriteList.Find((Sprite sp) => { return sp.name == name; }); } public void AddSprite(Sprite sp) { spriteList.Add(sp); }}使用方法:/* * type指的是该UI所在界面对应在Scripts或ResourcesHW下的文件夹名字,比如创角选角的CSHero * name是所需sprite的名字 * extendedStr是路径扩展字符,比如MainUI路径下各个动态图集预设是在其各个子目录下的,所以需要额外的扩展路径MainUI */ public static Sprite GetSpriteByTypeAndName(string type, string spriteName, string extendedStr = null) { string goPath = “”; if(string.IsNullOrEmpty(extendedStr)) { goPath = string.Format(“UI/{0}/{0}DynSprite”, type); } else { goPath = string.Format(“UI/{1}/{0}/{0}DynSprite”, type, extendedStr); } GameObject go = Resources.Load(goPath); 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”, type)); return null; } AtlasMap am = go.GetComponent(); if(am == null) { Debug.LogError(string.Format(“There is no AtlasMap on prefab of {0}.Be sure MakeAtlasMaker work”, type)); return null; } return am.GetSpriteByName(spriteName); }
请问雨哥,以前用NGUI 可以用自带的字体生成工具,生成想到的字体文件,现在用UGUI如果想自己制作一些文字,用什么工具呢。
TTF不行吗? 我们项目就是用了两个TTF 。我觉得最好用TTF 不然多语言做起来就很麻烦了。。
因为TTF是黑白的,我想弄得是制作好的一些有色彩的字体,比如可以弄成 Fnt格式 使用 NGUI 生成 prefab包 来使用,但是现在不打算在用NGUI了,所以一直没找到类似的插件来使用
目前我还没找到那种工具可以做这个,但是你确定要用动态字体? 因为我们以前也是用动态字体的, 后来做多语言很麻烦就去掉了。。
恩是的啊,我自己做的一些小项目,用着这个字体的效果会好很多
TTF 难道不是动态字体?
ttf是动态字体。。 上面那哥们说想自己做字体包。。
我们买text mesh pro来用, 可以参考看看
很不错,看来U3D自己的图集方式比NGUI人性化多了
恩恩哈。
http://docs.unity3d.com/Manual/SpritePacker.html