1. 程式人生 > >技術乾貨丨《大天使之劍H5》主程與專案總監:H5遊戲的壓縮與優化經驗

技術乾貨丨《大天使之劍H5》主程與專案總監:H5遊戲的壓縮與優化經驗

2018年3月,三七互娛在其主辦的中國國際互動娛樂大會上稱,《大天使之劍H5》最高單日流水超4000萬元,而單月最高流水超過了1.8億元。

 

上週末,在極光網路與三七互娛聯合主辦的極光會客廳——“2D小遊戲開發實戰技術沙龍”上,《大天使之劍H5》的主程陳策與網路專案總監陳源分享了“大型H5遊戲如何登陸微信小遊戲”以及“遊戲效能優化”的研發經驗。

 

 

以下是兩位嘉賓的演講整理:

 

陳策:大型H5遊戲如何登陸微信小遊戲

 

隨著公司業務的發展,我們的專案往往要登陸各種平臺,微信小遊戲就是這些平臺的其中之一。

 

微信小遊戲是微信小程式的一個類目,它即點即玩,無需下載安裝,體驗輕便,可以和微信內的好友一起玩,比如PK,圍觀等。

 

但想讓自己的遊戲登陸微信小遊戲,會有一些方面的限制,下面我們主要說下《大天使之劍H5》這一專案登陸微信小遊戲受到的主要限制和解決辦法。

 

一、《大天使之劍H5》受到微信小遊戲的主要限制

 

1.所有分包大小不得超過8M:分包指的是在微信開發工具裡上傳的所有資源,包括JS程式碼和資源,一共不得大於8M;

 

2.單個包的大小不得大於4M:上傳的檔案裡,不能有大於4M的檔案;

 

3.JS必須放在分包裡才可以執行,載入進來的JS檔案只會被當成文字:載入進的JS文字,無法轉成可執行指令碼。

 

《大天使之劍H5》在登陸微信小遊戲前,整個專案大小約有400多M,光JS程式碼部分就有大約10M。除邏輯程式碼的其它資源(圖片、音效、配置等),可以在遊戲執行時進行載入,不用在開發工具裡上傳,但約10M的JS程式碼部分必須全部上傳。因此,《大天使之劍H5》想登陸微信小遊戲,必須縮小JS程式碼的大小。

 

二、現有壓縮工具UglifyJS部分功能簡介

 

Layabox引擎裡將專案的AS3部分生成JS時會進行一定的優化,這個功能應該是基於UglifyJS來實現的。其優化內容主要有:

 

1.去掉程式碼中無效的空白字元

 

2.去掉程式碼中的註釋

 

如圖所示:

 

 

3.把方法中區域性變數名縮短

 

如圖所示:

 

 

4.程式碼格式優化 (把程式碼改為更省字元的寫法)

 

如圖所示:

 

 

5.壓縮屬性名 (預設不開啟)

 

如圖所示:

 

 

我們先來看看這個例子,這個是一個類,在工具預設不開啟壓縮屬性名稱時,工具就只會壓例子裡橙色的兩處X和Y,因為這個是引數,也就是剛才說的方法裡定義的變數,this.x,this.y這都是不壓的,因為這個是屬性名。如果類名Point,方法名setTo,屬性名X,Y壓了,那其他使用的地方就要根著一起改,如果程式碼裡有用到反射來呼叫的,那就呼叫不到了。所以壓縮這些名稱是有風險的,這也就是工具預設不壓的原因。那這個功能不就廢了?不會,工具還提供了很多引數讓你可以設定不壓縮的名稱的列表,還允許你定義壓縮的名稱的正則表示式等等,其實還是可以使用的,只是還是要先整出一份針對自己專案的名稱資料出來,整理出來的不壓縮名稱集要和程式碼同步進行維護,這樣難度會比較大,所以《大天使之劍H5》專案並沒有使用這個功能。

 

小結:

 

《大天使之劍H5》專案現有的AS3程式碼程式碼在Layabox生成JS程式碼時,已經預設進行了上述前四點優化:

 

