Unity3d外掛程式SmoothMoves載入速度最佳化

來源:互聯網
上載者:User

標籤:style   blog   color   使用   io   strong   檔案   資料   

我們遊戲是使用Unity3d做的2D遊戲,角色特效等都使用SmoothMoves來製作(在國內估計也算奇葩一朵吧,據說燃燒的蔬菜也是SmoothMoves作的),遊戲中的所有的資源--角色、特效、技能ICON、角色ICON、音效等幾乎都使用assetbundles來實現。

問題:載入一場戰鬥的時間大概要30s左右!!!

解決方案關鍵字:依賴打包、資料區塊共用、冗餘資料剔除

最佳化後:5s左右 :)

 

1. 依賴打包

  1.1 使用AssetDatabase.GetDependencies()介面可以查看資源的依賴引用情況,利用這些依賴資訊,就可以設計如何規劃依賴打包了

PS: 曾猜測unity是在meta檔案儲存體了資源間的依賴關係,結果在meta中沒有找到什麼痕迹... 有瞭解的兄弟請分享~ 

PS: GetDependencies 對prefab不起作用?我的開啟檔案不對?

 

  1.2 依賴打包指的是使用BuildPipeline.BuildAssetbundle()打包資源時,使用BuildPipeline.PushAssetDependencies() 和 PopAssetDependencies()兩介面,將資源間共用的引用資源抽離,避免重複資源。比如A、B兩資源都引用了C資源,如果不使用依賴打包,A、B對應產生的assetbundle中都會有C資源的拷貝,在記憶體中也就有兩份C資源,這樣既增大了資源套件,也浪費了寶貴的記憶體空間。於是,Push/Pop組合就可以派上用場了。

 // 打包樣本1
1 Push 2 BuildAssetbundle C 3 4 Push 5 BuildAssetbundle A 6 Pop 7 8 Push 9 BuildAssetbundle B10 Pop11 Pop

這樣會得到3個assetbundle檔案:A、B、C,在載入時由於依賴關係,一定要先載入C,才可以載入A或者B。而A與B間則沒有任何其他的依賴關係,先載入哪個無所謂。

對樣本打包方式略加修改:

// 打包樣本2
1 Push2 BuildAssetbundle C3 4 Push5 BuildAssetbundle A6 BuildAssetbundle B7 Pop8 Pop

或者再乾脆點

// 打包樣本3
1 Push2 BuildAssetbundle C3 BuildAssetbundle A4 BuildAssetbundle B5 Pop

這兩種打包方式,A和B仍然依賴於C,最後一種B同時還潛在的依賴於A。如果A B間除了共同引用了C資源之外,還有其他共同的依賴項D(善用AssetDatabase.GetDependencies),則在載入B之前還必須要先載入A。所以推薦使用第1種打包方式,以避免類似情況的發生。

    還是上面ABC的例子,如果A、B兩檔案本身沒有更新,而C有修改,此時,可以只重新打包C,無須重新打包A或B,但一定要使用BuildAssetBundleOptions.DeterministicAssetBundle選項。    

    另外,依賴關係本身我們使用ScriptableObject來儲存,當然也可以考慮使用XML等其它方式。

 

 1.3 SmoothMoves動畫打包

    前面說到我們使用了SmoothMoves來製作2D動畫:

    a. 由於動畫檔案間會交叉引用atlas檔案,為避免atlas的重複,將所有的atlas都由動畫檔案中抽離,單獨打包,然後再打包動畫檔案。

    b. 所有atlas都引用同樣的Shader,使用Profiler可以看到每個atlas的shader都要解析一次,為避免shader的重複解析,shader也抽離後單獨打包

    c. 每個動畫檔案都掛載有BoneAnimation指令碼,其atlas資訊則由TextureAtlas來儲存,這兩個指令碼都在SmoothMoves_Runtime.dll中。實測中發現,DLL檔案的依賴打包一定要小心處理。我們的方法是建立一個無用的TextureAtlas(僅僅為引用SmoothMoves_Runtime.dll),而所有的動畫檔案和相應的atlas檔案都依賴於此進行打包。

    大致打包流程如下:

 1 // SmoothMoves動畫打包樣本 2 BuildSMAnimation() 3 { 4     Push 5         BuildAssetbundle shared_shader 6  7         Push 8             BuildAssetbundle SmoothMoves_Runtime.dll 9 10             Push11                 foreach atlas do12                      BuildAssetbundle atlas13                 end14 15                 foreach sm_animation do16                     Push17                         BuildAssetbundle sm_animation18                     Pop19                 end20 21             Pop22 23         Pop24 25     Pop26 }

