MMORPG戰斗系統隨筆(四)、優化客戶端游戲性能


   轉載請標明出處http://www.cnblogs.com/zblade/

        說到游戲性能,這是一個永恆的話題。在游戲開發的過程中,性能問題一直是我們研發需要關注的一個節點。當然,說句客觀話,很多程序員在寫代碼的時候,是不會過多的在意其代碼的性能的。其實這是一種很危險的思想,一個不斷進階的程序員,需要持續的關注自己的代碼質量,同時需要借助工具來反查自己編寫的代碼質量。很多初階的程序員,在編寫代碼的時候,是單純的以完成任務為目標的,其實在寫完后,可以沒事翻看以前自己的代碼,就會有很多不一樣的視角。比如,代碼的整潔性是否注意,可讀性是否有注意,注釋是否有相應的標寫,這是一個基本的代碼規范。

  除了基本代碼規范,進一步的需要考慮的是代碼的質量,也就是性能是否有消耗,是否寫出一些消耗較大的邏輯。對於性能的優化,就需要相應的性能工具來進行計算和統計,不然所謂的性能優化都屬於自我安慰,沒有數據支持。

  下面我回憶總結一下自己參與的一些性能優化工作,也算一個個人總結吧。

一、對lua導出表的優化

  這是我最早而且持續時間最長的一個優化工作。在我們的游戲中,策划會有大量的配置表,全都配置在excel表中,通過編寫導表工具,可以將excel表導出為對應的lua表,用於在游戲中進行lua表的加載和查找。我總結一下優化的幾個節點:

        1、最初版本的導表

        最初版本的lua導表,是將excel中對應的key-value值逐一導出,其基本的格式可以表示為:

local skill = {
   {Id = 1, CD = 5, SkillTarget =1,...}    
   {Id = 2, CD = 3, SkillTarget =2,...}    
   {Id = 3, CD = 6, SkillTarget =1,...}    
}

  這種通過在table中插入多個table的方式,每個子table為hash的存儲方式。仔細分析一下存儲的類型,對於table而言,其基本的存儲方式分為hash和array兩種方式。hash的存儲會有較快的讀取操作,但是會帶來更多的內存占用,因為其需要申請內存來存儲key值,不單單只存儲value值。對於array的存儲方式,lua在底層的實現的時候,並不是我們常見的采用2的冪次的大小來存儲,也就是,不是采用2,4,8,16,32...這種大小的存儲,而是實際的根據數組的大小來分配對應的大小數組。這是后面一次優化中,通過實現設置對應大小的數組來分配內存,發現對比並沒有對應的優化,后來查看源碼才發現其數組的分配規則。

       最初版本的所有lua導出表都采用hash的存儲方式,那么可以想象有多大的內存占用~大概在50M左右~這是一個非常可怕的內存占用,如果在游戲加載的過程中,需要加載這么大的配置表,那么游戲加載會有多緩慢,可以體會一下 :D

      2、初次優化的導表

       其實,最開始引起我們關注的並不是其存儲方式,而是策划配置的某些excel表過於巨大,某些單一的表就會大到幾M的數量級,分析其中的數據,很多數據並不是反復分散變化的,而是集中在較多的幾個高頻中,這就引申出一種優化的策略:提取高頻的配置作為默認配置,少數低頻的配置采用對應的配置,這樣就可以得到對應的一個內存優化。具體的優化策略可以詳見我的這篇文章:table重構index方法優化內存http://www.cnblogs.com/zblade/

       這篇文章的基本思想,就是通過重構和提前高頻的方法,緩存高頻,每次查找的時候,默認去高頻中查找,如果沒有,則走自身的查找。可以查看文章中的對比存儲方法,寫的比較詳細。

    采用這種優化方法后,整體的lua導出表得到大大的優化,整體縮減了接近20M左右,可見我們的策划有多喜歡配置同樣的配置 Orz

      3、進一步的優化導表

    通過上面的一次優化后,我們大大的優化了游戲的lua表內存占用,整體游戲在加載的時候,lua表統計的內存占用在30M左右。如果我們只滿足於這一點,那么就不會有下一步的優化了。后續在第二次的優化上,我還進行了一些特定的優化,但是都沒有太過於亮眼的優化,包括前面提到的用array的方式代替hash存儲,也研究過設置array的大小,不采用2的冪次大小分配內存,事實的統計顯示其實lua本身的內存分配就是采用 按需分配的,不會過多的分配內存。

    最好的優化方法,就是多和別人交流,這是我對自己優化的一個另類總結吧。在上一次的優化后,都沒有太大的性能提升,但是這部分的內存占用又一直處於一個比較大的部分,后來在和其他項目組交流的時候,提供了一種靜態加載的實現思路。對於lua導出表,可以用一種靜態分表加載的方式。特別是對於占用內存比較大的一些表(具體每個表的占用可以做一個內存統計排序),可以分開成多份,這樣在最初的游戲加載的時候,加載的是頭文件部分,這部分是不包含具體的配置表信息的,具體的配置表信息存放在分表文件夾中。在實際使用的時候,比如將技能表分為part1-20,每個分表大小100,定位要獲取id為1001的技能的配置,這時候去加載其對應的分表part11, 加載進來后定位取到1001的技能配置,這樣就不會多余的加載part12-20這部分的數據表。

     通過靜態分表加載的方式,游戲在最初加載lua表的時候,對於分表實現的lua表大大降低了游戲的內存占用,效率提升非常明顯,內存占用縮小到20M左右=。=

       基於這樣的實現方式,進一步分析,其實這些lua表並不需要在游戲啟動的時候加載,只需要在每次第一次使用相關表的時候加載,再加載進來,同時對於大的內存表進行分塊加載,綜合這樣的實現方式,對lua表的優化最終達到一個可以接受的地步。