· 去掉程式碼中的無效的空白字元

· 去掉程式碼中的註釋

· 方法中的區域性變數名縮短

· 程式碼格式優化

 

但生成的JS程式碼有10M左右,還沒有達到微信小遊戲的要求,因此,為了縮小程式碼量,我們需要對我們的AS3程式碼再做一些優化,從而減少程式碼量。

三、減少程式程式碼

 

縮小程式碼量,最直接的方式就是減少程式碼裡的字元,這部分所作的優化,是在我們專案的AS3程式碼部分所做的優化,這些優化包括以下幾點:

 

1.將介面佈局的資料改為從外部載入

 

Layabox的UI編輯器編輯後,會生成對應的UI類檔案,其內容如圖:

 

 

這裡面主要內容是UI的佈局資料,不用涉及到邏輯,可提取出來。做為文字檔案儲存,在其對應的介面初始化時再載入,在Layabox中,我們可以通過修改UI模式來做調整:

 

 

我們可以隨意建立一個UI來做測試,如圖所示:

 

 

我們可以看到,新建一個TestPageUI介面,使用內嵌模式生成的TestPageUI.as檔案共有3283位元組,而使用分離模式,生成的檔案只有579位元組。圖中右邊綠色部分表示的是減掉的部分程式碼,為我們縮小約80%左右的UI佈局相關程式碼。

 

 

而在《大天使之劍H5》中,UI佈局檔案目前有931個,使用這種方式幫我們減少了1.8M的程式碼。

 

2.將類裡不使用的匯入刪除

 

我們在開發時,手誤import進入的一些專案並未使用到的類,需要將這些import刪除。如:import Sprite3D 類,2D遊戲用不上3D相關的東西,無需匯入。

 

3.將方法裡的this用區域性變數代替

 

當我們的AS3程式碼轉成JS後,類中的屬性名在方法中的訪問形式,是會在其前面加上this.,這裡的this我們是否能減少呢?如下圖所示:

 

 

上面的方法裡有若干個this。如果把this用一個區域性分變數來代替,那就是下面的方法這樣。這裡的區域性變數用的是一個字元的變數,因為最後我們專案會用UglifyJS來壓,所有方法內定義的變數,只要不超過54個,都會是單字元的變數。我們看優化前,一共是有四個this,他們佔用16個字元。優化後,四個this變成了四個n,是4個字元,還多出一個賦值語句,這個語句包括中間的空格,包手後邊的分號,一共是11個字元,加上四個n就是15個字元,比優化前少了一個字元。如果這個方法裡我再加一個this,那優化前的程式碼,就要增加4個字元,而優化後的程式碼只需要增加1個字元,所以方法裡的this越多,能減少的大小也越多。因此:

 

 

總結來說就是隻要方法裡的this關鍵字多於3個,就能省字元數量。而且this越多,省得也就越多。我在編譯好的JS程式碼裡搜尋,一共是有近18號個this,這個就可以省很多了。

 

但這個優化要注意,每個function都是一個作用域,每個作用域裡的this指代都是不一樣的,所以每個不同的作用域裡的this要分別進行計算,也就是說,方法裡如果有一個函式,那在計算方法裡的this數量時,不應該計算函式裡出現的this。第二個,是有一些方法,已經寫了內部的變數賦值是this的,那就可以利用這個已經存在的變數,可以進一步減少字元。這個優化最終省了0.3M。這個優化優化的不僅僅是程式碼的大小,因為在JS裡,區域性變數的呼叫效率是比this要高的,所以這還可以加快遊戲的執行效率。

 

4.壓縮包名、類名、方法名、屬性名

 

這一步做的事,是把UglifyJS預設不壓的名稱,我們用我們的方式把他給壓了:

 

① 相同的名稱壓為相同的短名稱,把程式碼裡出現的相同的名稱,壓為相同的短名稱

 

② obj.abc 與 obj["abc"] 區別處理:

 

預設壓縮規則:obj.abc 寫法的屬性名會被壓縮,obj[“abc”] 寫法的字串部分不會被壓縮。

 

想到這種方式,主要是因為UglifyJS也考慮到有些屬性名壓縮後,可能會引起某些屬性訪問不到,UglifyJS的做法是提供個不壓的屬性名的配置列表,但是這僅僅是個配置列表,我們通過這個列表無法定位到程式碼裡有用到這些屬性名的地方,有一定的侷限性,因此,通過obj.abc 與 obj["abc"] 區別處理,我們可以在寫程式碼的時候就用不同的寫法告訴編譯器,這裡的屬性名是否要壓。

 

有人會有疑問,用obj[“abc”]的寫法,會比obj.abc的寫法多了三個字元。不用擔心,因為在最後用UglifyJS壓縮的時候,會將[]語法轉成.語法的。

 

③ 自定義標籤 /*[ZIP-JSON]*/

 

為了不破壞程式設計師的程式設計習慣,我們在不得不用字串的形式去

訪問屬性時,想到了下面的解決方案:在字串前加上一個/*[ZIP-JSON]*/

如:

 

 

我們常用的緩動類的用法中,上圖的”x”和”y”是屬性名,我們預設情況下字串是不會被壓縮的。此時我們可以在程式碼中加上/*[ZIP-JSON]*/標籤,如:

 

 

這樣,”x””y”就會被壓縮成對應的名字了。

 

通過這些處理,我們的程式碼的寫法就會有以下幾種形式:

 

 

形式1:label 這個屬性名,並不會被壓縮,訪問時也用它原名訪問

 

形式2:label這個屬性名在定義時就被壓縮了,可以通過也會被壓縮的.語法去訪問

 

形式3:label這個屬性名在定義和訪問時都有被壓縮

 

當然,/*[ZIP-JSON]*/做為註釋塊,在最後AS3被轉成JS時,UglifyJS會幫我們把註釋塊給清除掉的,不用擔心加了註釋塊反而程式碼會大的問題。

 

④ 特別處理

 

諸如 hasOwnProperty、propertyIsEnumerable 等方法,以及Layabox 裡的 __JS__ 方法。

 

方法裡傳入的字串,其實是屬性名稱。因為預設屬性名稱是會被壓縮的,而字串是不會被壓縮的,所以對這些方法中名字,我們預設進行壓縮。但要壓縮成什麼樣的名字呢?

 

 

上面我們講的,是哪些名稱要壓,壓的時候要注意的一些點,那最終這些名稱,要壓成怎麼樣呢?當然是壓到越小越好,那最小是多少呢?一個字元是最好的。我們先看看要做名稱,受哪些限制。名稱是可以由字母組成的,字母是區分大小寫的,還可以使用數字,還有下劃線,還有一個比較不常用的$符號,要注意的是,名稱的首字元不能是數字。

 

如果我們把名稱全用單個字元,可以有多少個名稱呢?26個小寫字母,26個大寫字母,10個數字不有用,加兩個符號,就是54個。那雙字元的名稱呢,就有3456個,三個字元就是22萬個。當然這裡能用的還會少幾個,為什麼呢?因為比如像as,is,if,for這樣的名稱,也是兩個字元三個字元,但他們是關鍵字,名稱不能和關鍵字重名,不過這樣的關鍵字也不多,不多於10個。三個字元可以有22萬個名稱,那是否夠我們使用了呢?

 

 

上圖是《大天使之劍H5》中所用到名稱字數的分佈圖,一共有4萬個名稱,那兩個字元的3千多個肯定是不夠的,三個字元的22萬個就完全可以滿足了。而且我們看看這些名稱的長度分別是多少,可以從表裡看到,95%以上的名稱是大於三個字元的,那可以優化的空間就比較大了。最終我們專案把名稱都壓縮完後,一共減少了1.9M。在壓縮名稱這裡,大部分工作都是用編輯工具去完成的,有一部分是要修改原始碼的,也寫了一個工具去處理,儘量做到用工具去完成,不然要手動去修改,工作量會變得超大。

 

上述的五點對《大天使之劍H5》優化過後,結果如圖所示:

 

 

《大天使之劍H5》的程式碼由約10M減小到約5.1M的大小

 

⑤ 一些還未在《大天使之劍H5》中做的可取優化

 

· 靜態常量編譯為JS後是把值寫在使用的地方,這不一定是最優的

· 方法裡使用的屬性賦值給區域性變數再使用

· 使用(param)=>{}代替function(param){}

· 某類只有一個子類時可減少繼承鏈

· 包結構可以減化

 

四.使用分包

 

在上述的優化後,《大天使之劍H5》的主程式碼還有5.1M,任然需要對這5.1M進行拆分,這5.1M中,有遊戲引擎的部分佔了0.7M,其他小檔案佔了0.2M,剩餘的主程式還有4.2M,剩餘的4.2M可以通過分包處理。

 

怎樣分包我們可以在騰訊和layabox的官網上找到詳細的教程,下面是相關連結

騰訊官網關於分包載入的說明:

 

https://developers.weixin.qq.com/minigame/dev/tutorial/base/subpackages.html

 

Layabox官網關於微信小遊戲分包的示例:

 

https://ldc.layabox.com/doc/?nav=zh-js-5-0-6

 

關於在layabox下是如何分包的,在這裡簡單說一下:

 

 

在專案的根目錄下,建立一個module.def檔案,這是一個文字檔案,裡邊的內容如下,就可以在編譯後,生成主檔案的JS和模組.js兩個檔案。如果要分為多個模組的,就把這個結構寫多個,都定義好模組名稱和模組對應的程式碼所在的資料夾就可以了。

 

看起來是不是很簡單?但我們隨意的指定一個資料夾下的程式碼被編譯為一個模組獨立出去後,在執行時,就會出錯上圖紅色部分的一個報錯。

 

出現這個報錯的原因是主檔案會先執行,主檔案裡引用了模組裡的XXX,而執行到這裡的時候,模組還沒有被載入,所以xxx沒有被定義,所以報錯了。

 

所以,要做好分模組前,就需要對專案進行解偶。要解偶的話,那就得知道,我們分到模組裡的是什麼功能,這個功能裡如果需要和主程式進行互動,就需要設計相應的中轉機制來進行解偶。

 

如果專案是新專案,我們可以在一開始設計遊戲的時候就做好這部分內容,在功能進行開發中,會知道這個功能是要分出去的模組,要以怎麼樣的開發規則進行開發,就可以做到解偶進而做到分模組。

 

但我們的遊戲已經上線快一年了,如果現在才加入這樣的機制相當於我們要對需要放到模組裡的功能進行重構,這樣做工作量大,而且功能還要重新測試,開發週期開,還容易出BUG。後來我想了一個不需要解偶也可能分模組的辦法。

 

 

我在說我們辦法前,我要說明一點,我這個辦法只是為了解決在小遊戲裡做到分包小於4M而做的,與分模組的設計思路是不太一樣的。分模組的目的是什麼呢?是把還沒有使用到的功能放到模組中去,需要使用到的時候,再去載入對應的模組。而我的做法,是需要在進遊戲前,需要把所有模組都載入進來,無論模組的功能是否需要,也不管模組裡到底是什麼功能。

 

為了說清楚這點,我們先來看看JS的類。JS的類定義在書寫的時候,是否有先後順序?看看這段程式碼,這裡定義了一個父類,然後再定義了一個子類。這裡我們是否能先寫定義一個子類,再寫定義一個父類嗎?大家注意下子類的定義裡,是需要將父類的定義傳入的,如果先寫子類的定義,那傳入的父類定義就是一個undefined,裡邊在調到到父類定義裡的屬性時,就會報錯。

 

