CocosCreator ScrollView優(yōu)化系列之分幀加載
一、 前言
JS是單線程的,也就意味著所有任務(wù)需要排隊(duì),只有當(dāng)前一個(gè)任務(wù)結(jié)束了,后一個(gè)任務(wù)才會(huì)執(zhí)行。如果前一個(gè)任務(wù)耗時(shí)很長(zhǎng),后一個(gè)任務(wù)就不得不一直等著。
Cocos Creator 是采用 Java Script/Type Script語(yǔ)言開(kāi)發(fā),本質(zhì)上是JS,同樣會(huì)擁有以上特征。特別地,如果使用不當(dāng),極有可能導(dǎo)致界面卡頓。
比如:在為一個(gè)ScrollView的Content創(chuàng)建500個(gè)節(jié)點(diǎn)的的時(shí)候,可能就會(huì)出現(xiàn)下面界面卡死的問(wèn)題
PS:本來(lái)加載過(guò)程中有一個(gè)loading對(duì)話(huà)框,因?yàn)榭ㄋ懒耍透杏X(jué)從來(lái)沒(méi)出現(xiàn)
通過(guò)閱讀本文,你將了解到如何利用「分幀加載」技術(shù)解決上述問(wèn)題,最終效果對(duì)比如下:
二、卡死問(wèn)題分析
在正常情況下,我們?yōu)镾crollView創(chuàng)建一定數(shù)量的子節(jié)點(diǎn)的時(shí)候,代碼可能是這樣子的
public directLoad(length: number) { for (let i = 0; i < length; i++) { this._initItem(i); } } private _initItem(itemIndex: number) { let itemNode = cc.instantiate(this.itemPrefab); itemNode.width = this.scrollView.content.width / 10; itemNode.height = itemNode.width; itemNode.parent = this.scrollView.content; itemNode.setPosition(0, 0); }
一般而言,當(dāng)length的值很小,比如10個(gè)的時(shí)候,程序跑起來(lái)的時(shí)候,看上去可能會(huì)沒(méi)什么問(wèn)題,但其實(shí)如果仔細(xì)一點(diǎn)觀察,就發(fā)現(xiàn)其實(shí)也是會(huì)卡死一會(huì),只是很快就結(jié)束了。
特別地,如果length的值到一點(diǎn)量級(jí),比如50+個(gè),那么這段代碼就會(huì)出現(xiàn)上面截圖那樣子—— 卡死
歸根到底,問(wèn)題在于通過(guò) cc.instantiate
創(chuàng)建節(jié)點(diǎn)以及為這個(gè)節(jié)點(diǎn) setParent
時(shí),所需要的時(shí)間并沒(méi)有想象中那么小,當(dāng)然,也沒(méi)有想象中那么大。但是當(dāng)連續(xù)創(chuàng)建一定數(shù)量的時(shí)候,問(wèn)題就會(huì)被放大,也就是說(shuō),這個(gè)創(chuàng)建節(jié)點(diǎn)的時(shí)間可能需要一段時(shí)間。
可視化一點(diǎn)去理解這個(gè)問(wèn)題的話(huà),恩,大概就是下圖這樣子
Direct Load
很明顯,按照上圖,第1到4幀都被完成占用了,導(dǎo)致這期間所有的其他邏輯都會(huì)不能執(zhí)行(Loading對(duì)話(huà)框出不來(lái),旋轉(zhuǎn)動(dòng)畫(huà)卡死等等)。
那么怎么解決呢?
三、解決方案(理論篇)
可能有同學(xué)第一時(shí)間想到用Promise異步解決,但是在這個(gè)問(wèn)題上,Promise只是把紅色的這段連續(xù)創(chuàng)建節(jié)點(diǎn)的代碼放到后面一點(diǎn)的時(shí)間去執(zhí)行,但是當(dāng)紅色的代碼執(zhí)行的時(shí)候,它依舊會(huì)卡死那段時(shí)間,所以Promise是不能應(yīng)對(duì)這種場(chǎng)合的。
那么應(yīng)該怎么解決呢?
其中,一種解決方案,就是我們今天要講的 「分幀加載」 ,怎么理解「分幀加載」呢?
慣例,先上圖:
Framing Load
配合上圖,就比較好理解「分幀加載」了,具體執(zhí)行過(guò)程為
- 先將耗時(shí)卡死的代碼拆分為很多小段
- 然后每一幀,分配一點(diǎn)時(shí)間去執(zhí)行這些小段
- 這樣子一來(lái),每一幀,我們就留了時(shí)間給其他邏輯去跑(那么Loading對(duì)話(huà)框也可以出來(lái)了,旋轉(zhuǎn)動(dòng)畫(huà)也可以繼續(xù)了)
OK,理論說(shuō)清楚了,那么實(shí)際怎么弄呢?
比如:
- 怎么拆分代碼為很多小段?
- 怎么分配每一幀的一些時(shí)間去執(zhí)行這些小段呢?
這個(gè)時(shí)候,我們需要用到 ES6(ES2015)的協(xié)程——Generator
,去幫助我們實(shí)現(xiàn)。
四、解決方案(代碼篇)
以我們第二節(jié)舉例用到的代碼(為ScrollView創(chuàng)建一定數(shù)量的子節(jié)點(diǎn))為例子,我們將 實(shí)現(xiàn)代碼為多個(gè)小段 以及 分配每一幀的一些時(shí)間去執(zhí)行這些小段 。
4.1 利用 Generator 將代碼拆分為多個(gè)小段
拆分前:
public directLoad(length: number) { for (let i = 0; i < length; i++) { this._initItem(i); } } private _initItem(itemIndex: number) { let itemNode = cc.instantiate(this.itemPrefab); itemNode.width = this.scrollView.content.width / 10; itemNode.height = itemNode.width; itemNode.parent = this.scrollView.content; itemNode.setPosition(0, 0); }
拆分后:
/** * (新增代碼)獲取生成子節(jié)點(diǎn)的Generator */ private *_getItemGenerator(length: number) { for (let i = 0; i < length; i++) { yield this._initItem(i); } } /** * (和拆分前的代碼一致) */ private _initItem(itemIndex: number) { let itemNode = cc.instantiate(this.itemPrefab); itemNode.width = this.scrollView.content.width / 10; itemNode.height = itemNode.width; itemNode.parent = this.scrollView.content; itemNode.setPosition(0, 0); }
這里的原理就是 利用 Generator 將一次 for 循環(huán)里創(chuàng)建所有節(jié)點(diǎn),改為拆分 for 循環(huán)的每一步為一個(gè)小段
當(dāng)然,這份「拆分后」的代碼并不能跑起來(lái),因?yàn)樗皇菍?shí)現(xiàn)了拆分步驟,要讓它跑起來(lái),我們要上下面的第二段代碼
4.2 分配每一幀的一些時(shí)間去執(zhí)行
在看一次我們剛才的圖
Framing Load
配合圖,得出的代碼
/** * 實(shí)現(xiàn)分幀加載 */ async framingLoad(length: number) { await this.executePreFrame(this._getItemGenerator(length), 1); } /** * 分幀執(zhí)行 Generator 邏輯 * * @param generator 生成器 * @param duration 持續(xù)時(shí)間(ms) * 每次執(zhí)行 Generator 的操作時(shí),最長(zhǎng)可持續(xù)執(zhí)行時(shí)長(zhǎng)。 * 假設(shè)值為8ms,那么表示1幀(總共16ms)下,分出8ms時(shí)間給此邏輯執(zhí)行 */ private executePreFrame(generator: Generator, duration: number) { return new Promise((resolve, reject) => { let gen = generator; // 創(chuàng)建執(zhí)行函數(shù) let execute = () => { // 執(zhí)行之前,先記錄開(kāi)始時(shí)間戳 let startTime = new Date().getTime(); // 然后一直從 Generator 中獲取已經(jīng)拆分好的代碼段出來(lái)執(zhí)行 for (let iter = gen.next(); ; iter = gen.next()) { // 判斷是否已經(jīng)執(zhí)行完所有 Generator 的小代碼段 // 如果是的話(huà),那么就表示任務(wù)完成 if (iter == null || iter.done) { resolve(); return; } // 每執(zhí)行完一段小代碼段,都檢查一下是否 // 已經(jīng)超過(guò)我們分配給本幀,這些小代碼端的最大可執(zhí)行時(shí)間 if (new Date().getTime() - startTime > duration) { // 如果超過(guò)了,那么本幀就不在執(zhí)行,開(kāi)定時(shí)器,讓下一幀再執(zhí)行 this.scheduleOnce(() => { execute(); }); return; } } }; // 運(yùn)行執(zhí)行函數(shù) execute(); }); }
代碼中已經(jīng)附有大量注釋?zhuān)€是有幾個(gè)點(diǎn)需要說(shuō)明一下:
- 為了方便知道這些小任務(wù)是否已經(jīng)都執(zhí)行完了,我采用了Promise,當(dāng)都完成了的時(shí)候,
resolve
一下 - 每一個(gè)小代碼段的執(zhí)行時(shí)間可能不固定的,可能會(huì)超出占用我們的一些期望時(shí)間。比如我們期望每一幀分配1ms 去執(zhí)行這些小代碼段,假設(shè)前3段小代碼段,每一段的執(zhí)行時(shí)間假設(shè)為 0.2ms,0.5ms, 0.4ms,那么在我給出的這段代碼中,是會(huì)執(zhí)行完這3段小代碼段,然后就終止本幀繼續(xù)執(zhí)行這些小代碼段,因?yàn)檫@里的耗時(shí)已經(jīng)是 1.1ms,比我設(shè)定的 1ms 已經(jīng)多出了 0.1ms 。當(dāng)然你可以自行改動(dòng)代碼,讓這些執(zhí)行嚴(yán)格按照最大1ms去執(zhí)行,以實(shí)現(xiàn)不超時(shí)執(zhí)行(即不再執(zhí)行第3個(gè)小段)
至此,我們一定程度上已經(jīng)實(shí)現(xiàn)了「分幀加載」了~
本項(xiàng)目中所有圖示、代碼都在Github倉(cāng)庫(kù)中,如果需要運(yùn)行驗(yàn)證,可直接拉下項(xiàng)目即可,不用自己手?jǐn)]代碼驗(yàn)證
👉👉https://github.com/zhitaocai/CocosCreator-ScrollVIewPlus👈👈
五、總結(jié)
- 盡管我們標(biāo)題是 「ScrollView 優(yōu)化系列」,但我更加傾向于,「利用分幀加載去優(yōu)化ScrollView」。在這篇文章上,我們舉的例子是創(chuàng)建節(jié)點(diǎn),但是我刻意不說(shuō)「分幀創(chuàng)建」,這是因?yàn)槲艺J(rèn)為
「分幀加載」
是一種性能優(yōu)化方案 ,可以「分幀創(chuàng)建」、「分幀運(yùn)行」、「分幀計(jì)算」、「分幀渲染」等。 - 在實(shí)現(xiàn)分幀上,我們用到了
this.scheduleOnce
函數(shù),但是其實(shí)可以嘗試在update(dt:number)
上執(zhí)行,不妨嘗試修改我的 「測(cè)試項(xiàng)目」去驗(yàn)證呢~ - TypeScript 要用上 Generator 還需要需改一下Cocos項(xiàng)目中的
tsconfig.json
:compilerOptions.lib
數(shù)組中添加es2015
以上就是CocosCreator ScrollView優(yōu)化系列之分幀加載的詳細(xì)內(nèi)容,更多關(guān)于CocosCreator ScrollView優(yōu)化分幀加載的資料,請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- cocos creator Touch事件應(yīng)用(觸控選擇多個(gè)子節(jié)點(diǎn)的實(shí)例)
- iOS開(kāi)發(fā)中使用cocos2d添加觸摸事件的方法
- Cocos2d-x觸摸事件實(shí)例
- 詳解CocosCreator優(yōu)化之DrawCall
- CocosCreator實(shí)現(xiàn)技能冷卻效果
- 詳解cocoscreater預(yù)制體prefab
- 如何在CocosCreator中利用常駐節(jié)點(diǎn)做圖層管理
- 游戲開(kāi)發(fā)中如何使用CocosCreator進(jìn)行音效處理
- 詳解CocosCreator系統(tǒng)事件是怎么產(chǎn)生及觸發(fā)的
相關(guān)文章
如何通過(guò)javascript操作web控件的自定義屬性
這篇文章主要是對(duì)如何通過(guò)javascript操作web控件的自定義屬性進(jìn)行了詳細(xì)的介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所 幫助2013-11-11基于JavaScript實(shí)現(xiàn)表單密碼的隱藏和顯示出來(lái)
為了網(wǎng)站的安全性,很多朋友都把密碼設(shè)的比較復(fù)雜,但是如何密碼不能明顯示,不知道輸?shù)氖菍?duì)是錯(cuò),為了安全起見(jiàn)可以把密碼顯示的,那么基于js代碼如何實(shí)現(xiàn)的呢?下面通過(guò)本文給大家介紹JavaScript實(shí)現(xiàn)表單密碼的隱藏和顯示,需要的朋友參考下2016-03-03用Javascript判斷圖片是否存在,不存在則顯示默認(rèn)圖片的代碼
用Javascript判斷圖片是否存在,不存在則顯示默認(rèn)圖片的代碼,需要的朋友可以參考下。2007-03-03JS將數(shù)字轉(zhuǎn)換成三位逗號(hào)分隔的樣式(示例代碼)
本篇文章主要是對(duì)JS將數(shù)字轉(zhuǎn)換成三位逗號(hào)分隔的樣式(示例代碼)進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-02-02javascript實(shí)現(xiàn)控制的多級(jí)下拉菜單
這篇文章主要介紹了javascript實(shí)現(xiàn)控制的多級(jí)下拉菜單,包含示例代碼,效果非常不錯(cuò),這里推薦給大家。2015-07-07微信小程序數(shù)據(jù)劫持代理的實(shí)現(xiàn)
本文主要介紹了微信小程序?數(shù)據(jù)劫持代理的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01