如上所示,可以在打包的同時產生依賴關係配置,並在遊戲初始化時,首先讀取該依賴關係,然後是shared_shader、SmoothMoves_Runtime.dll,並且將兩者常駐記憶體,即不對其assetbundle執行unload操作,因為任何的動畫檔案載入時對BoneAnimation指令碼的處理都依賴於SmoothMoves_Runtime.dll,任何atlas載入時其shader都依賴於share_shader。

 

2. SmoothMoves.BoneAnimation 中的大資料區塊共用

    使用依賴打包後,載入速度有提升,但依舊需要近20s!!! 繼續使用Profiler查看分析,最終確定是動畫檔案掛載的指令碼BoneAnimation中的有大量資料,引起了大量GC Alloc。而且在動畫執行個體化時,BoneAnimation也會進行深度複製,進一步測試後確定是BoneAnimation中的triggerFrames和mAnimationClips兩成員變數佔用絕大多數記憶體(在較大的動畫檔案中,這兩項竟佔到1MB)。閱讀SmoothMoves_Runtime的原始碼後,確定遊戲中這兩個成員變數是唯讀(事實上triggerFrames會有寫操作,只是我們的遊戲不會用到),所以決定將這兩項資料抽離BoneAnimation,並儲存在SMAnimationData(自訂的ScriptableObject) 中單獨載入,並在需要的時候為BoneAnimation添加引用。這樣保證了同一動畫檔案的各執行個體化對象共用同一份資料,減少了GC Alloc的次數,同時也減少了記憶體佔用。如下:

 1 // 抽離BoneAnimation.triggerFrames 和 mAnimationCips 2 sm_animation_data = ScriptableObject createInstance of SMAnimationData 3  4 sm_animation_data.triggerFrames = bone_animation.triggerFrames 5 sm_animation_data.mAnimationClips = bone_animation.mAnimationClips 6  7 bone_animation.triggerFrames = null 8 bone_animation.mAnimationClips = null  9 10 BuildSMAnimation11 12 BuildAssetbundle sm_animation_data

雖然已經減少了執行個體化SmoothMoves動畫時的大資料區塊複製帶來的GC Alloc,但由於SMAnimationData中的triggerFrames 和 mAnimationClips兩大資料,載入時的大量GC Alloc依舊不可避免。繼續想辦法壓縮這兩個大資料區塊。

 