所以父類必須要寫在子類前邊。換成分模組的情況下是怎麼樣呢?假設我們現在有兩個檔案,先被載入的叫模組A,後被載入的叫模組B。模組A裡有一個子類的定義,在模組B裡有其他類的定義,也包括這個模組A裡的子類的父類的定義。在模組A被載入完成後,執行到子類的定義時,就呼叫到了他的父類,因為模組B還未載入,所以必然報錯了。這裡我們要怎麼避免報錯呢?很簡單,把父類的定義,也放到模組A裡,那就不會報錯了。如果父類還有父類,而且也在模組B裡的,那記得也要把他的父類也拿到模組A裡。

 

具體我們是怎麼操作把父類也放到模組A裡的呢?我們只需要在呼叫Laya的編譯器前,把父類的as檔案考到模組A的資料夾裡就可以了。父類裡的包名什麼的,都不需要做修改。要知道包名在AS裡雖然是和檔案存放的路徑相匹配的,但在用laya編譯時,是不檢測包名是否和路徑匹配的,最終生成到JS裡的,是檔案裡寫的包名,路徑只做為是放到哪個模組的依據。

 

 

剛才我們講的是父類是在另一個模組的情況下引起的報錯。除了這個,還有沒有其他情況呢?有的,比如說我們在剛才的模組A裡的類,在未解偶的邏輯裡,是肯定有呼叫到模組B的類。不過在初始化時,應該不會執行到業務邏輯裡,那為什麼會報錯呢?我們來看看模組A裡的程式碼。模組A裡的頭幾行一般是長這個樣子的,第二行,是將Laya引擎裡的一些公共方法定義了短名稱的變數,方便在邏輯裡呼叫。

 

第三行開始,就是把這個模組裡引用到的類,都用類的名稱做變數名賦值,這樣就方便在使用的時候,不需要寫包括包名的類名稱。也就是我們直接寫在AS裡的程式碼,不用做太多修改就可以在變成可執行的JS。要注意到,這幾行程式碼,是在這個JS檔案初始化的時候就會被執行的。注意看第四行,我們有一個類,假設這個類叫ClassName,這個類是定義在模組B裡的,那這句賦值語句就會因為模組B還未載入而找不到ClassName的定義,然後報錯。而且這個類之所以出現在這裡,就是因為在該模組的某個類裡使用了它。

 

這裡我們就明白了,寫在類的方法裡的程式碼,在初始化的時候是不會被執行的,所以寫了模組B裡定義的類也不會在初始化時報錯,被匯入的類會被寫到模組的最開頭,會在初始化時執行到就會報錯。那我們這麼處理,所有模組A裡的類,如果import的類是模組B的類,那就把這個import刪除掉。並且把所有使用這個類的地方,都寫成用這個函式呼叫的字串的包括包名的類名。

 

 

好像這樣改,需要改的地方會比較多,而且生成的程式碼裡,也會有多處長名稱,我改成了這樣,在類里加一個靜態的變數,讓他等於這個函式,那程式碼裡就不用修改,使用到這個類名的地方,其實呼叫的是這個定義的靜態變數。而且編譯為JS後,靜態變數的定義會變成get函式來得到這個值也就是在使用的地方才會呼叫,而不是初始化的時候。這樣就解決了模組A的程式碼裡呼叫到模組B的類的引起在初始化的時候報錯的問題。

 

 

做好剛才的兩個地方就完成了嗎?我們再回想一下兩個情況,都是模組A裡的類,如果引用了模組B裡的類,那就想辦法把他的引用去掉,讓他在首次執行時才呼叫。也就是說,在編譯為JS的時間,模組A裡的類是被當成沒有引用模組B裡的那個類了,那如果模組B裡的那個類,假設叫SimgleClass,只有唯一的一個引用就是模組A裡的類引用了,現在把模組A裡的引用去掉了,那SimgleClass就沒有類引用到它了,也就是編譯的時候,會把這個類不編譯到JS裡去。那執行的時候就會因為找不到定義而報錯。所以要在SimageClass里加上強制編譯的標籤,這個是由LayaBox提供的標籤,當有這個標籤時,這個類就算沒有引用,也會被編譯到JS裡去。

 

