4個(gè)方面入手 TiledMap 地圖優(yōu)化!W字干貨分享
引言:如何進(jìn)行 TiledMap 地圖優(yōu)化?開發(fā)者 Bool Chen 將分享一套行之有效的 TiledMap 地圖優(yōu)化方案,其中包括了渲染、解析、尋路方面。
當(dāng)項(xiàng)目里的地圖越來越龐大和復(fù)雜,一些性能上的問題也開始逐漸出現(xiàn)。本文將從裁剪區(qū)域共享、Sprite 顏色數(shù)據(jù)去除、多圖集渲染合批和分幀尋路四個(gè)方面,分享關(guān)于 TiledMap 地圖的優(yōu)化以及實(shí)現(xiàn)。

測(cè)試用例
本次的測(cè)試用例是這樣的一張地圖,有6個(gè)圖層,其中4個(gè)圖塊層、2個(gè)物件層。測(cè)試的數(shù)據(jù)來源是在瀏覽器環(huán)境下,利用 console.time 和 timeEnd 函數(shù),打印對(duì)應(yīng)的邏輯耗時(shí)或渲染耗時(shí),需要注意的是每次運(yùn)行的耗時(shí)并不是一致的,但是在取均值后,可以認(rèn)為是相對(duì)可靠的。

優(yōu)化前后(注:橫軸是游戲運(yùn)行的幀數(shù),縱軸是在該幀數(shù)下,對(duì)應(yīng)的耗時(shí),單位是毫秒)
上圖是我們最后將裁剪區(qū)域共享+Sprite 顏色數(shù)據(jù)去除+多圖集渲染合批一起使用后的優(yōu)化效果,測(cè)試顯示渲染耗時(shí)大約降低了20%左右。其實(shí)這張地圖并不算復(fù)雜,如果物件數(shù)量、圖層數(shù)量增加的話,優(yōu)化效果會(huì)更加明顯。
本次的主要優(yōu)化方案參考自大城小胖的《如何重繪<江南百景圖>》,文章介紹了很多性能優(yōu)化技巧,強(qiáng)烈推薦大家去看看。項(xiàng)目基于 Cocos Cocos Creator 2.4.3,不過大部分優(yōu)化思路在 v3.x 依舊適用。限于篇幅,本文僅呈現(xiàn)部分核心代碼,完整代碼及測(cè)試項(xiàng)目源碼下載見文末。
裁剪區(qū)域共享
玩家操控人物在地圖上移動(dòng)的時(shí)候,地圖顯示的內(nèi)容也需要跟隨人物的位置發(fā)生改變。此時(shí),為了優(yōu)化性能,引擎會(huì)計(jì)算屏幕的可視范圍,只有在可視范圍內(nèi)的圖塊才會(huì)被渲染。
研究引擎中 TiledMap 地圖的渲染流程后我們發(fā)現(xiàn),其實(shí) TiledMap 本身并不是渲染組件,地圖的渲染是通過圖層 TiledLayer 實(shí)現(xiàn)的,其對(duì)應(yīng)的渲染器是 TmxAssembler。渲染時(shí),渲染流會(huì)逐個(gè)調(diào)用 TmxAssembler 的 fillBuffers 函數(shù)進(jìn)行渲染數(shù)據(jù)填充,此函數(shù)中會(huì)調(diào)用 CCTiledLayer 的 _updateCulling 函數(shù)進(jìn)行可視范圍,只有可視范圍發(fā)生改變才會(huì)進(jìn)行渲染。
但是,在計(jì)算的時(shí)候,由于每個(gè)圖層都有對(duì)應(yīng)的 Assembler,所以每個(gè)圖層都會(huì)單獨(dú)計(jì)算一次。而一般情況下我們每個(gè)圖層顯示的范圍是一致的,所以我們希望它只計(jì)算一次就好了。
接下來我們就來實(shí)現(xiàn)裁剪區(qū)域共享(Share Culling),讓不同 TiledLayer 間,共享可視區(qū)域的裁剪計(jì)算結(jié)果,以此節(jié)約性能。
實(shí)現(xiàn)過程
首先我們繼承 TiledMap,重寫創(chuàng)建圖層的 _buildLayerAndGroup 函數(shù),實(shí)現(xiàn)創(chuàng)建自定義的 ShareCullingTiledLayer。
因?yàn)橄鄬?duì)來說記錄第一個(gè)圖層實(shí)現(xiàn)起來更方便,所以我們緩存第一個(gè)圖層,并將首個(gè) TieldLayer 傳遞給后面的圖層,方便后面去讀取計(jì)算結(jié)果。
_buildLayerAndGroup() {
for (let i = 0, len = layerInfos.length; i < len; i++) {
if (layerInfo instanceof cc.TMXLayerInfo) {
// 創(chuàng)建自定義的ShareCullingTiledLayer
let layer = child.getComponent(ShareCullingTiledLayer);
// 傳遞、記錄首個(gè)TiledLayer
layer._init(layerInfo, firstLayer);
firstLayer = firstLayer || layer;
}
}
}
接著修改 TiledLayer 的裁剪函數(shù),一樣通過重寫的方式實(shí)現(xiàn)。
這里我們進(jìn)行判斷,如果是首個(gè)圖層,我們才讓他進(jìn)行計(jì)算,并把結(jié)果緩存起來;如果不是首個(gè)圖層,我們就直接讀取首個(gè)圖層的計(jì)算結(jié)果。
最后重寫 TiledLayer 的裁剪函數(shù),實(shí)現(xiàn)復(fù)用裁剪區(qū)域的功能。
_updateCulling() {
// this._firstLayer為空時(shí) 表示為首個(gè)layer
let firstLayer = this._firstLayer;
if (!firstLayer) {
// 進(jìn)行裁剪區(qū)域計(jì)算
this._updateViewPort();
this._cacheCullingDirty = this._cullingDirty;
} else {
// 直接復(fù)用firstLayer的結(jié)果
this._cullingRect = firstLayer._cullingRect;
this._cullingDirty = firstLayer._cacheCullingDirty;
return;
}
}
很簡(jiǎn)單地我們就完成了這個(gè)優(yōu)化。Share Culling 實(shí)現(xiàn)起來并不麻煩,但效果是顯著的。
優(yōu)化效果

優(yōu)化前后
可以看到即使只有6個(gè)圖層的情況下,裁剪函數(shù)的平均耗時(shí)降低了35%左右,當(dāng)圖層數(shù)量增加的時(shí)候,優(yōu)化效率會(huì)更高。
講到裁剪區(qū)域,這里還有一個(gè)優(yōu)化點(diǎn)。在初始化圖塊圖層時(shí),引擎會(huì)遍歷整個(gè)地圖的圖塊,將所有圖塊的信息保存起來,方便后續(xù)使用。這里可以改成區(qū)域加載,一開始只解析當(dāng)前屏幕中的圖塊,隨后在移動(dòng)的時(shí)候,再動(dòng)態(tài)解析行動(dòng)方向上的圖塊——當(dāng)然這個(gè)方案也有缺點(diǎn),就是我們需要額外的內(nèi)存空間保存對(duì)應(yīng)的坐標(biāo)是否已經(jīng)解析過。
Sprite 顏色數(shù)據(jù)去除
接下來是物件顏色去除,這里我們用在地圖物件上,但其實(shí)這個(gè)優(yōu)化在所有 Sprite 組件中都是可以適用的。

Sprite 默認(rèn)的渲染頂點(diǎn)數(shù)據(jù)中包含了顏色數(shù)據(jù),但大部分情況下,美術(shù)給我們的素材我們都是直接放到游戲里,不會(huì)再對(duì)顏色做修改,此時(shí) Color 數(shù)據(jù)似乎成了一個(gè)非必要的東西,將其去除掉可以減少 CPU 和 GPU 的數(shù)據(jù)傳輸,也可以省去著色器中對(duì)顏色的計(jì)算。
簡(jiǎn)單講一下 Sprite 渲染流程。Sprite 組件會(huì)通過 resetAssembler 取得一個(gè)默認(rèn)的 Assembler,而 Assembler 會(huì)通過 updateRenderData 函數(shù),把 Sprite 的數(shù)據(jù)填充到 RenderData 中。最后引擎會(huì)幫我們把渲染數(shù)據(jù)傳遞給材質(zhì),進(jìn)而進(jìn)行渲染。
接著我們來看看怎么實(shí)現(xiàn)這個(gè)優(yōu)化。
實(shí)現(xiàn)過程
我們從底層步驟往上看,首先是著色器。仿照內(nèi)置的 Effect 及 Material 創(chuàng)建 Effect 和 Material,因?yàn)槲覀儾辉傩枰伾耍?strong style="box-sizing: border-box;">所以只要把著色器中關(guān)于顏色的輸入輸出、計(jì)算等的代碼去除即可。
// 刪除顏色相關(guān)輸入輸出處理
CCProgram vs %{
in vec3 a_position;
// in vec4 a_color;
// out vec4 v_color;
void main () {
// v_color = a_color;
gl_Position = pos;
}
}%
// 刪除顏色相關(guān)輸入、計(jì)算
CCProgram fs %{
precision highp float;
// in vec4 v_color;
void main () {
vec4 o = vec4(1, 1, 1, 1);
CCTexture(texture, v_uv0, o);
// o *= v_color;
gl_FragColor = o;
}
}%
接著我們要提供不帶顏色的渲染數(shù)據(jù)。繼承 cc.Assembler 實(shí)現(xiàn)一個(gè)新的 Assembler。在 Assembler 中,首先要新建一個(gè)頂點(diǎn)數(shù)據(jù)格式,將默認(rèn)的頂點(diǎn)格式中的顏色屬性去掉。隨后,為我們的新格式創(chuàng)建對(duì)應(yīng)的頂點(diǎn)數(shù)據(jù)容器。
// 自定義頂點(diǎn)格式,去掉默認(rèn)的顏色字段
let gfx = cc.gfx;
let vfmtNoColor = new gfx.VertexFormat([
{ name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
{ name: gfx.ATTR_UV0, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
// { name: gfx.ATTR_COLOR, …},
]);/**
* 初始化this._renderData 創(chuàng)建自定義格式的renderData
*/
initData() {
let data = this._renderData = new cc.RenderData();
this._renderData.init(this);
// 按我們自己的格式創(chuàng)建renderData
data.createFlexData(0, 4, 6, vfmtNoColor);
}
最后,把渲染顏色的函數(shù)也移除掉。這樣就完成了一個(gè)不帶顏色的 Assembler。
/**
* 更新顏色 拜拜了??
*/
// updateColor () {
// }
接著我們需要使用這個(gè) Assembler。重寫 Sprite 的 resetAssembler 函數(shù),將默認(rèn)的 Assembler 改成上面的 Assembler。
/**
* 修改默認(rèn)Assembler
*/
_resetAssembler() {
let assembler = this._assembler = new NoColorSpriteAssembler();
assembler.init(this);
this.setVertsDirty();
},
如果你要運(yùn)用在其他地方,只要給 Sprite 換上前面的 material 就可以了。
那么如何運(yùn)用在地圖物件中呢?我們通過繼承實(shí)現(xiàn)一個(gè) TiledObjectGroup,并重寫 _init 函數(shù)。在里面,我們將默認(rèn)的 Sprite 組件改成我們自定義的組件,并賦予對(duì)應(yīng)的去除顏色的材質(zhì)即可。
_init(groupInfo, mapInfo, texGrids, noColorMaterial) {
let objects = groupInfo._objects;
for (let i = 0, l = objects.length; i < l; i++) {
imgNode = new cc.Node();
let sp = imgNode.addComponent("NoColorSprite");
sp.setMaterial(0, noColorMaterial);
}
}
優(yōu)化效果

優(yōu)化前后
最終優(yōu)化效果,在大約有100多個(gè)組件的情況下,渲染耗時(shí)降低了約12%。我在測(cè)試優(yōu)化效果的時(shí)候,發(fā)現(xiàn)這個(gè)數(shù)據(jù)有較大的浮動(dòng),范圍大約是5-15%。
在邏輯層面,我們減少了顏色數(shù)據(jù)的填充,本身優(yōu)化效果其實(shí)并不算大。其次,數(shù)據(jù)統(tǒng)計(jì)監(jiān)聽不到 CPU 和 GPU 數(shù)據(jù)傳輸?shù)牟糠郑脖O(jiān)聽不到著色器優(yōu)化的部分。
另外,顏色數(shù)據(jù)的去除還可以為我們接下來的地圖物件多圖集渲染合批做準(zhǔn)備。
多圖集渲染合批
物件常常是地圖中不可或缺的一部分,世界觀豐富起來之后,物件來自不同的圖集也是很常見的,這個(gè)時(shí)候如果還要對(duì)物件進(jìn)行排序,圖集交錯(cuò)的情況下,非常容易產(chǎn)生大量的 DC。
優(yōu)化 DC 的常見方案是打包圖集,但當(dāng)圖片來自不同圖集的時(shí)候,這個(gè)方案就無法進(jìn)行了。多圖集渲染合批是一個(gè)類似于打包圖集的方案,我們?cè)阡秩镜臅r(shí)候,一次傳遞多張圖集,把原本的判斷圖片是否來自于同一張圖集,轉(zhuǎn)換為判斷圖片是否來自于同一批圖集。
大部分手機(jī)設(shè)備都可以支持8張圖集,所以理論上,只要使用的圖集不超過8張,就可以只要1次 DC。
實(shí)現(xiàn)過程
首先一樣需要修改著色器相關(guān)代碼。我們同樣復(fù)制一份內(nèi)置 Effect,之后在 Effect 的聲明中,增加一些 texture 參數(shù),來接收多張圖集數(shù)據(jù)。
CCEffect %{
techniques:
- passes:
- vert: vs
properties:
texture: { value: white }
texture1: { value: white }
texture2: { value: white }
texture3: { value: white }
// 4 5 6...
}%
隨后,通過頂點(diǎn)數(shù)據(jù)傳遞 texture_index,表示當(dāng)前使用的是哪張圖集。這里在著色器代碼中根據(jù) texture_index 從不同的圖集取值就可以了。
CCProgram fs %{
precision highp float;
in float texture_idx;
void main () {
vec4 o = vec4(1, 1, 1, 1);
#if USE_TEXTURE
if (texture_idx <= 1.0) {
CCTexture(texture, v_uv0, o);
} else if (texture_idx <= 2.0) {
CCTexture(texture1, v_uv0, o);
} else if (texture_idx <= 3.0) {
CCTexture(texture2, v_uv0, o);
}
// else ...
#endif
gl_FragColor = o;
}
}%
現(xiàn)在我們要想辦法把這些數(shù)據(jù)傳遞給材質(zhì)。
先說 texture_index。這個(gè)部分和前面的組件顏色去除有點(diǎn)類似,不過這次是增加數(shù)據(jù)。我們自定義新的頂點(diǎn)數(shù)據(jù)格式,在里面增加一個(gè) a_texture_index 屬性,之后創(chuàng)建一個(gè)新的頂點(diǎn)數(shù)據(jù)容器(注意 texture_index 聲明的位置,一會(huì)兒我們會(huì)用到)。
let gfx = cc.gfx;
var vfmtPosUvColorIndex = new gfx.VertexFormat([
{ name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
{ name: gfx.ATTR_UV0, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
{ name: "a_texture_idx", type: gfx.ATTR_TYPE_FLOAT32, num: 1 },
{ name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true },
]);
initData() {
let data = this._renderData = new cc.RenderData();
this._renderData.init(this);
data.createFlexData(0, 4, 6, vfmtPosUvColorIndex);
}
完事之后,我們就要往這個(gè)容器中寫值,把數(shù)據(jù)傳遞給著色器。
新建 updateTextureIdx 函數(shù),進(jìn)行數(shù)據(jù)的填充。按照我們定義的頂點(diǎn)格式,在每個(gè)頂點(diǎn)的對(duì)應(yīng)位置填充 texture_index 屬性值。
隨后找出填充頂點(diǎn)數(shù)據(jù)的 updateRenderData 函數(shù),在里面增加對(duì) updateTextureIdx 函數(shù)的調(diào)用,這樣就完成了數(shù)據(jù)的填充。
// 填充textureIndex數(shù)據(jù)
updateTextureIdx(sprite) {
let textureIdx = sprite._textureIdx;
let verts = this._renderData.vDatas[0];
let verticesCount = this.verticesCount;
let floatsPerVert = this.floatsPerVert;
for (let i = 0; i < verticesCount; i++) {
let index = i * floatsPerVert + 4;
verts[index] = textureIdx;
}
}
updateRenderData(sprite) {
if (sprite._vertsDirty) {
this.updateUVs(sprite);
this.updateVerts(sprite);
this.updateTextureIdx(sprite);
sprite._vertsDirty = false;
}
}
接著是傳遞圖集,我們?yōu)?objectGroup 傳遞一個(gè) texture 變量,來保存所有物件圖層使用到的圖集。在創(chuàng)建完 objectGroup 之后,再按順序地把圖集傳遞給材質(zhì)。
_buildLayerAndGroup: function () {
let layerInfos = mapInfo.getAllChildren ();
let textureSet = new Set();
for (let i = 0, len = layerInfos.length; i < len; i++) {
let layerInfo = layerInfos[i];
let group = child.getComponent("MutilObjectGroup");
group._init(this.objectMaterial, textureSet);
}
// 設(shè)置材質(zhì)的texture屬性
let objectTextures = Array.from(textureSet);
for (let i = 0; i < objectTextures.length; i++) {
let idx = i === 0 ? '' : i;
this.objectMaterial.setProperty(`texture${idx}`, objectTextures[i], 0);
}
}
接著看一下 objectGroup的部分。
我們實(shí)現(xiàn)新的 TiledObjectGroup,重寫 _init 函數(shù)。
除了 textureSet,我們同時(shí)維護(hù)一個(gè) textureIndexMap,來記錄圖集在 set 中的位置。新建 Sprite 組件的時(shí)候,動(dòng)態(tài)地去更新 TextureSet 和 TextureIndexMap。
然后,我們利用 map 來獲得 Sprite 的 texture_index。
需要注意的是,我們需要將材質(zhì)的哈希值寫死,否則更新圖集后,一樣會(huì)判定為不可合批。
_init(groupInfo, mapInfo, texGrids, material, textureSet) {
// texture資源 -> textureIndex
let textureIndexMap = new Map();
Array.from(textureSet).forEach((texture, idx) => textureIndexMap.set(texture, idx));
for (let i = 0, l = objects.length; i < l; i++) {
let sp = imgNode.getComponent("MutilSprite");
let spf = sp.spriteFrame;
// 收集所有圖集
let size = textureSet.size;
textureSet.add(grid.tileset.sourceImage);
// 更新Map
if (size !== textureSet.size) {
textureIndexMap.set(grid.tileset.sourceImage, size)
}
sp.setMaterial(0, material);
// 設(shè)置textureIndex
let index = textureIndexMap.get(sp.spriteFrame._texture);
// 寫死哈希值 使其可以合批
sp.getMaterial(0).updateHash(9999);
sp.setTextureIdx(index + 1);
}
}
優(yōu)化效果

優(yōu)化后,DC 從16降到了6,平均降低了13%的渲染耗時(shí)。而在復(fù)雜的環(huán)境下,不論物件產(chǎn)生了多少次 DC,最后的 DC 都會(huì)是6次,優(yōu)化效果也會(huì)提升。
因?yàn)橄鄬?duì)默認(rèn)的渲染方式,我們額外增加了 texture_index 這個(gè)數(shù)據(jù),這會(huì)有一點(diǎn)性能損耗。但是如果和前面的顏色去除結(jié)合起來使用,就可以抵消這個(gè)損耗,達(dá)到更好的優(yōu)化效果。
此外,在圖塊圖層也有類似記錄圖集的操作。
初始化時(shí),需要獲取圖層用到的所有圖集,并為他們創(chuàng)建對(duì)應(yīng)的材質(zhì),這里需要遍歷整張地圖。這里是一個(gè)優(yōu)化點(diǎn),首先我們可以要求策劃拼地圖的時(shí)候每個(gè)圖層只使用一個(gè)圖集,這也可以避免多個(gè)圖集導(dǎo)致的 DC 上升。在這之后,我們可以修改對(duì)應(yīng)的代碼,只要找到一個(gè)圖集,就可以停止遍歷了,避免多次完整遍歷整張地圖。
分幀尋路

