合併圖集的好處:
1:減少DrawCall:多張圖片需要多次DrawCall,合併成一張大圖只需要調用一次DrawCall
2:減少對內存的佔用:OpenGL每張貼圖都需要設置爲2的N次方纔能使用,比如你有一張寬高爲100*100和一張10*10的圖片,如過不合成大貼圖,那麼就需要使用128*128的和16*16的圖片,如果是使用一張大圖的話,就可以將100*100和10*10的圖片放在128*128的圖集上了
UGUI圖集的打包及工作原理:
1:首先圖集的Altas的區別,對於NGUI來說,需要對圖片進行一個對圖集的打包,並且需要去關心這個圖集的大小,比如圖集大小是否會超過1024*1024,圖集該如何規劃等問題
2:UGUI的原理則是,讓開發者徹底模糊圖集的概念,讓開發者不要去關心自己的圖集。做界面的時候只用小圖,而在最終打包的時候unity纔會把你的小圖和並在一張大的圖集裏面。然而這一切一切都是自動完成的,開發者不需要去care它。
3:如下圖所示,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 在這裏你就可以預覽到你的圖集信息。圖集的大小還有圖集的格式等等很多參數我們都是可以控制的,也可以通過腳本來設置。
此時在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。