最終的結果,如圖所示:

 

 

這4.2M的主程式檔案,就被拆分為了一個1.2M和一個3M,小的那個和引擎程式碼還有其他一堆小檔案一起打包成一個包,共2.1M,3M的那個檔案就一個包。在程式執行的時候,會在進入遊戲的時候,先載入2.1M的包,完成後會立即載入3M的包。兩個包都載入完成後,才會進入遊戲。

 

陳源:遊戲效能優化

 

遊戲效能問題,往往是我們遊戲程式設計師最關心的問題,對於這個問題,我在這裡總結一下我關於遊戲效能優化的八個理念:

理念一:善於從問題的表象上出發進行優化

 

遊戲出現問題時,最直接的表現就是卡,造成卡頓的問題又有很多不同的情況。在解決卡頓問題前,我們應該最先排除是否是外部問題造成的卡頓,外部問題:網路差,硬體差,系統問題等。排除是外部問題導致的卡後,我們可以根據卡頓的現象來定位問題。

 

1.輕微的較頻繁卡頓

 

1)幀率

 

根據遊戲自身來設定幀率範圍,一般遊戲建議30幀就夠了

 

2)Drawcall

 

在瞭解什麼是drawcall後,我們知道,過高的drawcall會導致卡頓,這裡就介紹一些減少drawcall的方法:

 

A.檢視drawcall

 

在layabox中,新增程式碼DebugTool.showStatu = true;

 

B.連續渲染相同圖集裡的圖,只會執行一次drawcall

 

C.UI上drawcall優化

 

優化的思路就是儘可能讓相同圖集裡的圖一次性連續渲染完,舉例來說:在layabox中開啟一個UI,層級窗體裡看到介面裡的子物件,如下圖:

 

 

我們可以看到看每一個子物件前邊,都有一個小圓點,這個圓點的顏色代表了他來自哪個圖集,相同顏色的圓點代表是同一個圖集。渲染UI時,UI上的每個子物件是按照自上而下的順序去渲染的。在不影響介面的情況下,把介面裡各元件的層級調整一下,可以達到減少drawcall的目的。調整後,如圖所示:

 

 

優化前:會有6次drawcall

 

優化後:只有3次drawcall

 

D.場景上的drawcall優化

 

拿場景中的人物來舉例:

 

假設一個人物的某套裝資源在一個圖集裡。

 

 

那麼場景裡連續渲染穿這個套裝的人物,只用1次drawcall。實際上在遊戲裡,穿著相同套裝的人不會理想的按順序出現。一個場景裡會有許多穿著不同套裝的人物,而每個套裝在一個圖集。也就是說,場景裡,渲染N個這樣的人物就需要無限接近於N次drawcall。

 

當每個人物還有影子。

 

 

影子的資源,肯定是在不同於各個套裝資源的圖集裡。

 

情況1:人物和影子是在同一個容器裡處理,每一個容器就是一個人物,這種情況就會出現,渲染單位A,會使用兩個圖集套裝圖集+影子圖集有兩次drawcall。當渲染N個人物,就有N*2次drawcall

 

情況2:把渲染影子拆出來,當渲染完所有人物後,再根據所有人物的位置,繪製影子。這種情況,渲染N個人物,只會有N+1次drawcall顯然情況2的處理方式更優。

 

當每個人物還有名字、稱號、血條等等元素,若這些元素是按照上面情況1的處理,那drawcall就有N*M次。把這些元素按照上面情況2的處理,顯然可以減少大量的drawcall

 

E.其他減少drawcall的方法

 

如:減少被遮擋的單位渲染

圖集的控制

 

3)有較高消耗運算,特別是enterframe和for迴圈裡面的

 

注意程式碼邏輯優化,減少演算法複雜度

 

4)資源載入快取有問題/資源太零散,造成I/O過高

 

優化資源載入策略

 

5)日誌列印

 

減少日誌列印

 

6)限制底層繪製解析度

 

2.突然大卡頓

 

