【厚積薄發】AssetBundle中加載SpriteAtlas圖集之後卸載異常

2021-03-02 侑虎科技

這是第232篇UWA技術知識分享的推送。今天我們繼續為大家精選了若干和開發、優化相關的問題,建議閱讀時間10分鐘,認真讀完必有收穫。

UWA 問答社區:answer.uwa4d.com

UWA QQ群2:793972859(原群已滿員)

本期目錄:

AssetBundle中加載SpriteAtlas圖集之後卸載異常Shader相關問題如何監聽GameObject的localScale改變

項目中大量的字節文件的合併和熱更新方案

一個關於相機的幾何數學問題

Q:我從AssetBundle包中加載圖集和音頻,然後在卸載的時候使用Resources.UnloadAsset,發現音頻可以卸載,但是SpriteAtlas無法卸載。

代碼:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.U2D;
using UnityEngine.SceneManagement;

public class Test_ResourceUnload : MonoBehaviour
{
    public AudioClip[] clips;
    public SpriteAtlas[] atlas;
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.A)) StartCoroutine(LoadAB());
        if (Input.GetKeyDown(KeyCode.Space))
        {
            //SceneManager.LoadScene("222"); 加載場景自動卸載

            for (int i = 0; i < atlas.Length; i++)
            {
                Resources.UnloadAsset(atlas[i]); //不能卸載
            }
            for (int i = 0; i < clips.Length; i++)
            {
                Resources.UnloadAsset(clips[i]); //可以卸載
            }

            //下面的可以卸載
            //for (int i = 0; i < clips.Length; i++)
            //    clips[i] = null;
            //for (int i = 0; i < charAtlas.Length; i++)
            //    charAtlas[i] = null;
            //Resources.UnloadUnusedAssets();
        }
    }

    private IEnumerator LoadAB()
    {
        atlas = new SpriteAtlas[5];
        for (int i = 1; i < 6; i++)
        {
            string ABPath = Application.streamingAssetsPath + "/chars/" + i.ToString();
            var ABRequest = AssetBundle.LoadFromFileAsync(ABPath);
            yield return ABRequest;
            AssetBundle charAB = ABRequest.assetBundle;
            if (charAB != null)
            {
                atlas[i - 1] = charAB.LoadAllAssets<SpriteAtlas>()[0];
                charAB.Unload(false);
            }
            else
                Debug.LogError("加載關卡charAB錯誤 null");
        }

        string ABPathAudios = Application.streamingAssetsPath + "/audiodubbing/1";
        var ABRequestAudios = AssetBundle.LoadFromFileAsync(ABPathAudios);
        yield return ABRequestAudios;
        AssetBundle charABAudios = ABRequestAudios.assetBundle;
        if (charABAudios != null)
        {
            clips = charABAudios.LoadAllAssets<AudioClip>();
            charABAudios.Unload(false);
        }
        else
            Debug.LogError("加載關卡charAB錯誤 null");
    }
}

在Proflier中查看(打包後電腦測試,非Editor),按下A加載如下:

按下空格卸載如下:

前後對比發現AudioClips已經卸載了,但是圖集卻沒有卸載。項目是簡單的測試項目並沒有在別處使用加載資源。

測試Unity版本2019.4.9。


A1:Resources.UnloadAsset在Unity的文檔中有這樣一句話:「This function can only be called on Assets that are stored on disk.」

所以SpriteAtlas是無法使用這個接口卸載的,而Texture是可以的。卸載SpriteAtlas可以將圖集單獨打AssetBundle,使用AssetBundle.Unload(true)來卸載,或者清空引用後由下一次Resources.UnloadUnusedAssets來卸載。

感謝範君@UWA問答社區提供了回答

A2:SpriteAtlas裡面生成的圖集(Texture)確實是無法使用Resources.UnloadAsset來卸載的,使用這個接口只能卸載內存中SpriteAtlas對象,而不能卸載SpriteAtlas裡面引用的sactx開頭的Texture。這種關係類似於Sprite和Texture。

可以看到內存中有SpriteAtlas,也有SpriteAtlas引用的Texture,這個Texture是被SpriteAtlas引用的。

