【Unity】UGUI模擬NGUI的UISprite-->LImage

lovewaits發表於2024-10-30
UGUI本沒有像NGUI方便使用圖集的元件,之前也寫過繼承Image,加入SpriteAtlas作圖集,切換圖片顯示的元件,現在弄一個3.0版本的
這個元件的誕生源於上一篇:

【Unity】Addressables下的圖集(SpriteAtlas)記憶體最佳化

====================================================================================

1、首先是在Image的基礎上加入圖集資訊和切換圖片的操作

在上篇裡面都有寫,是透過序列化圖集的路徑和選擇Sprite的Name,初始化時透過Addressables載入圖集,並完成圖片選擇顯示
2、在編輯器模式下,透過屬性皮膚完成圖集資訊和圖片的選擇

[CustomEditor(typeof(LImage), true)]
[CanEditMultipleObjects]
public class LImageInspector : ImageEditor
SpriteAtlas m_SpriteAtlas;//臨時變數
string atlasName = AddressableEditorHelper.GetAssetPathByAddressableName(m_Comp.AtlasName);
m_SpriteAtlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasName);
在屬性皮膚的Enable方法裡,獲取已設定好資訊的圖集資源
m_SpriteAtlas = EditorGUI.ObjectField(rect, m_SpriteAtlas, typeof(SpriteAtlas), false) as SpriteAtlas;
將臨時圖集變數顯示在屬性皮膚,來進行替換
if (m_SpriteAtlas != null)
{
    m_NewAtlasPath = AddressableEditorHelper.GetAddressableNameByObject(m_SpriteAtlas);

    if (m_AtlasPath.stringValue!= m_NewAtlasPath)
    {
        m_AtlasPath.stringValue = m_NewAtlasPath;
        Object[] sprites = m_SpriteAtlas.GetPackables();
        Sprite newSprite = null;
        for (int i = 0; i < sprites.Length; i++)
        {
            if (sprites[i].name == m_Comp.SpriteName)
            {
                newSprite = AssetDatabase.LoadAssetAtPath<Sprite>(AssetDatabase.GetAssetPath(sprites[i]));
            }
        }

        m_Comp.overrideSprite = newSprite;
    }
}
//判斷當臨時圖集發生更改時,修改參與序列化的圖集地址資訊
//根據選擇的圖片名稱,在新圖集裡面嘗試尋找資源並賦值給overrideSprite
//賦值給overrideSprite 是因為它不參與序列化,但又可以完成顯示
if (GUILayout.Button("更換Sprite", EditorStyles.miniButton))
{
    if (m_SpriteAtlas != null)
    {
        SpriteSelector.Show(serializedObject, m_SpriteInfo, m_SpriteAtlas, m_Comp);
    }
    else
    {
        EditorUtility.DisplayDialog("錯誤", "請先設定圖集!", "確定");
    }
}
//增加更改圖片的按鈕,呼叫開啟切換圖片視窗的介面

3、實現根據圖集展示所有資源,並提供展示選擇的視窗

public Sprite[] LoadSprites(SpriteAtlas tempAtlas, string spriteName)
{

    UnityEngine.Object[] sprites = tempAtlas.GetPackables();

    Sprite[] _spriteArray = new Sprite[sprites.Length];
    Texture tempTexture = null;
    Sprite tempSprite = null;
    for (int i = 0; i < sprites.Length; i++)
    {
        if ((tempTexture = sprites[i] as Texture) != null)
        {
            _spriteArray[i] = AssetDatabase.LoadAssetAtPath<Sprite>(AssetDatabase.GetAssetPath(tempTexture));
        }
        else if ((tempSprite = sprites[i] as Sprite) != null)
        {
            _spriteArray[i] = tempSprite;
        }
    }

    if (string.IsNullOrEmpty(spriteName))
    {
        return _spriteArray;
    }
    return Array.FindAll(_spriteArray, (a) => { return a.name.ToLower().Contains(spriteName.ToLower()); });
}

//更具圖集獲取它對應的所有Sprite,其實這裡可以直接呼叫SpriteAtlas的GetSprites介面,我這裡是之前採取源資源直接賦值給sprite屬性的方案,現在clone的sprite都可以

然後用上面獲取到的所有Sprite,在建立的視窗上面顯示出來,並給所有圖片一個點選事件

if (mImage != null)
{
    mImage.SetSpriteEditor(sprite);
    EditorUtility.SetDirty(mImage.gameObject);
}
//在點選事件中,將選中的Sprite傳遞給LImage元件,進行編輯模式切換Spirte處理
public void SetSpriteEditor(Sprite _sprite)
{
    overrideSprite = _sprite;
    if (_sprite != null)
    {
        m_SpriteName = _sprite.texture.name;
    }
}
//修改當前顯示
//修改參與序列化的Sprite Name值

因為overrideSprite是不參與序列化的,在編輯器預覽預設時,需要給LImage增加一個初始化顯示的方法,和LImageInspector皮膚Enable時類似,更具圖集地址獲取本地圖集資源,再根據選擇SpriteName從圖集資源中找到對應的資源,賦值給overrideSprite,完成預覽顯示

4、完結!!

解釋一下為什麼不採用Sprite源資源直接賦值給sprite的方法,Image的sprite欄位是參與序列化的,然後結合我上一篇最後的那段話

在非執行情況下(編輯器模式下),將資源直接拖拽到Image元件的Sprite屬性上後,該資源會被Unity序列化到場景或預製件中,即便在執行時銷燬了Image元件本身,資源本體依然儲存在序列化資料中,並不會被直接解除安裝。執行期間,Unity對拖拽到Inspector皮膚上的資源進行引用計數管理,即使Image元件被銷燬,拖拽到Sprite屬性中的資源依然佔用記憶體,因為它在場景中存在序列化引用。

所以更改overrideSprtie,減少參與序列化的資源資訊,及時清理記憶體;

但是上面這個處理方案可能會造成顯示“延遲”,因為它需要再載入,可以修改上面對圖集的處理,將它更改為參與序列化,但是要注意記憶體!

相關文章