二、3D模型的優化

  在3D游戲中,美術在3DMAX中做完游戲模型的相關建模和動畫后,會導入到unity中作為游戲資源。通常,這樣的美術資源是含有冗余的資源,可以在unity中對這些冗余的資源進行優化,降低美術資源的內存占用。這兒我例舉兩種美術資源相關的優化:模型UV的優化和動畫animation冗余節點的優化。

  1、模型UV的優化

   一半導入unity的角色模型,都含有冗余的多套UV,對於這些對於的UV,我們是可以用一定的方法來實現剔除的。由於u3d中模型的類型為FBX類型,所遇需要采用FBX SDK,結合FBX SDK的API進行UV的剔除。這兒給出一個FBX SDK的官網:FBX SDK官網

       FBX SDK的官網給出了兩種實現方法:C++和python,對應的都有詳細的API講解和例子。我主要采用python的方法,搭建基本的pyhton環境后,就可以查看相關的API接口,從而實現對應的代碼編寫。具體代碼由於保密我就不提供了,我可以給出相關的實現思路:首先是查找到指定文件夾下的所有FBX文件,然后對於這些FBX文件進行讀取,獲取其下面的所有節點,對於每一個節點,讀取其屬性,如果屬性包含UV,則讀取UV的數量,如果數量大於1,則說明有多余的UV,則將其移除,然后保存

  通過移除多余的UV,可以實現對FBX文件的一次"瘦身",從而降低美術資源對游戲內存的占用。如果有什么需要交流的,可以在下面留言進一步討論。

      2、動畫animation冗余節點的優化

       模型的動畫,在美術制作完后,會被導入到unity中,進而可以在animation窗口中查看。其實我們分析模型的animation,我們可以發現,大部分節點的animation在插值過程中,其實質是不會變換的,基本為1。由於每個節點的animation主要修改 rotation/scale/position這三個transform屬性,所以對於rotation和scale可以進行一次剔除:如果整體animation的插值都為1,則這樣的一條animationCurve是可以剔除的。不知道有沒有讀者也有相關的優化策略,可以在下面留言進一步的討論。

  我就說說我用FBX SDK優化animation冗余節點的思路:首先獲取到FBX模型,然后獲取到對應的FbxAnimStack,也就是animationClip,獲取到animationClip后,進一步獲取到animationLayer,基於animationLayer,可以獲取到每個animationLayer的animationChannels,也就是rotation/scale對應的xyz三個通道,分析這三個通道的通道值,如果都在1的誤差允許范圍附近,則可以移除這個rotation/scale。

      最后存儲修改后的FBX文件,可以發現以前較多的animation經過優化后,都被剔除過濾掉了,這說明很多時候我們的animation其實只是在修改position這一個屬性,並不會修改rotation/scale這2個屬性,這是可以被優化的。