調用Resoures.UnloadAsset(sa)之後,SpriteAtlas對象從內存裡卸載了,但是那個sactx開頭的Texture還在內存中,只是沒有了SpriteAtlas引用它而已。在Sprite中,我們可以調用Resources.Unload(Sprite.texture)來卸載這個Sprite引用的紋理,但是SpriteAtlas沒有提供這樣的接口。我們可以曲線獲取到這個Texture,從SpriteAtlas裡面加載一個小的Sprite,然後調用這個Resources.UnloadAsset(Sprite.texture),但是Unity會報錯。

報錯內容是「UnloadAsset can only be used on assets;」,所以只能清理完引用關係後調用Resources.UnloadUnusedAssets,或者AssetBundle.Unload(true)來卸載。

感謝Xuan@UWA問答社區提供了回答,歡迎大家轉至社區交流:

https://answer.uwa4d.com/question/5fd4de5210a17c6c2b09d625

Q:UWA報告中指出Shader.Parse調用頻繁,這裡我們目前有二個疑問:

第一,Shader解析以後佔用的ShaderLab內存,在我們釋放對應Shader以後是否也是正常釋放的?

第二,Shader重複解析除了預加載我們是否可以通過其他方式來避免?比如,對Shader依賴分析做好以後是否可以避免?

另外,關於Standard ,是否可以提供一個工具讓我們查詢有哪些使用到了Standard?

A:1. Shader釋放後,ShaderLab的內存是會相應下降的;如果Shader的依賴關係做好,可以很大程度上降低Shader資源的冗餘問題;

2. Standard Shader可以通過UWA在線AssetBundle檢測來查看,具體是打包到哪些AssetBundle文件中。同時,也可以通過UWA本地資源檢測來查看Standard Shader的具體情況。

以下服務登錄UWA官網均可免費使用:

在線AssetBundle資源檢測

UWA本地資源檢測工具

感謝芭妮妮@UWA問答社區提供了回答,迎大家轉至社區交流:

https://answer.uwa4d.com/question/5fd82ca810a17c6c2b09d68b

Q:我遇到一個問題:在一個時間點一個GameObject的localScale會被設置成另外一個我不期望的值,但是找了半天相關引用的代碼都沒有發現localScale被改變。中途彈出了一個「 [Physics.PhysX] cleaning the mesh failed」錯誤,我本來以為是這個引起的,但是我逐幀列印localScale發現是在這個錯誤輸出之後的N幀之後才出現的。相關引用方法也都列印了日誌,但是都沒有發現調用。


A:可以嘗試下這個工具:

https://github.com/handzlikchris/Unity.MissingUnityEvents 

注意這個工具是需要在Windows使用的,通過注入Unity的DLL實現。簡單寫了個例子測試可用。

Callstack可以看到調用信息:

而斷點跟進去通過Rider的反編譯可以看到目前的Transform的localScale的set方法已經有回調了:

感謝範君@UWA問答社區提供了回答,迎大家轉至社區交流:

https://answer.uwa4d.com/question/5fd71e7310a17c6c2b09d658

Q:我們項目中有大量的字節文件,大到地圖數據,小到各種模塊自定義的字節數據。都是通過流的方式去加載的。需求是希望通過合併這些字節數據,減少打開流的數量,同時可以分塊壓縮。


現在的方案:

1. 定義一個Block的大小比如1MB。

2. 對於大於1MB的字節數據按1MB分割成Block,每個Block獨立壓縮,最後把這些壓縮後的Block合併成一個文件。需要讀取某一段數據的時候,通過壓縮前後記錄的位置,來判斷需要解壓哪幾塊Block,然後讀取。

3.對於小於1MB的字節數據和其他字節合併,直到大小大於等於1MB。對合併之後的Block壓縮。需要讀取某一個文件的時候,把文件所在的Block解壓,通過之前記錄的位置來讀取數據。

最後,生成的文件裡面,大文件還是一個文件(內部包含了多個1MB+的Block),但是小文件被合成了多個1MB左右的Block。

熱更新方面:

1. 對於大文件來說,某一個BlockA數據變化之後,會New一個新的文件,BlockA數據會從伺服器下載,其他的Block從本地原來的文件中拷貝過去。

2.對於小文件來說,其中一個文件刪除或者添加,會導致後續分Block的順序不同。

比如:本來有兩個小文件的Block->ABCD和EFG,之後把小文件B刪除了,生成的規則變成了ACDE和FG了,這樣就需要把之前ABCD和EFG全部重寫掉。


現在的方案對於熱更新不太友好,特別是小文件,一旦一個刪除了或者添加,後續的Block都需要修改。

A1:提供一個思路,僅供參考。

按這個邏輯,打包小文件時應該要把上一次的打包結果的Block Table也作為輸入,之前已經存在的資源並且也在Block Table中有對應的Block時,應首先考慮仍保留在這個Block中。

在這個基礎上,針對文件新增、刪除和更新的情況處理(以問題中Block1:ABCD,Block2:EFG來說明)。

例子中提到的文件刪除、文件B被刪除,則新的版本中,Block1應為ACD。

文件新增,比如新增了文件H,如果大小大於Block Size,則按照你們的大文件邏輯處理,否則可以插入到某個仍有空間的Block內,如果沒有符合的Block,則新開一個Block存放。

如果有文件更新,例如文件A更新為A1,更新後如果大於Block Size,則從Block1中拿出按大文件處理,Block1變更為BCD;如果小於Block Size,當A1 BCD的總大小仍然滿足Block Size的限制,則正常更新處理,如果A1 BCD的總大小大於Block Size的限制,則將其分割,例如:A1B為一個新的Block,Block1變成CD。

這類大文件存儲方式其實可以參考一些端遊的實現方式,比如Blizzard早期使用的時MPQ格式及後期使用的CASC格式,GitHub上都有開源庫可以參考:

https://github.com/ladislav-zezula/StormLib

https://github.com/ladislav-zezula/CascLib

感謝範君@UWA問答社區提供了回答,迎大家轉至社區交流:

https://answer.uwa4d.com/question/5fd9bc3910a17c6c2b09d6cc

Q:在知道玩家的坐標點A,怪物的坐標點B,A和B在同一個水平面,相機的所有參數。A和B在視口的位置,可能是同一側,也可能是不同側,下圖只是一個情況。


中間的紅線是視口坐標X=0.5的位置,現在怪物的視口坐標X=y是在黃線的位置,現在想求相機繞著玩家的坐標點Y軸的方向,旋轉多少度可以讓怪物在視口的坐標變為X=x(就是綠線的位置)?目的是戰鬥的時候保證怪物主體顯示在相機視口,即想顯示在相機的部分視口範圍內。

mul(VP, 怪物世界坐標).x = 指定值

mul(VP, 玩家世界坐標).xy = 指定值

攝像機位置和人的位置的距離 = 指定值

A1:如果是希望角色和怪物主體始終顯示在相機視口中,可以讓相機始終對準A、B兩點的中點(或中點附近的某一點),同時保持相機分別與AB的距離不小於某個值,看相機更靠近A點還是更靠近B點,以近的為準。插值計算應該可以實現你要的效果,思路供參考,還沒有實踐。

感謝eangulee@UWA問答社區提供了回答


A2:在前提是玩家是第一人稱視角下,屏幕上目標點A(ax,ay),換算到地面上對應的目標點B(bx,by,bz),假設玩家坐標P,當前怪物坐標M,剩下就是求PM和PB之間的夾角了。

感謝孫星星@UWA問答社區提供了回答

A3:以下幾點供參考:

1. center:相機看向中心。

2. d:相機與中心距離。

3. monster:怪物坐標。

4. fov:相機y軸方向的視野角度。

5. aspect:相機視野的寬高比。

6. viewRatio:怪物在視口的x方向的坐標比例(0到1)。

7. 假設相機旋轉角度:a。

8. 相機坐標:(center.x+dsina , 0, center.z+dcosa)。

9. 相機x軸:(cosa, 0, -sina)。

10. 相機y軸:(0, 1, 0)。

11. 相機z軸:(sina, 0, cosa)。

12. 怪物在相機空間的x坐標monsterCamX:dot(相機到怪物的向量,相機的x軸)

= (monster.x-center.x-d * sina) * cosa - (monster.z-center.z-d * cosa) * sina

= (monster.x - center.x) * cosa - (monster.z-center.z) * sina。

13. 怪物在相機空間的z坐標monsterCamZ:dot(相機到怪物的向量,相機的z軸)

= (monster.x-center.x) * sina - d * sina * sina + (monster.z - center.z) * cosa - d * cosa * cosa

= (monster.x - center.x) * sina +(monster.z - center.z) * cosa - d。

14. 相機在怪物的z坐標(深度)處可看到的xy面的寬度camWidth:

2*tan(fov/2) * aspect * monsterCamZ

