UGUI的图集打包与工作原理,整整看了一天多,终于看明白了~晕~还是记录一下我研究的成果,也希望大家在下面给我留言我们一起讨论一下。
先说说UGUI的Atlas和NGUI的Atlas的区别,NGUI是必须先打出图集然后才能开始做界面。这一点很烦,因为始终都要去考虑你的UI图集。比如图集会不会超1024 ,图集该如何来规划等等。而UGUI的原理则是,让开发者彻底模糊图集的概念,让开发者不要去关心自己的图集。做界面的时候只用小图,而在最终打包的时候unity才会把你的小图和并在一张大的图集里面。然而这一切一切都是自动完成的,开发者不需要去care它。
如下图所示,Editor->Project Settings 下面有sprite packer的模式。Disabled表示不启用它,Enabled For Builds 表示只有打包的时候才会启用它,Always Enabled 表示永远启用它。 这里的启用它就表示是否将小图自动打成图集。
我的选项是Always Enabled 。因为开发的时候我们需要清楚的看到现在是几个Draw Call,从而才能优化小图。在最终打包的时候unity会自动构建大的图集,可是我开发的时候就想看图集会占几个Draw Call,这怎么办呢?如下图所示,首先将你的图片拖入unity中,将同一图集的所有图片的packing tag设置成一个名子即可。
注意你的图片不能放在Resources文件夹下面,Resources文件夹下的资源将不会被打入图集,切记(也就是在这里混淆了我很久)。然后在Windows->Sprite Packer 里,点击packer 在这里你就可以预览到你的图集信息。图集的大小还有图集的格式等等很多参数我们都是可以控制的,也可以通过脚本来设置。我在下一篇文章里详细说这个(请期待嘿嘿)。
图集的预览紧紧是让你看看你的图集大概张什么样子。那么我们的图集的这张图片保存在了哪里呢?它保存在和Assets文件夹同级的目录,Libary/AtlasCache里面。你不用管它,也不要删除它,就算你删除了也没用因为只要你打包,它就会生成并且会打到包中。
此时在Hierarchy视图中创建两个Image对象。如下图所示,我们可以清楚的看到此时我的draw call已经被合并成了1 。
这两个图片是我是在Editor模式下预先拖入Hierarchy视图中的,可是如果我想运行时根据图片的名子来动态创建精灵该如何?可是unity根本没有提供加载图集的方法,也没有提供加载图集上某个图片的方法。 因为UGUI就不像让开发者有图集的这个概念,可是我们肯定是要实现这个需求的。。怎么办呢?
第一个设想,先把散=小图打包成图集,然后再把所有散图拷贝在Resources文件夹下,这样运行时就能用Resources.load了。
第二个设想,还是先把小图打成图集,然后把所有小图关联在prefab上,拷贝在Resources文件夹下,这样运行时也能用Resources.load了。到底那个靠谱呢? 给大家看一个图大家就知道答案了。
如下图所示,打成图集的图片如果在放在Resources那么资源就变成双份了。。 所以我们只能把小图关联在Prefab上,把所有的Prefab放在Resources下面,这样就不占用多余的空间了。
好了,现在方法我们已经掌握,那么就开始写工具吧。如下图所示可以按文件夹分,每一个文件夹就是一个图集。然后每一张小图创建一个Prefab,Prefab的名子就起小图的名子,文件关联在Resources下面。
代码比较简单,我就不注释了。
[MenuItem ("MyMenu/AtlasMaker")]
static private void MakeAtlas()
{
string spriteDir = Application.dataPath +"/Resources/Sprite";
if(!Directory.Exists(spriteDir)){
Directory.CreateDirectory(spriteDir);
}
DirectoryInfo rootDirInfo = new DirectoryInfo (Application.dataPath +"/Atlas");
foreach (DirectoryInfo dirInfo in rootDirInfo.GetDirectories()) {
foreach (FileInfo pngFile in dirInfo.GetFiles("*.png",SearchOption.AllDirectories)) {
string allPath = pngFile.FullName;
string assetPath = allPath.Substring(allPath.IndexOf("Assets"));
Sprite sprite = Resources.LoadAssetAtPath<Sprite>(assetPath);
GameObject go = new GameObject(sprite.name);
go.AddComponent<SpriteRenderer>().sprite = sprite;
allPath = spriteDir+"/"+sprite.name+".prefab";
string prefabPath = allPath.Substring(allPath.IndexOf("Assets"));
PrefabUtility.CreatePrefab(prefabPath,go);
GameObject.DestroyImmediate(go);
}
}
}
然后是运行时的代码。
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class UIMain : MonoBehaviour {
void Start ()
{
CreatImage(loadSprite("image0"));
CreatImage(loadSprite("image1"));
}
private void CreatImage(Sprite sprite ){
GameObject go = new GameObject(sprite.name);
go.layer = LayerMask.NameToLayer("UI");
go.transform.parent = transform;
go.transform.localScale= Vector3.one;
Image image = go.AddComponent<Image>();
image.sprite = sprite;
image.SetNativeSize();
}
private Sprite loadSprite(string spriteName){
return Resources.Load<GameObject>("Sprite/" + spriteName).GetComponent<SpriteRenderer>().sprite;
}
}
因为这两个图是在同一个图集上,所以drawcall就是1了。这样我们就可以根据图片的名子来运行时加载图片了。
接下来就是Assetbundle了,如果我们的图集需要在线更新那该怎么办呢? 其实Assetbundle比Resources要更简单一些,无论如何我们要先开始打图集。
[MenuItem ("MyMenu/Build Assetbundle")]
static private void BuildAssetBundle()
{
string dir = Application.dataPath +"/StreamingAssets";
if(!Directory.Exists(dir)){
Directory.CreateDirectory(dir);
}
DirectoryInfo rootDirInfo = new DirectoryInfo (Application.dataPath +"/Atlas");
foreach (DirectoryInfo dirInfo in rootDirInfo.GetDirectories()) {
List<Sprite> assets = new List<Sprite>();
string path = dir +"/"+dirInfo.Name+".assetbundle";
foreach (FileInfo pngFile in dirInfo.GetFiles("*.png",SearchOption.AllDirectories))
{
string allPath = pngFile.FullName;
string assetPath = allPath.Substring(allPath.IndexOf("Assets"));
assets.Add(Resources.LoadAssetAtPath<Sprite>(assetPath));
}
if(BuildPipeline.BuildAssetBundle(null, assets.ToArray(), path,BuildAssetBundleOptions.UncompressedAssetBundle| BuildAssetBundleOptions.CollectDependencies, GetBuildTarget())){
}
}
}
static private BuildTarget GetBuildTarget()
{
BuildTarget target = BuildTarget.WebPlayer;
#if UNITY_STANDALONE
target = BuildTarget.StandaloneWindows;
#elif UNITY_IPHONE
target = BuildTarget.iPhone;
#elif UNITY_ANDROID
target = BuildTarget.Android;
#endif
return target;
}
如下图所示,我的assetbundle已经打出来了。
然后把UIMain.cs在改一改。
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class UIMain : MonoBehaviour {
AssetBundle assetbundle = null;
void Start ()
{
CreatImage(loadSprite("image0"));
CreatImage(loadSprite("image1"));
}
private void CreatImage(Sprite sprite ){
GameObject go = new GameObject(sprite.name);
go.layer = LayerMask.NameToLayer("UI");
go.transform.parent = transform;
go.transform.localScale= Vector3.one;
Image image = go.AddComponent<Image>();
image.sprite = sprite;
image.SetNativeSize();
}
private Sprite loadSprite(string spriteName){
#if USE_ASSETBUNDLE
if(assetbundle == null)
assetbundle = AssetBundle.CreateFromFile(Application.streamingAssetsPath +"/Main.assetbundle");
return assetbundle.Load(spriteName) as Sprite;
#else
return Resources.Load<GameObject>("Sprite/" + spriteName).GetComponent<SpriteRenderer>().sprite;
#endif
}
}
如下图所示,依然还是一个drawcall。
衷心希望有经验的朋友在留言处给我提提意见, 或者大家一起讨论讨论。。 我们共同为把NGUI干掉的目标而奋斗,嘻嘻。
- 本文固定链接: https://www.xuanyusong.com/archives/3304
- 转载请注明: 雨松MOMO于 雨松MOMO程序研究院 发表








做好的UI 的Prefab,怎么样导出后不带图片Sprite资源?
prefab 不是关联着 图集么?
UGUI这个导出UI的prefab的时候,他会自动把图集也打包进去的。怎么样做才能不打包进去?
最后关于AssetBundle那里不太清楚?是说:在打bundle的过程中,将图集信息包含进去,在主程序加载bundle的时候,在重新动态打图集 。还是说:在打包bundle的时候,其实就是把之前生成的图集直接打包进去?
在打包bundle的时候,其实就是把之前生成的图集直接打包进去?
如果我刻意少打几张贴图,那是不是应该重新生成图集?要不然bundle中岂不是存在无用资源了
你怎么会少打几张贴图? 如果你100个图的tag都一样, 那么你只打其中一个图 那么其实这100个整个图集都打进assetbundle 了。。只是你只能load 你打的这一张图。。
这样看来,自己还要经常维护自己的无用资源了,多谢,我再去试验一下
UGUI的问题在于,如果工程整体打包成AB的话,依赖关系难以处理。一个prefab依赖于一个图片的话,而这个图片是在一个图集中时。我们希望看到的依赖是prefab依赖于图集,但是结果却是prefab只依赖于图片,这样导致我们在打包时很难处理这种依赖关系,需要把图片当作一种特殊的资源进行处理。我也是在实验中,不知道各位是否已经有经验。
问题解决了吗,我遇到了与你同样的问题
问题解决了吗,我遇到了与你同样的问题
商城icon至少有上千个小贴图,NGUI时代可以直接做到N张1024*1024的大贴图中,做成N个atlas,打包成assetbundle做动态更新+动态加载,UGUI如果要实现这种需求应该如何处理?
一样的, 因为图集里面的某一个sprite变了, 那么在打assetbundle的时候就会整体变的。。
我的意思是,UGUI怎么在一个大贴图下找到我需要的sprite,如何自行打包成一个类似atlas的大图集,使用的时候又如何动态替换指定大图集上的sprite
一个sprite变,整个图集肯定是变的。我的意思是,我应该采用何种方案或者工具,把那些小贴图合并成一张大图集,做成外部资源,使用的时候又如何动态指定图集和图集上的sprite
毕竟一张小贴图做成一个外部资源,在开发流程上是一个很麻烦的事情,文件越多,误操作越多,出问题的几率越大,及早发现问题也越难
整个游戏有一个公共的1024图集。 然后每个界面 有一个256 – 512不等的图集, 没有更好,尽量用公共图集上的。
商城icon至少用到N张1024*1024的大贴图,NGUI时代可以直接做成N个atlas,打包成N个assetbundle动态更新+动态加载,UGUI如果要实现这种需求应该如何处理?
商城icon至少用到N张贴图,NGUI时代可以直接做成N个atlas,打包成N个assetbundle动态更新+动态加载,UGUI如果要实现这种需求应该如何处理?
我也想确定 如何在一个大的prefab中确定那些小的sprite ,因为image需要动态更改的。我现在的解决办法是sprite放在resources下 动态load改变。这种方法不知道怎么弄
放在resource下,如何动态更新呢
不能放在resources下。。 因为resources下就不会自动打图集了。。
在线更新的情况下,是不是更新一张小图片,就要重新打包整个AssetBundle?
更新整个图集。。
我发现在 editor 模式下,图集是不会被引用的,draw call 很高,运行时候才能起作用
可以设置的。。 在Editor菜单下有选项
灰常厉害呀
prefab里面的sprite都关联了特定的altas的sprite,请问NGUI的图集该怎么更新?
ugui 在构建的时候都是使用散图 这点是很好的,就是不知道 ugui 在实际运行中的 图集情况,有空我测试一下。
我认为终极的图集管理应该是这样的,所有的UI在你构建的时候都使用散图,然后在程序动态运行的时候,程序动态生成对应的图集.这样一来你的图片绝对不会冗余,Drawcll 永远保持最低。貌似 ugui 有几分这样的意思。
把小图都做成prefab。。。这个太蛋疼了吧。其次导出build项目,Resources都会引用吧?这不是加满了build速度。其实Editor可以用AssetDatabase加载。比较排斥在Resources放资源。还有如果我希望实现SD&HD的自动切换,其实就是运行时选择图集。
NGUI可以实现,其实就是运行时切换sprite图集。不知道ugui实现的区别
只能用prefab关联, 但是关联的就不是小图了,而是图集里的大图。。
嘿嘿。
ugui有data binding没
data binding 是啥??
就是一种数据跟界面相互绑定的方式,改变其中一方,另一方也随之改变。在mvvm架构中比较常用
Foundation plugin有你说的功能但不知道好不好用https://www.assetstore.unity3d.com/en/#!/content/15647
竟然还是免费版本,值得看一下。。
这个好像的确不错,功能很多哇,就是不知道效果怎么样。我现在用的是Ndata,适用于NGUI,不过已经不更新了.
赞一个。师傅
哈哈~~
MOMO老师,不好意思,问个题外话:在新版的Mecanim动画系统中,怎么改变Animator中单个state的播放速度?而不是改变所有state的速度呢?在老版系统中的话,只需要在脚本animation[“idle”].speed = somespeed ;就能做到,新版的动画系统在脚本中如何做到这种效果呢?
第二个设想,还是先把小图打成图集,然后把所有小图关联在prefab上,拷贝在Resources文件夹下,这样运行时也能用Resources.load了。到底那个靠谱呢? 给大家看一个图大家就知道答案了。MOMO这里说的是把图集关联在PREFAB上吧。
把小图关联在prefab上。。
那你的意思是PREFAB其实就是用来作为索引用的?
UGUI在哪下载。
http://unity3d.com/unity/beta/4.6