三、頭頂文字描邊的優化

  熟悉MMORPG游戲的都知道,在游戲中角色頭頂都會有名字,稱號等相關的文字信息,實時的反應玩家的信息,便於游戲的交互。在MMO游戲中,會有很多的玩家同時在場,同時也會有較多的怪物NPC等游戲角色的存在,而為了追求游戲體驗,都會對玩家頭頂的文字進行描邊,總結一下對頭頂文字描邊的優化過程:

  1、4個基准點的描邊

  最開始提出要添加頭頂文字的描邊后,和leader商量了一下,就只是單純的對玩家頭頂文字進行四個點的描邊。這里面可能會說一些渲染相關的知識,通常我們在進行渲染描邊的時候,我們最開始是需要填入需要渲染的UV三角形的,如果有不是很了解的同學,可以搜查一下渲染的過程,填充完渲染三角形后,CPU才會把這些三角形傳遞到GPU中,設置渲染狀態,從而進行三角形的渲染,得到最后的渲染結果。所以描邊要么在CPU中進行,要么在GPU中進行,我選擇的是在CPU中進行,在填充三角形的UV的時候,選擇擴展這些UV數組。

  基本的實現思想是:原始的UV數組一份,然后分別在左上,右上,左下,右下的四個基准點進行拓展,這樣就會得到五個數組,最終描繪出來一個描繪了外輪廓的描邊結果,大概結果示意如下圖:

                                                           

  2、四方向的描邊

  基於四個基准點的UV擴展,是可以實現基本的描邊效果,但是仔細推敲就會發現,這其實是對文字的外輪廓進行一個拓展而已,獲得的效果其實並不是非常好,如果對描邊效果有一定要求,這樣的描邊並不是很優秀的。果不其然,高層要求優化描邊效果。我仔細推想了一下,就把四個基准點的描邊變為四個方向的描邊,分別為向左,向右,向上,向下的UV擴展,如果用一個文字來表示效果,可以表示為:

                                                                                                              

  讀到這兒,我想你也可以自己在頭腦中描繪出這樣的5份UV,一份為原始的UV,一份為向左偏移一定offset的UV,一份為向右偏移一定offset的UV,一份為向上偏移一定offset的UV,一份為向下偏移一定offset的UV,這5份UV在進過三角形設置后,會傳遞到GPU中進行三角形描繪,得到的結果就會是比較理想的描邊結果,分別描繪了文字的上下左右四個方向。

  這種描邊方式有很好的描邊效果,帶來的消耗,也是可以直接理解的,通過四份UV的復制,而不是簡單的四個點的復制,對於內存的占用會更大。可見想要有好的效果,還是需要一定的付出。最后這種描邊也被優化掉了,最后采用的是shadow來代替描邊,通過動態合批的處理來降低內存占用。不過從這次描邊的優化,可以更了解整個渲染的過程,CPU是如何計算UV的,怎么設置UV,最后在GPU上渲染得到對應的效果,都是一個比較直觀的過程。

 四、代碼的優化

   寫到這兒,也提一下對代碼質量的優化吧。現在比較主流的熱更都會采用lua來實現邏輯,lua這種腳本語言,上手極其容易,但是如果使用不是很仔細,還是會帶來一些不必要的問題。table作為Lua的基本構造點,在使用的時候,會有一些可以規避的地方。比如在頻繁更新或者使用的代碼部分,不要反復申請table,這會使得虛擬機不斷的去進行內存分配。我們可以將這些頻繁使用的功能相同的table作為一個內部變量存儲,在第一次進行申請,當前更新后,將其內部的值賦值為nil,這樣下次再使用的時候,是在當前table的基礎上進行擴展,這樣采取擴展table而不是頻繁構建新table的方式,可以避免內存碎片的產生。

  此外在檢測代碼質量的時候,最好做好相關的工具,進行各種性能統計,我們才能得到實際的性能數據,通過性能數據的分析修改可能存在的問題點。比如對於高頻變化的數據,是否采用增刪改的方式更能提升性能,對於高頻檢測的方法,是否通過特殊的規則可以降低檢測的次數等等,這些都需要結合實際的應用設計來修改。

  當然,游戲還包含一部分的優化,就是UI部分的優化,這部分可以參考MMO雨松的博客,他主要負責這部分的工作,所以可以參考他的博客,不知道最近他有沒有更新博客,哈哈,估計太累了~

       好了,本文也算一個小結,后續我看還有什么需要繼續寫的,我會再接着寫博客,下篇文章見~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM