原生Canvas游戲性能優(yōu)化

隨著微信小游戲的推出索守,其全面支持以往的H5游戲開(kāi)發(fā),微信借小游戲的社交方式徹底激活小程序抑片。同樣的卵佛,也算是重新吹起了H5游戲的風(fēng)口。

可以預(yù)見(jiàn)的是,借助小游戲的風(fēng)截汪,前端游戲開(kāi)發(fā)這一分支也會(huì)燃起來(lái)了疾牲。在游戲開(kāi)發(fā)中,最令人難受的也許就是性能優(yōu)化了吧衙解。本人在整理過(guò)往幾次游戲開(kāi)發(fā)的經(jīng)歷中阳柔,總結(jié)了一些常被忽視的優(yōu)化小措施,與諸君分享蚓峦。


針對(duì)游戲性能優(yōu)化舌剂,首先,我們要知道我們優(yōu)化的目標(biāo)是什么暑椰?往往我們覺(jué)得性能優(yōu)化很難霍转,是因?yàn)槲覀儾淮_定優(yōu)化目標(biāo)是什么,針對(duì)什么進(jìn)行優(yōu)化一汽。

在我看來(lái)谴忧,性能優(yōu)化的實(shí)質(zhì),實(shí)際上就是盡可能的減少等待時(shí)間和內(nèi)存使用角虫。

有了目標(biāo)有好辦了沾谓,接下來(lái),我們需要知道我們通過(guò)優(yōu)化哪些目標(biāo)可以減少代碼執(zhí)行和內(nèi)存使用戳鹅。我粗略的分為了3個(gè)方面:

  • Canvas均驶,原生canvas游戲,首要目標(biāo)自然就是canvas
  • 內(nèi)存使用枫虏,果7之前safari運(yùn)行內(nèi)存只有100M妇穴,濫用內(nèi)存直接給你強(qiáng)制鎖死
  • 多線程,兩條腿走路肯定比一條腿快多了啊

Canvas

離屏canvas

場(chǎng)景:針對(duì)需要大量使用且繪圖繁復(fù)的靜態(tài)場(chǎng)景

實(shí)現(xiàn):對(duì)象內(nèi)放置一個(gè)私有canvas隶债,初始化時(shí)將靜態(tài)場(chǎng)景繪制完備腾它,需要時(shí)直接拷貝內(nèi)置canvas的圖像即可

//每一幀重繪時(shí)
setInterval(function () {

    context.fillRect(x,y,width,height)
    context.arc(x,y,r,sA,eA)
    context.strokeText('hehe', x, y)
    
}, 1000/60)

設(shè)置離屏canvas

let background = {
    width: 400,
    height: 400,
    canvas: document.createElement('canvas'),
    init: () => {
        let self = this
        let ctx = self.canvas.getContext('2d')
        
        self.canvas.width = self.width
        self.canvas.height = self.height

        ctx.fillRect(x,y,width,height)
        ctx.arc(x,y,r,sA,eA)
        ctx.strokeText('hehe', x, y)

    }
    
}

background.init()

setInterval(() => {

    context.drawImage(background.canvas, background.width, background.height, 0, 0);
    
}, 1000/60)

不設(shè)置離屏canvas的情況下,每幀繪制會(huì)調(diào)用3次繪圖api死讹;設(shè)置離屏canvas后瞒滴,每幀只用調(diào)用一次api。

實(shí)質(zhì):減少調(diào)用api的次數(shù)赞警,減少代碼執(zhí)行語(yǔ)句妓忍,從而減少每幀渲染時(shí)間,從而提高動(dòng)畫(huà)流程度愧旦。

狀態(tài)修改

場(chǎng)景:針對(duì)需要頻繁修改canvas對(duì)象的渲染狀態(tài) (fillStyle, strokeStyle ...)

實(shí)現(xiàn):按canvas狀態(tài)分別繪制世剖,而不是按對(duì)象進(jìn)行繪制

混合繪制


for (let i = 0; i < line.length; i++) {

    let e = line[i]

    context.fillStyle = i % 2 ? '#000': '#fff'

    context.fillRect(e.x, e.y, e.width, e.height)

}

不同狀態(tài)分別繪制:


context.fillStyle = '#000'

