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位置。
不過這樣只取得了該張Atlas,想要正確使用Atlas上面的Sprite圖還需要對應的Rect參數,不過Unity同樣也無法取得該Atlas上面所有Sprite的Rect的列表,也許是我沒找到方法,不過同樣也是可以自己製作一個列表,所以這次換一個方式一樣用怪招在Awake或Start的時候取得Atlas同時把相關的Sprite建立列表。
所以把Rect建立列表後,就可以用物件的名稱來取得Rect並動態更換同一個Atlas上的Sprite物件,如果要替換不同Atlas的話,也可以用類似的方式先取得不同Atlas的參照保留下來,並把各個Atlas上的物件各自做列表。不過我這邊只是單純把材質跟圖片座邊標分別存Dictionary,還是要看自己的需求去做。
使用這個方法,Prefab變成只是為了取得Sprite資料用的,當然也可以直接Load這些Prefab,直接用來建立物件設定好位置並把之前的物件刪除就好了,好像這樣比較輕鬆的樣子。
不過有時候可能會有奇妙的用途,例如在NGUI裡面有個Unity 2D Sprite的物件,就可以用這個方法取得Atlas中的某些物件然後設定到Unity 2D Sprite上讓它顯示在UI上。
如果有任何錯誤歡迎提出。
測試測試
當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上。
如果有任何錯誤歡迎提出。
Labels:
Unity3D
Subscribe to:
Posts (Atom)