15. 最後根據怪物視口比例:

viewRatio = monsterCamX / camWidth

也可能會出現解這樣的方程:sina - 2cosa = 0.2,求角度a。

感謝Manchy@UWA問答社區提供了回答

A4:請參考下圖公式:

感謝Xuan@UWA問答社區提供了回答,歡迎大家轉至社區交流:

https://answer.uwa4d.com/question/5fd38ac210a17c6c2b09d620

封面圖來自網絡

今天的分享就到這裡。當然,生有涯而知無涯。在漫漫的開發周期中,您看到的這些問題也許都只是冰山一角,我們早已在UWA問答網站上準備了更多的技術話題等你一起來探索和分享。歡迎熱愛進步的你加入,也許你的方法恰能解別人的燃眉之急;而他山之「石」,也能攻你之「玉」。

官網:www.uwa4d.com

官方技術博客:blog.uwa4d.com

官方問答社區:answer.uwa4d.com

UWA學堂:edu.uwa4d.com

官方技術QQ群:793972859(原群已滿員)

(長按識別二維碼進入UWA問答)

《UWA本地資源檢測又更新|幫你把關Shader變體問題》

近期精彩回顧

【學堂上新】遊戲自動化測試

【厚積薄發】用ScriptableObject代替部分配置表的坑點

【厚積薄發】Prefab優化:預製體中的各種細節選擇

【學堂上新】8種用戶流失原因分析法