· 某些不定時的操作,迴圈裡複雜度高

可能是執行到一些複雜度高的迴圈處,導致突然大卡頓

 

· 協議包過大

 

注意協議包優化,優化方式諸如:

 

① 刪除不用欄位

② 整合小欄位

 

如某協議中的欄位

 

var a:int;

var b:int;

var c:int;

 

假如上述a,b,c的值只會是十位數以下時,可以整合成一個欄位:

 

var d:int;

 

d的每一位為ABCDEF

 

AB為a的值,CD為b的值,EF為c的值

 

比如d = 123456

 

那麼a為12,b為34,c為56

 

這樣做的好處,原來需要12個位元組的資料,現在只用4個位元組就可以了。

 

③ 欄位型別選擇

 

如用int還是short

 

只有兩個值的情況選擇用boolean

 

④ 儘量避免使用字串

 

協議中的字串,儘量通過ID傳送

 

ID對應的字串在程式碼裡做好對映或配置

 

· 垃圾回收負荷過重

單位是否是頻繁初始化,用完後就銷燬?

是否需要使用物件池

 

如果卡頓發生UI上:

 

· 面板內容初始化分幀處理

· 圖片和特效尺寸,圖集依賴規劃是否合理

· UI面板是否分頁籤

· UI內特效,3D模型等次要元素延遲初始化

· List優化,動態初始化,迴圈使用

 

3.持續地越來越嚴重卡頓

 

· 是否有物件/物件用完了隱藏掉,而沒有被刪除,越積越多

如:飄血、事件監聽等

· 記憶體洩漏,GC越來越頻繁

 

4.卡頓到閃退

 

· 記憶體過高,記憶體有洩漏

· 檢測是否有報錯或死迴圈等問題

 

理念二:記憶體為主

 

很多能感知的較大卡頓都是垃圾回收引起的;

 

記憶體問題往往比CPU問題更難解決,甚至需要大規模重構;

 

降低記憶體會使得遊戲更加輕快,可以緩解其他較小問題。

 

理念三:記憶體使用策略比記憶體釋放策略更重要

 

關於記憶體使用的策略,往往在專案的初期就應該制定好,下面有幾點建議:

 

· new物件必須由Factory或者Manager進行統一管理,這點做好了,對後期排查記憶體問題尤為重要;

 

· View和Model要完全分離;

· View的建立細化到每幀,根據效能情況建立;

· FpsManager按照幀率動態調整閥值;

· 合理地運用物件池:“頻繁建立和銷燬”的“小型”物件採用物件池。

 

理念四:重視資源壓縮

 

遊戲程式中,大量的記憶體來自於資源,合理的壓縮資源是減小記憶體行之有效的辦法。關於資源壓縮,這裡提一些建議:

 

· 重視制定製作流程,技術標準

資源的製作不能隨心所欲,必須擁有一定的標準,例如:

 

① 原始圖片資源使用jpg還是png

② 是否是可以用映象處理的圖片資源

③ 相似的資源是否可以統一

④ 避免程式上使用濾鏡和灰化等

⑤ 一些圖片資源上不起眼的光效或羽化效果等是否可以不用

⑥ 美術字體圖片中,相同文字的拆分與組合

⑦ 九宮格拉伸圖片的概念

 

· 合理優化動作、特效等資源序列幀

許多人物動作、特效等資源,美術給出的效果十分精細。在處理記憶體問題上,可以考慮對這些資源做如下處理:

 

① 抽幀

一些影響不大的關鍵幀是否可以刪除

 

② 縮小再放大

一些不起眼的部位,序列幀可以按比例縮小,程式使用時,再放大

 

③ 砍方向

人物向左和向右的資源是否可以通過旋轉其中一個動作實現?

 

是否所有方向的資源都要用到?

 

· 對稱的資源砍半/四分之一映象使用

· 序列幀特效用IDE製作替代

· UI全屏解析度選擇 1024畫素

· icon儘量使用 64*64 的格式

· 使用pvrtc etc ,png8格式

