Unity - 用Script動態取得內建SpritePacker製作的Atlas,並替換為不同的Sprite圖

  同樣是個使用Unity內建SpritePacker的狀況,想要動態取得該Sprite並且把這個圖替換成同一張Atlas上的另一個圖片,或是不同Atlas上的另一張圖片,該如何做,Unity專案中似乎找不到SpritePacking後的那張Atlas來用(Atlas資源在專案的\Library\AtlasCache但是不符合目前的需求)




測試測試
 



當Sprite資料設定了Packing Tag之後,Unity就會在Build的時候把設定的圖片全部打包成一張Atlas(在edit->project setting->editor當中可以設定spritePacker的運作)


這邊我把這六張圖的Tag都設定相同的Set1。


使用SpritePacker打包完的圖片成為一張Atlas,之後再使用這些Sprite製作物件的時候,就可以減少DrawCall的數量。


不過如果當有時候想要動態讀取某些圖片來使用的時候該怎麼辦,一般的用法就是把圖片資料放到Resources資料夾下面,這樣在遊戲運作就可以使用Resources.Load()來載入,但是如果在這個地方這樣使用的話會有問題。


因為這幾張圖片都是設定過PackingTag的,當SpritePacker重新打包(只是把圖片移動到Resources資料夾SpritePacker不會重新打包,可以隨意調整一下圖片的設定讓他強制更新),後會發現這幾張原本設定了Packing Tag的圖從Atlas中消失了。

  這是Unity正常的運作機制,當你把圖片放到Resources中就會在Build的時候把資源一起封裝起來,而原本的SpritePacker製作的Atlas就已經會Build在一起,所以這是Unity避免一個相同的圖片資源重複增加到Build裡面。

  同時這個狀況下原本想要節省的Draw call就產生不了作用了,放到Resources的那幾張圖片,即便有設定PackingTag也不會被放進Atlas。




  我目前雖然是沒找到Unity可以直接取得SpritePacker Atlas或相關Rect的方式,因此這邊使用了一個怪招,雖然有點小麻煩,但是可以達到相同的目的。


首先相關的圖片設定好Packing Tag然後使用這些Sprite來製作幾個Prefab,並且把Prefab放到Resources資料夾下。

  接下來要做的事情就是先從Resources載入Prefab,取得Prefab上面的Sprite資料,接著取得Sprite所屬的Atlas Texture名稱以及在Atlas中的Rect位置。

//取得Prefab所屬的Atlas
private void GetSpriteAtlas(string prefabName)
{
    //先從Resources中載入Prefab
    GameObject obj = Resources.Load(prefabName) as GameObject;
    if(obj == null)
        return null;

    //取得物件上的SpriteRenderer
    SpriteRenderer sr = obj.GetComponent(typeof(SpriteRenderer)) as SpriteRenderer;
    //Texture2D spriteTex = sr.sprite.texture; //這個可以得到Sprite的Texture,如果是有設定PackingTag的則會取得Atlas的Texture
    //Debug.Log(sr.sprite.texture.name); //Atlas的名稱,例如SpriteAtlasTexture-1024x1024-fmt13
    int texID = sr.sprite.texture.GetNativeTextureID();

    //既然有了Atlas的ID,就可以從這個去Resources裡面讀取這張材質,因為SpritePack製作的Atlas會一起Build起來,但是一般在Project視窗中看不到。
    //先把Resources裡面所有的Texture2D載入,再來一個一個比對名稱,注意這個過程效率低、速度慢
    Texture2D[] allTexs = Resources.FindObjectsOfTypeAll();
    foreach(Texture2D tex in allTexs)
    {
        //如果使用Texture的name來比對會有名稱相同的問題
        if (tex.GetNativeTextureID() == texID)
            return tex;
    }
    return null;
}



  不過這樣只取得了該張Atlas,想要正確使用Atlas上面的Sprite圖還需要對應的Rect參數,不過Unity同樣也無法取得該Atlas上面所有Sprite的Rect的列表,也許是我沒找到方法,不過同樣也是可以自己製作一個列表,所以這次換一個方式一樣用怪招在Awake或Start的時候取得Atlas同時把相關的Sprite建立列表。


//使用一個簡單的方法把物件建立列表
public class SpritesList : MonoBehaviour
{
    public Dictionary<string, Dictionary<string, Rect>> allSpr = new Dictionary<string, Dictionary<string, Rect>>();
    public Dictionary<string, Texture2D> allTextures = new Dictionary<string, Texture2D>();

    public string folderPath = "";
    public SpriteRenderer targetSprite;

    void Awake ()
    {
        //先從Resources資料夾讀取所有的Prefab
        Object[] allObjs = Resources.LoadAll(folderPath, typeof(GameObject));
        foreach(Object obj in allObjs)
        {
            GameObject go = obj as GameObject;
            SpriteRenderer sr = go.GetComponent(typeof(SpriteRenderer)) as SpriteRenderer;

            if(sr != null && sr.sprite.packed) //只是確認該Sprite是有被SpritePacker打包過的
            {
                //取得材質的ID,如果這邊是用texture.name的話也是可以,但是有遇過名稱相同的問題
                string texID = sr.sprite.texture.GetNativeTextureID().ToString();

                if(!allTextures.ContainsKey(texID))
                {
                    allTextures.Add(texID, sr.sprite.texture);
                    allSpr.Add(texID, new Dictionary<string, Rect>());
                }
                //用Prefab的名稱來作為取得該Rect的Key,注意這邊的textureRect,根據官網的說明,如果SpritePacker是使用TightPack的話這邊就會造成例外
                Dictionary<string, Rect> spriteRect = allSpr[texID];
                spriteRect.Add (go.name, sr.sprite.textureRect);
            }
        }
    }
}


  所以把Rect建立列表後,就可以用物件的名稱來取得Rect並動態更換同一個Atlas上的Sprite物件,如果要替換不同Atlas的話,也可以用類似的方式先取得不同Atlas的參照保留下來,並把各個Atlas上的物件各自做列表。不過我這邊只是單純把材質跟圖片座邊標分別存Dictionary,還是要看自己的需求去做。

  使用這個方法,Prefab變成只是為了取得Sprite資料用的,當然也可以直接Load這些Prefab,直接用來建立物件設定好位置並把之前的物件刪除就好了,好像這樣比較輕鬆的樣子。

  不過有時候可能會有奇妙的用途,例如在NGUI裡面有個Unity 2D Sprite的物件,就可以用這個方法取得Atlas中的某些物件然後設定到Unity 2D Sprite上讓它顯示在UI上。


  如果有任何錯誤歡迎提出。

No comments:

Post a Comment