相關焦點

  • 【厚積薄發】技術分享連載(七十八)| UI元素重建 |BlendTree採樣原理 |LoadSubs(Async)加載性能...
    如下圖所示,對於WWW、AssetBundle、GameObject,卸載方法分別為WWW.Dispose、assetBundle.Unload、Destroy/DestoyImmediat。但對於通過AssetBundle加載出來的Assets資源, 這塊的資源用什麼策略清理合適?
  • Unity AssetBundle 從入門到掌握(適合初學者)
    AssetBundle的加載和卸載AB的加載AB的卸載6. AssetBundle分組策略總結邏輯實體分組注意7. Manifest文件什麼是Manifest文件通過Manifest文件得到某個包的依賴8. 文件校驗9.
  • 【厚積薄發】AssetBundle如何計算可靠的Hash值
    除非Reimport,但是Reimport就會造成建置時間暴漲(如果是把 AssetBundleName commit進Repo建置時不設定,則有可能有忘記commit的問題)ScriptableObject .asset沒有commit卻在Build machine自行變化,有的時候是 SerializeFile觸發升級
  • 【厚積薄發】技術分享連載(七十四)| 網格頂點屬性丟失| 優化數據表的加載| 圖集格式設置...
    一開始我們採用了ScriptableObject,把全部模板數據加載到內存並序列化為Asset的方式進行Assetbundle打包,該方案加載速度較為理想。但當我們通過Dll替換熱更新安卓客戶端時,發現這種方式不支持熱更新,一旦Dll中修改了模板表結構,熱更新替換後,ScriptableObject的AssetBundle就無法讀取了,提示損壞的AssetBundle,目前的方案是採用Protobuf代替ScriptableObject進行序列化,可以實現熱更新模板表結構,但是加載速度相對ScriptableObject有較大的差距,目前數據模板加載較慢便導致了玩家進入世界的時間比較久
  • 【厚積薄發】技術分享(六十) 網格的頂點色渲染編輯器下字體引用 LoadFromCacheOrDownload使用規範..
    A:在Unity 4.x的版本中,如果通過LoadFromCacheOrDownload來加載AssetBundle,那麼有兩種情況需要考慮:1. 內存中加載的AssetBundle數量。Q:我閱讀了UWA的你應該知道的AssetBundle管理機制一文,想對其中「每次加載都涉及到解壓操作」的理解進行確認:對於new WWW實際是解壓到WebStream。而如果是用的LoadFromCacheOrDownload,那麼資源是在磁碟,所以在調用www.assetbundle時才做解壓。
  • Unity技術分享(83)|耗時分析|AssetBundle加載出錯|失真……
    加載Q2:我在Profiler中觀察性能曲線,發現某一幀AssetBundle加載中,LockPersistentManager耗時比較大。請問這部分能否優化?這說明當前幀或前幾幀中存在較大量的資源在通過LoadAsync來進行加載,其本質是所加載的資源過大所致,對自身資源進行合理優化可降低Loading.LockPersistentManager的開銷。另外,將異步加載換成同步加載,LockPersistentManager就不會出現了,但其總加載耗時是沒有變化的,因為總加載量沒變。
  • 【厚積薄發】不同種Mesh的DrawCall優化策略
    UGUI打圖集,發現空間夠512*512,每張圖的大小是101x101的,17張101卻打成了512x1024的圖集。感謝lopezycj@UWA問答社區提供了回答A2:Sprite atlas有一個Padding屬性,最小是2像素,默認是4像素。
  • 【厚積薄發】Android 10系統下的PSS數值統計不準
    的新類型CompatibilityAssetbundleManifest並沒有被打成Bundle,而是一個Yaml格式的文件:問答社區提供了回答,歡迎大家轉至社區交流:https://answer.uwa4d.com/question/5f2a22063d242404549aabc8Q:從Bundle中加載的資源
  • 13歲iOS開發者:Swift開發Sprite Kit遊戲實踐
    本文作者Ajay Venkat是一名年僅13歲的iOS開發者,他非常喜歡用蘋果的Sprite Kit 2D遊戲框架來開發iOS遊戲,在了解到很多同齡孩子也對學習如何使用Sprite Kit來開發iOS遊戲非常感興趣之後,他以自己用Swift語言所開發的一款名為「Space Monkey」的遊戲為例,在Ray Wenderlich上寫下了這篇指南,以下為譯文:
  • 【厚積薄發】如何通過Timeline的形式實現技能編輯器
    你可以參考幾個Unity 的插件:https://assetstore.unity.com/packages/tools/animation/cinematic-sequencer-slate-56558 推薦SLATE,擴展起來比較方便:https://assetstore.unity.com/packages/tools/animation
  • AssetBundle從入門到掌握(基於Unity2017)
    課時列表:01-學前必讀02-AssetBundle的定義和作用03-什麼是AssetBundle04-AssetBundle包使用流程05-使用代碼打包AssetBundle06-AssetBundle打包注意事項07-AssetBundle的加載和使用08-AssetBundle
  • 如何通過 python API 安裝和卸載 Blender 加載項
    Blender 加載項的安裝和卸載可以通過 python API 進行管理。在腳本中使用以下命令:要停用加載項:bpy.ops.wm.addon_disable(module = 'add-on name')要卸載加載項:bpy.ops.wm.addon_remove(module
  • 【厚積薄發】UGUI LateBinding使用注意事項
    當我以AssetBundle的方式試圖顯示這個UI Prefab時,如果不事先將SpriteAtlas加載完畢,並且寫好SpriteAtlasManager.atlasRequested回調,確實會報錯。
  • 【厚積薄發】DrawInstance和完全不做合批情況下的性能差異
    UWA 問答社區:answer.uwa4d.comUWA QQ群2:793972859(原群已滿員)本期目錄:DrawInstance和完全不做合批情況下的性能差異UWA報告中檢測出工程沒有的資源精靈設置九宮後,如何不在界面中顯示出來關於AssetBundle資源的卸載問題Total Mono
  • 科學家揭示人腦細胞結構的三維概率圖集
    科學家揭示人腦細胞結構的三維概率圖集 作者:小柯機器人 發布時間:2020/8/2 23:43:44 德國於利希研究中心Hartmut Mohlberg課題組在研究中取得進展。
  • 【厚積薄發】LWRP+UGUI使用方式
    AssetBundle中的LoadAsset()中加載出來的GameObject嗎?這個GameObject不是Instantiate出來的,而是直接從AssetBundle中加載出來的。發出這個疑問主要是因為Resources.UnloadAsset去卸載這個GameObject的時候會提示報錯:GameObject、Component、AssetBundle不能被Resources.UnloadAsset卸載。
  • 常見的類加載異常
    上篇文章回顧了一下類加載的委派模型和類加載的三個階段所做的一些事情,本篇文章準備介紹一下 Java 程序運行中常見的類加載異常。
  • CAD插件卸載怎麼操作?CAD插件卸載的步驟方法
    CAD插件卸載怎麼操作?在之前我們講過了CAD如何加載插件,但過多的插件容易導致CAD軟體運行速度緩慢,這時候要怎麼卸載一部分不常用插件呢?本期,模型云為您帶來了CAD插件卸載的步驟方法,一起來看看吧!