for (let i = 0; i < line.length / 2 - 1; i++) {

    let e = line[i * 2 + 1]

    context.fillRect(e.x, e.y, e.width, e.height)

}

context.fillStyle = '#fff'

for (let i = 0; i < line.length / 2 - 1; i++) {

    let e = line[i * 2]
    
    context.fillRect(e.x, e.y, e.width, e.height)

}

前后比較看,雖然循環(huán)次數(shù)沒(méi)變笤虫,但循環(huán)內(nèi)調(diào)用的語(yǔ)句變少了旁瘫,即不在循環(huán)內(nèi)修改canvas狀態(tài)了祖凫。

實(shí)質(zhì):減少canvas api的調(diào)用,不用在每次根據(jù)對(duì)象屬性去修改canvas的狀態(tài)酬凳,而是將具有相同狀態(tài)的對(duì)象提出蝙场,批量渲染。

分層和局部重繪

場(chǎng)景:針對(duì)場(chǎng)景中大背景變化緩慢粱年,而角色的狀態(tài)變換頻繁

實(shí)現(xiàn):將場(chǎng)景按狀態(tài)變換快慢進(jìn)行層次劃分售滤,設(shè)置不同的透明度和z-index進(jìn)行層級(jí)疊加。

分層.jpg

實(shí)質(zhì):通過(guò)分層台诗,對(duì)連續(xù)幀中的相同場(chǎng)景不重復(fù)渲染完箩,減少渲染所需的canvas api的調(diào)用。

但在微信小游戲中拉队,本方法不能使用弊知,因?yàn)槲⑿判∮螒蛑杏腥治ㄒ籧anvas,其他canvas都是離屏canvas粱快,不能顯示秩彤。

requestAnimationFrame

這個(gè)不存在什么場(chǎng)景,就是一把梭事哭,無(wú)腦直接上RAF漫雷,別再setInterval了。

簡(jiǎn)單點(diǎn)說(shuō)鳍咱,RAF是瀏覽器根據(jù)頁(yè)面渲染的情況降盹,自行選擇下一幀繪制的時(shí)機(jī)。

但是有一個(gè)tip需要注意谤辜,RAF不管理回調(diào)函數(shù)蓄坏,即在RAF回調(diào)被執(zhí)行前,如果RAF多次調(diào)用丑念,其回調(diào)函數(shù)也會(huì)多次調(diào)用涡戳。所以需要做好防抖節(jié)流。不然會(huì)導(dǎo)致RAF的回調(diào)函數(shù)在同一幀中重復(fù)調(diào)用脯倚,造成不必要的計(jì)算和渲染的消耗渔彰。

const animation = timestamp => console.log('animation called at', timestamp)

window.requestAnimationFrame(animation)
window.requestAnimationFrame(animation)

內(nèi)存優(yōu)化

對(duì)象池

場(chǎng)景:針對(duì)游戲中需要頻繁更新和刪除 的角色

實(shí)現(xiàn):對(duì)象池維護(hù)一個(gè)裝著空閑對(duì)象的池子,如果需要對(duì)象的時(shí)候挠将,不是直接new胳岂,而是從對(duì)象池中取出编整,如果對(duì)象池中沒(méi)有空閑對(duì)象舔稀,則新建一個(gè)空閑對(duì)象。

const __ = {
  poolDic: Symbol('poolDic')
}

/**
 * 簡(jiǎn)易的對(duì)象池實(shí)現(xiàn)
 * 用于對(duì)象的存貯和重復(fù)使用
 * 可以有效減少對(duì)象創(chuàng)建開(kāi)銷和避免頻繁的垃圾回收
 * 提高游戲性能
 */
export default class Pool {
  constructor() {
    this[__.poolDic] = {}
  }

  /**
   * 根據(jù)對(duì)象標(biāo)識(shí)符
   * 獲取對(duì)應(yīng)的對(duì)象池
   */
  getPoolBySign(name) {
    return this[__.poolDic][name] || ( this[__.poolDic][name] = [] )
  }

  /**
   * 根據(jù)傳入的對(duì)象標(biāo)識(shí)符掌测,查詢對(duì)象池
   * 對(duì)象池為空創(chuàng)建新的類内贮,否則從對(duì)象池中取
   */
  getItemByClass(name, className) {
    let pool = this.getPoolBySign(name)

    let result = (  pool.length
                  ? pool.shift()
                  : new Object()  )

    return result
  }

  /**
   * 將對(duì)象回收到對(duì)象池
   * 方便后續(xù)繼續(xù)使用
   */
  recover(name, instance) {
    this.getPoolBySign(name).push(instance)
  }
}

實(shí)質(zhì):減少內(nèi)存的使用产园。每次創(chuàng)建一個(gè)對(duì)象,都需要分配一點(diǎn)內(nèi)存夜郁,而由于瀏覽器的回收機(jī)制什燕,導(dǎo)致會(huì)有大量無(wú)用的對(duì)象的累加,白白消耗大量的內(nèi)存竞端。


多線程

Worker

場(chǎng)景:針對(duì)需要進(jìn)行大量計(jì)算任務(wù)

實(shí)現(xiàn):使用worker單獨(dú)開(kāi)啟線程進(jìn)行并行計(jì)算屎即,主線程仍執(zhí)行自己的任務(wù)。

實(shí)質(zhì)就是并行計(jì)算事富,避免進(jìn)程堵塞技俐。任務(wù)計(jì)算需要的時(shí)間是不會(huì)減少的,形象點(diǎn)來(lái)說(shuō)就是從一條腿走路變成兩條腿走路

//main.js
//創(chuàng)建worker線程
let worker = new Worker('worker.js')
//監(jiān)聽(tīng)worker線程的返回事件
worker.onmessage = (e) => {
    //e worker線程的返回對(duì)象
}
//發(fā)送消息
worker.postMessage(obj)

//worker.js
//監(jiān)聽(tīng)主線程的執(zhí)行請(qǐng)求
onmessage = (e) => {
    //執(zhí)行對(duì)象e
    
    postMessage(result)
}

實(shí)質(zhì):并行計(jì)算统台,可以認(rèn)為計(jì)算任務(wù)與主線程工作是異步的雕擂,互不干擾。因?yàn)槭菍⒂?jì)算任務(wù)全部交給worker贱勃,所有計(jì)算時(shí)間是不會(huì)減少的井赌。

對(duì)象池不僅可以針對(duì)對(duì)象,還可以針對(duì)worker進(jìn)行線程池的管理贵扰,有興趣的朋友可以試試仇穗。


其實(shí)除了上述3個(gè)方面,還有一個(gè)非常重要的優(yōu)化目標(biāo)戚绕,那就是網(wǎng)絡(luò)優(yōu)化仪缸,但這也是我們常說(shuō)的瀏覽器性能優(yōu)化的終點(diǎn)內(nèi)容,所以關(guān)于網(wǎng)絡(luò)優(yōu)化列肢,各位就請(qǐng)移步其他大神的文章恰画,我也就不再賣弄我那一點(diǎn)三腳貓技術(shù)了。各位朋友有什么其他的優(yōu)化措施的瓷马,歡迎交流拴还。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市欧聘,隨后出現(xiàn)的幾起案子片林,更是在濱河造成了極大的恐慌,老刑警劉巖怀骤,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件费封,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蒋伦,警方通過(guò)查閱死者的電腦和手機(jī)弓摘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)痕届,“玉大人韧献,你說(shuō)我怎么就攤上這事末患。” “怎么了锤窑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵璧针,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我渊啰,道長(zhǎng)探橱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任绘证,我火速辦了婚禮走搁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘迈窟。我一直安慰自己私植,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布车酣。 她就那樣靜靜地躺著曲稼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪湖员。 梳的紋絲不亂的頭發(fā)上贫悄,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音娘摔,去河邊找鬼窄坦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凳寺,可吹牛的內(nèi)容都是我干的鸭津。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肠缨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逆趋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起晒奕,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤闻书,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后脑慧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體魄眉,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年闷袒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坑律。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡霜运,死狀恐怖脾歇,靈堂內(nèi)的尸體忽然破棺而出蒋腮,到底是詐尸還是另有隱情淘捡,我是刑警寧澤藕各,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站焦除,受9級(jí)特大地震影響激况,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜膘魄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一乌逐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧创葡,春花似錦浙踢、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至骚露,卻和暖如春蹬挤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棘幸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工焰扳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人误续。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓吨悍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蹋嵌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子畜份,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容