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上。


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

手做貓走道 - Part 2

  接續之前的進度繼續開工,這周末又剛好有連續假期,可以製作的進度我想應該會比較多一點,不過根據之前製作的經驗來看,時間其實都不是花在製作上,一小部分花在規劃跟測量上,大多的時間是花在打混摸魚、看電視、上網上吧XD

  這次的進度就是把上升階段完成一半,至少要到開始轉向的部分,接下來就可以到天花板附近了,這有些高的地方鑽孔之類的感覺有點危險,說不定會有懼高症之類的症狀,哈。



手做貓走道 - Part 1

  最近打算把很久以前就想做的貓走道開始製作,需要的工具基本上都算有,大概就只缺材料了。這個計畫拖了好久才終於要開始動作了,終於找了一個周末去附近的材料店買了幾塊木板、繩子、螺絲等等。

  材料買了回來但卻不能立刻做,因為當初想製作的東西也只是個簡單的概念,目前除了需要找一個可以利用的空間之外,還需要簡單的測量一下空間,雖然不想要全部詳細的規畫完後才開始做,但至少初期的部分要有些規劃,之後的部分就等初期完成再說。



手做貓抓板(Cat scratch board)

  好久以前做的貓抓板已經爛掉好久了,最近終於有時間,而且也剛好有材料,就來做一個新的吧,而且材料還很多,這次可以做一個大的。

  之前看過有一種是正方形中間中空的,這次就來做這種的,材料的大小我覺得也算剛好,做出來中間的空間不會太大讓整個結構不穩,也不會太小讓使用者在裡面抓起來不舒服。

  大致準備好一些工具就可以來玩啦!

  先把要用的瓦楞紙箱準備好,這次的品質比較好,上面也沒有印任何廣告花紋,監工已經在檢查了。


Unity - 使用2D Sprite時改變SortingLayer或SortingOrder造成Draw Call大量增加的問題

  為了稍微增進一點效能,節省Draw Call的情況下,使用Unity內建的Sprite Packer,這可以把設定相同Tag的圖片合成一張Atlas,這是個很方便的功能,也就可以省去使用外部軟體自己合成一張Atlas的步驟。

關於Sprite Packer的相關使用說明就請自行參考官網說明文件
http://docs.unity3d.com/Manual/SpritePacker.html


  不過有時候在使用這個功能卻發現DrawCall並沒有預期中的減少,反而是增加許多的情況,一開始其實覺得有些莫名其妙,稍微研究一下後才發現事有蹊俏。

Unity - 2D場景中使用ParticleSystem時,粒子被Sprite擋住的問題

  製作2D遊戲的時候免不了會想使用一些粒子系統,但是在某些狀況下使用的粒子系統發射的粒子會被2D的Sprite擋住無法呈現在Camera前面。基本上這只是2D的SortingLayer、SortingOrder造成的問題。

北方之塔18

  又過了好一陣子阿,這次的主介面又有稍作調整,不過並沒有調整太多,主要的部份還是在修改許多舊的Code,同時製作一個新的系統,簡單的場景互動系統,這個部分可能包含的像是物品拾取、啟動機關、移動物件之類的功能。

  主介面除了調整配置、更換一些圖,另外就是改變功能按鈕,原本功能按鈕只是簡單的一個按鈕背景加上文字,但是覺得有點太佔空間,尤其是顯示在畫面中的時候,而且文字也比較不容易看,所以更換成小圖示,不過要選擇哪種小圖示又是個煩惱的決定,因為使用的素材不是自己畫,所以要挑選有代表性的才行。


北方之塔17

  角色介面稍微做個調整,調整一下配置跟UI圖片,也是使用跟物品介面相同的免費素材,裝備介面跟之前類似點擊部位後打開裝備選擇,不過裝備選擇介面還沒調整完。接著就是打算暫時做一個技能介面,然後可以簡單地做一個快捷按鈕的配置,這樣就可以開始來弄技能系統了。

  然後把一個怪物的Model替換成蜘蛛,原本是用一個方塊來表示,替換成蜘蛛後就感覺好像有這麼一回事一樣,哈哈。當然這個也是Unity的AssetStore可以下載到的免費模型,上面有幾個品質還不錯的模型,如果我可以把這個遊戲做到快完成的話,也許可以購買幾個便宜的模型來使用。


北方之塔16

  重新調整完物品資訊的介面圖片後,接著稍微調整一般的介面圖片以及架構,除了更新圖片去掉無用又太大的圖之外,重新調整的架構又讓DrawCall少2還算滿不錯的。

  除了調整一般操作介面,也調整腳色資訊面板,這個時候其實覺得原本的UI相關的Code有些繁雜,製作或使用上也不是太方便,也就有打算重新製作整個UI相關的部分,這部分花了不少時間,把原本的Code都去掉改為使用新的,也要確認新的UI運作跟原本的一樣,也花了些時間把原本的Code清理一些不太有用的方法,目前雖然還沒全部調整完,但大致上UI已經換成新的架構了。


北方之塔15

  最近這一個多月有點忙阿,公司專案需要趕些進度,結果回家都不想動只想無腦看個小說,還好最近稍稍閒了一點,抽空把之前沒做完的部分繼續補齊。

  之前做到了裝備欄位可以選擇裝備物品,接著要做的就是一個簡單的技能面板,不過我打算在製作這個面板前,先把技能系統架構出來,一方面可以知道我畫面需要呈現的有哪些資訊,以及把技能系統簡單確定一個方向。

  目前還在調整技能系統,同時也順便再次修改一下裝備欄位跟物品資訊面板。