近期在一家公司負責H5游戲加載速度優化,這里把近期做的項目優化項做一個整理分享:(若文中有錯誤的地方,還請指出。)
分享流程:了解html渲染流程 -> html相關優化 -> http相關優化 -> 項目結構和游戲流程及優化 -> 游戲渲染相關優化 -> 代碼編寫優化
html渲染流程
HTML解析過程:構建DOM樹、構建CSSOM樹、根據DOM樹和CSSOM樹構建render樹、有了render樹就開始布局Layout、最后繪制paint。
1、構建DOM樹: 將HTML構建成一個DOM樹,也就是構建節點,把所有的節點都構建出來。
2、構建CSSOM: 解析css去構建CSSOM樹。
3、構建render樹: DOM已經構建好了,css也有了,瀏覽器就會根據這兩個來構造render樹。
4、布局: 當render樹有了,通過render樹,瀏覽器開始計算各個節點的位置和樣式。
5、繪制: 遍歷render樹,在頁面上繪制每個節點。
6、重排reflow: 當render樹繪制完成之后,比如JavaScript改變樣式或添加節點,這時候render樹就需要重新計算。
7、重繪repaint: 重新繪制頁面。
HTML整個解析過程看起來很簡單,但是我們要知道解析過程中css、js和dom的加載順序。我們都知道HTML是自上往下解析的,在解析過程中:
1、如果遇到link和style,那就就會去下載這些外部的css資源,但是css跟DOM的構建是並行的,就是說不會阻塞DOM樹的構建。
2、如果遇到script,那么頁面就會停止html的解析和渲染把控制權交給JavaScript,直到腳本加載完畢或者是執行完畢。
1> 沒有defer和async標簽的script會立即加載並執行。
2> 有async標簽的js,js的加載執行和html的解析和渲染並行。
3> 有defer標簽的js,js的加載和html的解析和渲染並行,但會在html解析完成后執行,在觸發DOMContentLoaded事件前執行。
3、頁面的渲染是依靠render樹,也就是說如果css沒有加載完成,頁面也不會渲染顯示。
4、JavaScript執行過程中有可能需要改變樣式,所以css加載也會阻塞JavaScript的加載。
5、JavaScript執行過程中如果操作DOM,但是DOM樹又是在JavaScript之后才能構建,就會報錯,找不到節點。
6、DOMContentLoaded和onload的區別:DOMContentLoaded在html解析完畢后執行,loload在頁面完全加載完成后執行(包括樣式和圖片)。
html相關優化
其中對我們項目首屏啟動速度影響最大的就是網絡請求,所以優化的重點就是使用文件緩存和減少Http請求(頁面中每發送一次請求,都會完成請求+響應這個完成的HTTP事務,會消耗性能,造成HTTP鏈接通道阻塞)。
1.html代碼壓縮。
2.減少頁面上引用的文件數量(非首屏依賴CSS或者js文件、圖片資源的引用,等首屏展示后靜默下載(按實際項目需求))。
3.減少域名查詢:DNS
查詢和解析域名也需要消耗時間,不同域名使用越少越好。
4.優化頁面加載順序:1.將css
文件放在head
中 2.js
放在body
的底部。
5.防抖動、節流.
6.css相關優化
1.正確使用 Display 屬性,因為 Display 屬性會影響頁面的渲染。
2.避免圖片和 iFrame 等空 Src。
3.盡量避免重設圖片大小。
4.避免 CSS 表達式。
5.移除空的 CSS 規則。
6.不濫用 Web 字體、Float。
7.不聲明過多的 Font-Size。
8.值為 0 時不需要單位。
以下貼出示例的html文件內容,以注釋進一步說明優化內容:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>title</title> <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1, minimum-scale=1,maximum-scale=1" /> <meta name="full-screen" content="yes" /> <meta name="x5-fullscreen" content="true" /> <meta name="360-fullscreen" content="true" /> <meta name="screen-orientation" content="portrait" /> <meta name="x5-orientation" content="portrait"> //初始化只加載首屏頁面渲染依賴的css文件,非初始化依賴css文件,等游戲主包js下載后初始化由WebCssManager.js動態引入 <link rel="stylesheet" href="./css/layout.css"> </head> <body onload="load()"> <script src="src/res/loading.js"></script> //游戲定制頁,在主包下載之前過度顯示,避免因下載主包導致首屏展示太慢,提高游戲體驗 <script> //SDK或者外部插件,全部由插件管理類管理,初始化依賴的SDK在管理類下載完后立即執行,其他插件或者渠道SDK等主包下載完成后下載 var loadPluginManager = function() { var script = document.createElement('script'); script.onload = function() {//下載js主包} script.onerror = function(err) {loadPluginManager();} script.src = "./plugin/webPluginManager.js"; document.body.appendChild(script); } loadPluginManager(); </script> <canvas id="gameCanvas" width="1136" height="640"></canvas></body> </html>
//html代碼邏輯盡量少,只引入少量文件避免過多的HTTP請求
HTTP相關優化
1.如果支持http合並請求就合並請求。合並資源,減少 HTTP 請求數,minify / gzip 壓縮,webP,lazyLoad。
靜態資源打包,因為瀏覽器下載靜態文件的時候是有線程數限制的:同一時間針對同一域名下的請求有一定數量限制,超過限制數目的請求會被阻塞。為了提高性能,服務器端會把js/css 合並成一個文件(因為都是文本嘛)再向客戶端輸出,這樣頁面能更快的展現。
2.緩存:HTTP 協議緩存請求,離線緩存 manifest,離線數據緩存localStorage。
項目結構和游戲流程及優化

示例:打包出來的項目結構(不含子包),其中主包js文件大小2.9MB左右,資源文件大概15MB(壓縮后,png類型資源在打包的時候會額外生成一份Webp格式(android使用webp、ios使用png))。
項目結構優化:
1.(棋牌大廳)游戲主包或者游戲子包(棋牌游戲)的發布直接覆蓋原包內容(便於沒變動文件使用緩存),其中主包目錄和子包目錄獨立分開,便於版本緩存管理。
資源相關優化:
1.打包並壓縮所有游戲js文件。
2.合並所有CSS文件(按自身項目情況而定)。
3.合並非初始化所需插件或者SDK的js文件(按自身項目情況而定)。
4.壓縮圖片資源,png紋理生成一份webp格式(並再次壓縮75%)供android使用(按自身項目情況而定,因為webp紋理下載后需要解碼,算上解碼耗時小圖使用webp不划算)。
5.合並所有資源集合圖plist文件(減少大量http請求數、IO)。
6.項目結構中大廳游戲根據版本號修改html文件和主包文件名,資源文件使用文件MD5命名,游戲子包根據服務器下發版本號區分http請求。
流程邏輯相關優化:
前言:
因為我們游戲APP、微信小程序、h5都是共同游戲服務器,針對h5通訊流程后端沒做任何優化,共用一套(連接大廳服務器(含請求進入大廳相關接口)、連接游戲服務器(webSocket)(含請求進入游戲房間相關接口)耗時長),充分利用通訊耗時做該做的事情(如下載圖片資源):異步執行游戲中同步順序執行的邏輯(按實際情況而定)。
游戲優化前流程(只列舉主要流程):
1.正常方式登錄大廳:預覽登錄資源 -> 連接大廳服務器 -> 請求進入大廳協議 -> 預覽大廳主界面資源及通用資源 -> 顯示大廳
2.通過分享連接進游戲:預覽登錄資源 -> 連接大廳服務器 -> 請求進入大廳協議 -> 預覽大廳主界面資源及通用資源 -> 顯示大廳 -> 連接游戲服務器 -> 請求進入房間 -> 預覽游戲資源 -> 顯示游戲房間界面
游戲優化后流程(按實際項目情況而定,主要保持異步耗時平衡):
1.正常方式登錄大廳:
(連接大廳服務器 -> 請求進入大廳協議) 同時異步執行 預覽登錄和大廳主界面及通用資源 -> 顯示大廳
2.通過分享連接進游戲:
(連接大廳服務器 -> 請求進入大廳協議) 同時異步執行 預覽登錄和通用資源 -> (連接游戲服務器 -> 請求進入房間) 同時異步執行 預覽游戲資源 -> 顯示游戲房間界面
詳細游戲優化相關說明:
1.連接大廳服務器、請求大廳相關接口的同時下載游戲資源(部分資源直接載入紋理緩存)(此處必須保持異步平衡,連接大廳耗時充分用來下載和預覽資源)(注意控制同時請求數量,因為大部分瀏覽器並發請求數是4-6個)。
2.進游戲房間同上。
3.異步執行影響游戲流程中部分獨立不互相影響的流程(如GPS、影響游戲流程的接口)。
4.通過分享進游戲,只預覽少量大廳通用資源和對應游戲資源,跳過大廳相關流程且不創建大廳相關界面,直接進游戲房間(通訊和游戲資源下載預覽同上,且保持異步平衡)。
游戲渲染優化
1.相同圖集里的圖一次性連續渲染完,減少Draw Call。
2.減少被遮擋或者超出可視區的單位渲染。
3.部分UI元素較多的界面或者場景建議使用分幀加載,元素量大的則規划渲染規則。
4.允許情況下減低幀率。
CPU
引發的問題:
- 由於短時間內的計算量太大,導致畫面流暢性降低,俗稱跳幀
- 發熱嚴重,耗電量高
常見的優化手段:
- 將計算分到多個邏輯幀中進行計算,避免短時間內的性能超過負荷,俗稱“分幀”(time-slice)。
- 將可以緩存的數據盡可能的緩存起來,避免重復計算和重復分配內存,常見的示例為“內存池”。
- 使用合理的算法和數據結構,比如:冒泡排序和直接插入排序在整體數組比較有序的情況下效率大大好於快速排序。把快排替換成是優化程序排序效率的一個常見的思路。
GPU
引發的問題:
- 發熱嚴重,耗電量高
- FPS降低
常見的優化手段:
- 優化美術資源,比如合理規划圖集,約定好模型的最大三角形面數,制定合理的粒子效果規范。這個可以說是游戲優化中最重要的一個,因此,技術美術在游戲開發中作用巨大。
- 簡化或者優化着色器(shader),如在游戲開始前就對Shader進行編譯和加載。
- 使用Batching,盡量減少DrawCall
- 使用平台推薦的壓縮格式,比如安卓平台的ETC1和IOS平台的PVRTC
IO和網絡
引發的問題:
- 網絡延遲甚至掉線
- 加載資源導致的跳幀
- 加載時間過長
常見的優化手段:
- 使用獨立的線程進行加載,有些引擎如Unity中還能利用協程
- 減少網絡包里面的冗余數據
- 合並小包,減少請求數據的次數
- 分幀對回包進行處理
- 限制一定時間內的發包頻率
內存
引發的問題:
- 閃退和卡死,比如安卓的Low Memory Killer會在低內存情況下殺掉內存占用過大的程序。
常見的優化手段
- 動態加載和卸載資源,比如在游戲內的時候,我們可以把游戲外的一些UI圖集卸載掉。
- 降低資源質量或屏幕分辨率,這是有損優化,一般作為最后的手段
代碼編寫優化
這里先推薦一本書:《代碼整潔之道》:代碼質量與其整潔度成正比。干凈的代碼,既在質量上較為可靠,也為后期維護、升級奠定了良好基礎。
js原生庫推薦 : Lodash (一個一致性、模塊化、高性能的 JavaScript 實用工具庫。它內部封裝了諸多對字符串、數組、對象等常見數據類型的處理函數)。
1.適當使用函數式編程。
2.盡量使用異步編程。
3.在js中盡量減少閉包的使用。
4.封裝盡量做到低耦合高內聚。
5.整理代碼規范,避免破窗效應。
6.設計模塊化、組件化、分層化。
7.避免使用全局數據。
8.避免編寫相似得函數。
9.養成不斷的批判對待自己代碼的習慣,尋找重新組織、改善結構和正交性機會。