<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          4個(gè)方面入手 TiledMap 地圖優(yōu)化!W字干貨分享

          共 16284字,需瀏覽 33分鐘

           ·

          2022-12-31 01:05

          引言:如何進(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.timetimeEnd 函數(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)用 TmxAssemblerfillBuffers 函數(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)置的 EffectMaterial 創(chuàng)建 EffectMaterial,因?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(1111);
              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(046, 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(1111);
               #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(046, 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)地去更新 TextureSetTextureIndexMap


          然后,我們利用 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


          往期精彩


          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色乱视频 | 99精品视频在线免费观看 | 日韩一级片网站 | AV天堂偷拍亂伦 | 国产一级a毛一级a做免费图 |