· UI背景使用最接近的某個2的次方的尺寸

· 背景圖不要打到圖集中,並且儘量用jpg

· 少用遮罩

· 音效使用單聲道

 

理念五:遮蔽策略

 

在遊戲出現效能瓶頸的時候,我們不得不考慮遮蔽一些遊戲內容的顯示,比如一些次要的場景單位、特效等。遮蔽必然會減弱玩家的遊戲體驗,但是,比起卡頓甚至是閃退,適當的遮蔽規則是必要的。

 

·加入遮蔽設定,效能差的時候自動開啟

·遮蔽策略要覆蓋各類顯示物件

·某些場景使用統一模型

 

理念六:不要忽略看起來小的地方

 

正所謂積少成多,一些看起來小的地方,卻用了不太合理的處理方式,往往也影響著遊戲的效能,在《大天使之劍H5》中,我們就對如下這些地方做了些優化:

 

· 配置表資料優化

· String陣列方案

· 相似String的處理

· 版本號檔案,採用樹形儲存減少URL欄位的重複度

· 數值型別廣泛使用變長

· 解析戰報,分段解析

 

理念七:深入學習開發者工具的使用

 

在除錯遊戲時,我們往往要依賴開發者工具來獲悉遊戲的記憶體變化、CPU的使用、遊戲內物件的整體情況、資源的應用情況、甚至是我們關注的變數值等等。善於使用各種開發者工具能讓我們事半功倍。

 

· 使用谷歌瀏覽器的開發者工具

 

layabox開發的H5遊戲預設是用谷歌瀏覽器除錯的,這裡我們稍微講下谷歌瀏覽器的開發者工具的運用。遊戲執行在谷歌瀏覽器時,按F12可以開啟開發者工具,他的一些常用功能有:

 

① console的使用

 

 

Console的介面如上圖所示,它主要顯示著我們遊戲執行時的日誌等資訊。每條日誌的後面,有連結可以快速跳轉到輸出日誌的程式碼處,在console的下方,有輸入框功能,我們可以通過這裡輸入程式碼改變當前遊戲內的數值、用不同方式列印日誌等。

 

② 除錯

 

我們可以在工具的Sources選項卡下找到我們要除錯的JS,進行斷點或條件斷點除錯。

 

③ NetWork

 

NetWork面板提供了有關已經下載和載入過的資源的詳細資訊

 

在這裡我們可以很直觀的找到一些載入耗時較長的資源進行優化

 

④ TimeLine

 

TimeLine介面中,我們可以看到關於時間開銷的完整概述。

 

 

如圖所示,通常我們在遊戲效能表現差的情況下,在TimeLine介面點選左上角的錄製按鈕,錄製一段時間後,點選完成,它會幫我們蒐集到在錄製的這段時間裡,遊戲每一幀的執行狀況。

 

· 綠色的幀是在幀時間內處理完需求處理的事情的

 

· 紅色的幀是處理時間大於幀應該有的時間的,也就是卡了的幀

 

我們可以重點看下紅的幀,在下邊可以看到是哪個方法佔了多少時間,以及這個檔案裡又是呼叫哪些方法,分別調多少時間

 

 

在圖中左下部分,我們還可以看到程式碼邏輯和渲染的比重,可以用來判斷可以做什麼優化,怎麼優化。這個功能,對程式的參考決不止我提到這些,這裡有很多資訊幫我們分析找到問題,可以對我們優化提供很多幫助。

 

⑤ Profiles

 

Profiles介面中,我們可以使用快照功能。最常用的還是Take Heap Snapshot功能。它可以記錄當前記憶體分佈的詳細資訊,多張記憶體快照還可進行比對,分析在不同的時間點,記憶體具體有哪些變化。

 

理念八:抓大放小,先抗住再優化

 

沒有一個性能問題是由單一問題造成的;

 

單次單點修改,注意記錄便於前後對比找到關鍵問題;

 

除錯崩潰的時候一定要注重日誌,一行一行看總會找到Keyword。