3. 最佳化BoneAnimation.triggerFrames

    a. 出發點

    BoneAnimation.triggerFrames 是以clipIndex & frame 為索引值的TriggerFrame數組,每個TriggerFrame都儲存了相應clip的相應frame上所有骨骼主要畫面格的資訊,即TriggerFrameBone列表(TriggerFrame.triggerFrameBones)。詳細分析後發現,這些TriggerFrameBone列表間或列表內部,存在大量屬性完全一致的TriggerFrameBone。以此為出發點,建立一個無重複的TriggerFrameBone集合,並讓每個TriggerFrame中的TriggerFrameBone列表都引用該集合中的元素。

    b. 建立TriggerFrameBone集合和索引表

        b.1 在SMAnimationData中添加新成員List<TriggerFrameBone> triggerFrameBoneSet,作為TriggerFrameBone集合使用;

        b.2 在TriggerFrame中添加新成員List<int> triggerFrameBoneIndexes,儲存該TriggerFrame中原有的triggerFrameBones列表在SMAnimationData.triggerFrameBoneSet中的索引;(事實上項目中使用的List<byte>類型,就是這麼摳門...)

        這樣在SMAnimationData.triggerFrames賦值後就可以建立SMAnimationData.triggerFrameBoneSet 和各個 TriggerFrame.triggerFrameBoneIndexes了:

 1   // 建立TriggerFrameBone 集合         2   BuildTriggerFrameBoneSet 3   { 4        foreach tf in sm_animation_data.triggerFrames do 5            foreach tfb in tf.triggerFrameBones do 6                //此處遍曆整個列表是否有屬性完全一致的TriggerFrameBone,即是說需要一個對比兩個TriggerFrameBone是否一致的介面 7                if sm_animation_data.triggerFrameBoneSet not contains one TriggerFrameBone equaling tfb    8                    sm_animation_data.triggerFrameBoneSet.Add(tfb) 9                endif10 11                tf.triggerFrameBoneIndexes.Add(sm_animation_data.triggerFrameBoneSet.IndexOf(tfb))12            end13    14            tf.triggerFrameBones.Clear()15        end16   } 

如此,在對SMAnimationData進行序列化時,TriggerFrame.triggerFrameBones是沒有內容的,而所有的TriggerFrameBone都在triggerFrameBoneSet中。實際效果表明,TriggerFrameBone重複率非常高,其數量可減少90%以上。

相應的在載入SMAnimationData完成時,要根據TriggerFrame中的triggerFrameBoneIndexes重建triggerFrameBones列表。

 

4. 最佳化BoneAnimation.mAnimationClips

    BoneAnimation.mAnimationClips是以clip name為索引值的AnimationClipSM_Lite數組,每個AnimationClipSM_Lite儲存了該clip所有骨骼的顏色資訊,即bones列表

    a. 刪除空的AnimationClipBone_Lite

        AnimationClipSM_Lite.bones 列表中上存在大量沒有實際意義的元素,即AnimationClipBone_Lite中的顏色資訊為空白,也就是下面的函數返回為true。

 1         // 判斷AnimationClipBone_Lite是否為空白    2         public bool IsEmpty() 3         { 4             if (colorACurveSerialized.keyframes.Count > 0 5                 || colorRCurveSerialized.keyframes.Count > 0 6                 || colorGCurveSerialized.keyframes.Count > 0 7                 || colorBCurveSerialized.keyframes.Count > 0 8                 || colorBlendWeightCurveSerialized.keyframes.Count > 0)  9             {10                 return false;11             }12             else13             {14                 return true;15             }16         }

     在AnimationClipSM_Lite的建構函式中加入AnimationClipBone_Lite是否空的判斷,如果為空白則不添加到bones列表中。如此,還要調整原本對bones的訪問:原代碼中都使用boneDataIndex來對bones進行讀取,即boneDataIndex即是bones列表的索引下標。將縮減後的bones列表要轉換為以boneDataIndex為索引值的映射表,如下

 1         public void BuildBoneDict() 2         { 3             m_bone_dict = new Dictionary<int, AnimationClipBone_Lite>();  4  5             if (bones != null) 6             { 7                 for (int i = 0; i < bones.Count; i++) 8                 { 9                     AnimationClipBone_Lite each_bone = bones[i];10                     m_bone_dict.Add(each_bone.boneDataIndex, each_bone);11                 }12             }13         }

代碼中所有對bones的訪問,都改用m_bone_dict,而bones列表僅起到序列化/還原序列化的作用。

 

    b. 刪除AnimationClipBone_Lite中的無用幀

        AnimationClipBone_Lite中的儲存是該骨骼的顏色變化曲線,以下兩種情況可以最佳化:

        b.1 當顏色變化為一條直線,那除了直線兩端的點以外,其它點都是多餘的

        b.2 當顏色變化僅有一個點,且其blendWeight值為0時,該點是多餘的

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.