尋路是游戲中的重要部分,當(dāng)?shù)貓D面積增加時(shí),尋路算法的損耗也會(huì)變成一個(gè)不可小視的部分。分幀的思路也是一個(gè)常見的優(yōu)化方法,我們把一件復(fù)雜的工作拆成好幾段,每幀做一段。它本身并沒有減少運(yùn)算的數(shù)量,但是可以幫你壓平 CPU 的使用率曲線,避免突發(fā)的計(jì)算占用導(dǎo)致掉幀。
實(shí)現(xiàn)過程
在我們的尋路工具類里面提供一個(gè)接口,來進(jìn)行尋路任務(wù)的提交。
因?yàn)榉謳幚砗螅a的執(zhí)行變成異步的了,所以我們需要緩存尋路任務(wù)的數(shù)據(jù)以及進(jìn)度,才能正確地接著上一幀的結(jié)果繼續(xù)處理。
之后,我們?cè)谟螒蛑忻繋{(diào)用對(duì)應(yīng)的尋路函數(shù),進(jìn)行尋路的計(jì)算。
在進(jìn)行路徑計(jì)算的時(shí)候,我們每次訪問路徑點(diǎn)前,都先判斷已訪問的路徑點(diǎn)數(shù)量,如果超出了數(shù)量,就不再進(jìn)行尋路,等待下一幀的調(diào)用。
/**
* 開始一個(gè)尋路任務(wù)。此函數(shù)為外部調(diào)用入口
*/
findRoad(start, target, wall, callback, config) {
const { maxWalkPerFrame } = config;
this._maxWalkPointAmount = maxWalkPerFrame || Number.MAX_VALUE;
// ...存儲(chǔ)數(shù)據(jù)
// 立即執(zhí)行一次尋路
this._findPath();
}
/**
* 此函數(shù)應(yīng)由外部引用者每幀調(diào)用
*/
update() {
this._findPath();
}
/**
* 執(zhí)行一次尋路
*/
_findPath() {
let walkPointAmount = 0;
while (walkPointAmount++ < this._maxWalkPointAmount) {
// 訪問路徑點(diǎn)...
const point = this._waitQueue.poll();
}
}
優(yōu)化效果

優(yōu)化前后
測(cè)試用例是在游戲開始時(shí),提交了四個(gè)尋路任務(wù)。可以看到優(yōu)化前的時(shí)間消耗接近 8ms,這對(duì)我們來說是不可接受的。在優(yōu)化后,最高的耗時(shí)也不過 1ms。相對(duì)來說是一個(gè)可以接受的數(shù)字。
除了分幀處理,我們還可以再進(jìn)一步地進(jìn)行優(yōu)化。
比如游戲世界剛啟動(dòng)的時(shí)候,所有的 NPC 都需要進(jìn)行隨機(jī)移動(dòng),這個(gè)時(shí)候會(huì)有大量的 NPC 需要同時(shí)進(jìn)行尋路運(yùn)算,仍然會(huì)導(dǎo)致 CPU 占用率過高。這里有兩個(gè)方案,一個(gè)是讓 NPC 在不同的時(shí)機(jī)點(diǎn)開始移動(dòng),另一個(gè)是對(duì)尋路任務(wù)進(jìn)行統(tǒng)一的管理。這里介紹一下后一個(gè)方案。
我們可以將提交的尋路任務(wù)保存到隊(duì)列中。只有當(dāng)尋路任務(wù)完成的時(shí)候,我們才從隊(duì)列中取出新的任務(wù)。
/**
* 添加一個(gè)尋路任務(wù)。此函數(shù)為外部調(diào)用入口
* @param {FindRoadTask} task 尋路任務(wù)
*/
addFindRoadTask(task) {
if (this._finding) {
this._taskList.push(task);
} else {
this._startFindRoadTask(task);
}
}/**
* 尋路任務(wù)結(jié)束回調(diào)。不論尋路成功或失敗都會(huì)調(diào)用本函數(shù)
*/
_onFindOver() {
if (!!this._taskList.length) {
this._startFindRoadTask(this._taskList.shift());
} else {
this._finding = false;
}
}
如此一來,就可以保證我們每幀同時(shí)只會(huì)執(zhí)行一個(gè)任務(wù),進(jìn)一步地壓平曲線。我們用同樣的測(cè)試用例進(jìn)行測(cè)試,結(jié)果如下。

優(yōu)化前后
需要注意一下,這里的藍(lán)色線就是剛剛優(yōu)化后的綠色線。可以看到綠色線又進(jìn)一步地更平緩了,最高也不超過 0.5ms,我們可以不用再擔(dān)心尋路會(huì)對(duì)幀數(shù)造成影響了。
總結(jié)與資源下載
如果把本文介紹的優(yōu)化全做了是什么效果?

開頭提到,這里我們測(cè)試了裁剪區(qū)域共享+顏色去除+多圖集渲染合批,渲染耗時(shí)大約降低了20%左右。
完整代碼與測(cè)試項(xiàng)目歡迎移步下方論壇專貼查看與下載,如有疑問、或是其他好的優(yōu)化方案,都可以在論壇一起交流!
論壇專貼地址
裁剪區(qū)域共享(Share Culling):
https://forum.cocos.org/t/topic/134525
Sprite 顏色數(shù)據(jù)去除:
https://forum.cocos.org/t/topic/135235
多圖渲染合批:
https://forum.cocos.org/t/topic/136349
分幀尋路+尋路任務(wù)統(tǒng)一管理:
https://forum.cocos.org/t/topic/134884
往期精彩
