Cocos Creator 中的assembler那点事
创始人
2025-05-28 21:31:05
0

一:Assembler的来龙去脉

二:自定义一个渲染组件(使用自定义的assembler挂载)

一:Assembler的来龙去脉: 

之前的文章中提到和使用过assembler相关的技术,但是现在看来还是理解的不是很到位,

1: 首先看看那assembler是如何绑定到渲染组件的,随便找到一个渲染组件,比如说老生常谈的Sprite组件 

cocos2d/cocos-engine-2.4.8/cocos2d/core/renderer/webgl/assemblers/sprite/index.js

import Assembler from '../../../assembler';
import { Type, FillType } from '../../../../components/CCSprite';import Simple from "./2d/simple";
import Sliced from "./2d/sliced";
import Tiled from "./2d/tiled";
import RadialFilled from "./2d/radial-filled";
import BarFilled from "./2d/bar-filled";
import Mesh from './2d/mesh';import Simple3D from "./3d/simple";
import Sliced3D from "./3d/sliced";
import Tiled3D from "./3d/tiled";
import RadialFilled3D from "./3d/radial-filled";
import BarFilled3D from "./3d/bar-filled";
import Mesh3D from './3d/mesh';let ctor = {getConstructor(sprite) {let is3DNode = sprite.node.is3DNode;// sprite节点默认绑定的是SimpleAssembler2Dlet ctor = is3DNode ? Simple3D : Simple;switch (sprite.type) {case Type.SLICED:ctor = is3DNode ? Sliced3D : Sliced;break;case Type.TILED:ctor = is3DNode ? Tiled3D : Tiled;break;case Type.FILLED:if (sprite._fillType === FillType.RADIAL) {ctor = is3DNode ? RadialFilled3D : RadialFilled;} else {ctor = is3DNode ? BarFilled3D : BarFilled;}break;case Type.MESH:ctor = is3DNode ? Mesh3D : Mesh;break;}return ctor;},Simple,Sliced,Tiled,RadialFilled,BarFilled,Mesh,Simple3D,Sliced3D,Tiled3D,RadialFilled3D,BarFilled3D,Mesh3D,
};Assembler.register(cc.Sprite, ctor);

可以看到sprite组件会根据type选择对应的assembler来组装渲染数据。

Assembler.register = function (renderCompCtor, assembler) {renderCompCtor.__assembler__ = assembler;
};

register函数是初始化渲染组件的构造函数的__assembler__属性,以便后来的init函数赋值renderComp._assembler ,你可能会问Assembler的init函数在哪里执行的呢,不错这个init函数是在_resetAssembler函数里面执行的。

_resetAssembler() {Assembler.init(this);this._updateColor();this.setVertsDirty();},

2:Assembler的执行顺序是怎样的 

聊完了注册和初始化,接下来看看一个Assembler它的执行顺序是怎么样的,想要了解这些之前有必要知道Assembler都有哪些跟渲染有关的方法:

export default class Assembler {constructor () {this._extendNative && this._extendNative();}init (renderComp) {this._renderComp = renderComp;}updateRenderData (comp) {}fillBuffers (comp, renderer) {}getVfmt () {return vfmtPosUvColor;}
}
import Assembler from './assembler';
import dynamicAtlasManager from './utils/dynamic-atlas/manager';
import RenderData from './webgl/render-data';export default class Assembler2D extends Assembler {constructor() {super();this._renderData = new RenderData();this._renderData.init(this);this.initData();this.initLocal();}get verticesFloats() {// verticesCount 顶点个数// 每个顶点数据几个float数据 5个 x,y,u,v,return this.verticesCount * this.floatsPerVert;}initData() {let data = this._renderData;data.createQuadData(0, this.verticesFloats, this.indicesCount);}initLocal() {this._local = [];this._local.length = 4;}updateColor(comp, color) {let uintVerts = this._renderData.uintVDatas[0];if (!uintVerts) return;color = color != null ? color : comp.node.color._val;let floatsPerVert = this.floatsPerVert;let colorOffset = this.colorOffset;for (let i = colorOffset, l = uintVerts.length; i < l; i += floatsPerVert) {uintVerts[i] = color;}}getBuffer() {return cc.renderer._handle._meshBuffer;}updateWorldVerts(comp) {let local = this._local;let verts = this._renderData.vDatas[0];let matrix = comp.node._worldMatrix;let matrixm = matrix.m,a = matrixm[0], b = matrixm[1], c = matrixm[4], d = matrixm[5],tx = matrixm[12], ty = matrixm[13];let vl = local[0], vr = local[2],vb = local[1], vt = local[3];let floatsPerVert = this.floatsPerVert;let vertexOffset = 0;let justTranslate = a === 1 && b === 0 && c === 0 && d === 1;if (justTranslate) {// left bottomverts[vertexOffset] = vl + tx;verts[vertexOffset + 1] = vb + ty;vertexOffset += floatsPerVert;// right bottomverts[vertexOffset] = vr + tx;verts[vertexOffset + 1] = vb + ty;vertexOffset += floatsPerVert;// left topverts[vertexOffset] = vl + tx;verts[vertexOffset + 1] = vt + ty;vertexOffset += floatsPerVert;// right topverts[vertexOffset] = vr + tx;verts[vertexOffset + 1] = vt + ty;} else {let al = a * vl, ar = a * vr,bl = b * vl, br = b * vr,cb = c * vb, ct = c * vt,db = d * vb, dt = d * vt;// left bottomverts[vertexOffset] = al + cb + tx;verts[vertexOffset + 1] = bl + db + ty;vertexOffset += floatsPerVert;// right bottomverts[vertexOffset] = ar + cb + tx;verts[vertexOffset + 1] = br + db + ty;vertexOffset += floatsPerVert;// left topverts[vertexOffset] = al + ct + tx;verts[vertexOffset + 1] = bl + dt + ty;vertexOffset += floatsPerVert;// right topverts[vertexOffset] = ar + ct + tx;verts[vertexOffset + 1] = br + dt + ty;}}// 填充 meshBufferfillBuffers(comp, renderer) {if (renderer.worldMatDirty) {this.updateWorldVerts(comp);}let renderData = this._renderData;let vData = renderData.vDatas[0];let iData = renderData.iDatas[0];let buffer = this.getBuffer(renderer);let offsetInfo = buffer.request(this.verticesCount, this.indicesCount);// buffer data may be realloc, need get reference after request.// fill vertices 填充meshBuffer中的顶点数据let vertexOffset = offsetInfo.byteOffset >> 2,vbuf = buffer._vData;if (vData.length + vertexOffset > vbuf.length) {vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset);} else {vbuf.set(vData, vertexOffset);}// fill indices 填充meshBuffer中的顶点索引数组let ibuf = buffer._iData,indiceOffset = offsetInfo.indiceOffset,vertexId = offsetInfo.vertexOffset;for (let i = 0, l = iData.length; i < l; i++) {ibuf[indiceOffset++] = vertexId + iData[i];}}packToDynamicAtlas(comp, frame) {if (CC_TEST) return;if (!frame._original && dynamicAtlasManager && frame._texture.packable && frame._texture.loaded) {let packedFrame = dynamicAtlasManager.insertSpriteFrame(frame);if (packedFrame) {frame._setDynamicAtlasFrame(packedFrame);}}let material = comp._materials[0];if (!material) return;if (material.getProperty('texture') !== frame._texture._texture) {// texture was packed to dynamic atlas, should update uvscomp._vertsDirty = true;comp._updateMaterial();}}
}cc.js.addon(Assembler2D.prototype, {floatsPerVert: 5,verticesCount: 4,indicesCount: 6,uvOffset: 2,colorOffset: 4,
});cc.Assembler2D = Assembler2D;

Assembler的主要几个方法:updateUVs,updateRenderData ,updateVerts,updateWorldVerts,fillBuffers,updateColor,initData,initLocal

那么接下来看看他们的作用,

a: initData: 是创建渲染所需要的数据

分为4个顶点数据和6个顶点索引数据,并且初始化6个顶点数据的值一般是(0,1,2)(1,3,2),如果想要个性化修改图片的边数可以再这里进行重写修改

b:initLocal: 初始化节点的本地坐标(4个坐标,分别是左下,右下,左上,右上的坐标)数组

initData,initLocal在Assembler构造函数中完成。

更新顶点数据和uv数据是在什么时候调用的呢,首先想一想什么时候才能触发更新这些数据的时候,如果一个节点渲染数据没有变化肯定不会触发这些操作,只有需要更新渲染数据的时候才会触发。updateRenderData是触发的条件

protected updateRenderData(comp: cc.RenderComponent) {if (comp._vertsDirty) {this.updateUVs(comp);this.updateVerts(comp);comp._vertsDirty = false;}}

接着就是updateRenderData的调用时机,说起这个的调用时机它跟render-flow有着千丝万缕的联系,讲到render-flow之前 可以看看我之前写的render-flow渲染流的章节,简单的说就是根据二进制的标志位进行渲染的切换,一般的渲染链条一般是:null -> Final -> postRender->children->render -> Opacity_color -> color -> Opacity -> updateRenderData -> Transform -> WorldTransform -> localTransform,这里的updateRenderData标志位为1的时候就会触发 上述的updateRenderData函数

_proto._updateRenderData = function (node) {let comp = node._renderComponent;// 这个就是assembler的更新渲染函数了comp._assembler.updateRenderData(comp);// 渲染之前的_renderFlag是需要update_render_data,这里的操作是执行完updateRenderData函数之后需要将之前的标志位取反也就是证明该操作执行完毕了node._renderFlag &= ~UPDATE_RENDER_DATA;// 转入下一个流程this._next._func(node);
};

 举一反三类似的updateColor是在color标志位为1的时候触发:

_proto._color = function (node) {let comp = node._renderComponent;if (comp) {comp._updateColor();}node._renderFlag &= ~COLOR;this._next._func(node);
};

 相应的fillBuffers的执行时机:render标志位为1的时候触发,每帧执行

_proto._render = function (node) {let comp = node._renderComponent;// 检查合批comp._checkBacth(_batcher, node._cullingMask);// 填充缓冲区 meshBuffercomp._assembler.fillBuffers(comp, _batcher);// 转入下一个流程 children 或者是postRenderthis._next._func(node);
};

 提交渲染数据流程时机:

_proto._postRender = function (node) {let comp = node._renderComponent;// 检查是否可以合批comp._checkBacth(_batcher, node._cullingMask);comp._assembler.postFillBuffers(comp, _batcher);this._next._func(node);
};

 讲了这么多是时候拿个例子🌰来看看了。

二:自定义一个渲染组件(使用自定义的assembler挂载)

有这么个需求就是防止节点结构合批被打乱,自己实现一个渲染组件来改善这种情况。我之前的做法是图片和图片放到一起,label和label放到一起,这样就能达到合批的目的,但是这样做的结果就是代码组织就变得很没有结构性,最好的办法是既能保持节点结构又能达到轻松合批的效果。之所以合批被打乱的原因就是节点遍历cocos默认是深度优先遍历,很容易就会造成节点合批被打乱。此次优化方案(节点采用广度优先遍历的方法)也是论坛的大神提供的思路,在此搬砖分享给各位水友。

1:定义自己的MyAssembler2D,理解这个之前一定要知道渲染数据的构成,注释里面详细的解释了


/*** * 自定义渲染数据* 每个2d渲染数据里面有 * 4个顶点属性数据 为什么是4个是因为一个四边形由四个顶点* 6个顶点索引 -> (一个四边形由两个三角形组成)* * * 每个顶点数据格式:* | x | y | u | v | r | g | b | a | ....*   4   4   4   4   1   1   1   1    (一个顶点总共20个字节的数据)* * 其中uv数据偏移是2个单位,color的偏移是4个单位* */
export default class MyAssembler2D extends cc.Assembler {/** 顶点数据个数 */public verticesCount = 4;/** 顶点索引个数 */public indicesCount = 6;/** 每个顶点数据有几个float32位的值 */public floatsPerVert = 5;/** uv偏移量位2个单位 */public uvOffset = 2;public colorOffset = 4;protected _renderData: cc.RenderData;protected _local: any = null;init(comp: cc.RenderComponent) {super.init(comp);this._renderData = new cc.RenderData();/** 这里的初始化? */this._renderData.init(this);this.initLocal();this.initData();}get verticesFloats() {return this.verticesCount * this.floatsPerVert;}initData() {let data = this._renderData;data.createQuadData(0, this.verticesFloats, this.indicesCount);}initLocal() {this._local = [];this._local.length = 4;}/*** 更新节点的颜色* @param  {cc.RenderComponent} comp* @param  {any} color*/updateColor(comp: cc.RenderComponent, color: any) {if (!this._renderData.uintVDatas) {if (CC_EDITOR) {Editor.log("uintVDatas is undefined");}return;}if (CC_EDITOR) {Editor.log("unitVDatas is ", this._renderData.uintVDatas);}let uintVerts = this._renderData.uintVDatas[0];if (!uintVerts) {if (CC_EDITOR) {Editor.warn("uintVerts is null");return;}};// @ts-ignorecolor = color != null ? color : comp.node.color._val;let floatsPerVert = this.floatsPerVert;let colorOffset = this.colorOffset;for (let i = colorOffset, l = uintVerts.length; i < l; i += floatsPerVert) {uintVerts[i] = color;}if (CC_EDITOR) {Editor.log("verts is ", uintVerts);}}getBuffer() {// @ts-ignorereturn cc.renderer._handle._meshBuffer;}/** 更新顶点数据到世界坐标系 */updateWorldVerts(comp) {if (CC_NATIVERENDERER) {this.updateWorldVertsNative(comp);} else {this.updateWorldVertsWebGL(comp);}}updateWorldVertsNative(comp: cc.RenderComponent) {let local = this._local;let verts = this._renderData.vDatas[0];let floatsPerVert = this.floatsPerVert;let l = local[0], r = local[1], b = local[2], t = local[3];let index = 0;// left bottom verts[index] = l;verts[index + 1] = b;index += floatsPerVert;// right bottomverts[index] = r;verts[index + 1] = b;index += floatsPerVert;// left topverts[index] = l;verts[index + 1] = t;index += floatsPerVert;// right topverts[index] = r;verts[index + 1] = t;}/*** 更新webGL平台世界坐标 cocos 采用行向量的方式(优点: 直接数组操作)来与矩阵运算的 M * V(列式向量) V(行式向量) * M* @param  {cc.RenderComponent} comp*/updateWorldVertsWebGL(comp: cc.RenderComponent) {if (CC_EDITOR) {Editor.warn("updateWorldVerts");}let local = this._local;// 顶点数据let verts = this._renderData.vDatas[0];// @ts-ignore 节点的世界矩阵let matrix = comp.node._worldMatrix;let m = matrix.m,a = m[0], b = m[1], c = m[4], d = m[5],tx = m[12], ty = m[13];// a, d 缩放因子参数  b,c 斜切因子参数    let vl = local[0], vr = local[2],vb = local[1], vt = local[3];/*                         -          -m00 = 1, m01 = 0, m02 = 0, m03 = 0, | 1  0  0  0 |m04 = 0, m05 = 1, m06 = 0, m07 = 0, | 0  1  0  0 |m08 = 0, m09 = 0, m10 = 1, m11 = 0, | 0  0  1  0 |m12 = 0, m13 = 0, m14 = 0, m15 = 1  | 0  0  0  1 |-          -*/let justTranslate = a === 1 && d === 1 && b === 0 && c === 0;let index = 0;let floatsPerVert = this.floatsPerVert;if (CC_EDITOR) {Editor.warn("justTranslate is ", justTranslate);}if (justTranslate) {// left bottomverts[index] = vl + tx;verts[index + 1] = vb + ty;index += floatsPerVert;// right bottomverts[index] = vr + tx;verts[index + 1] = vb + ty;index += floatsPerVert;// left topverts[index] = vl + tx;verts[index + 1] = vt + ty;index += floatsPerVert;// right topverts[index] = vr + tx;verts[index + 1] = vt + ty;} else {// 根据矩阵运算规则 【x,y,z,w】* M可以得到以下结果/**            -            -*            |  a  b  0  0  |*            |  c  d  0  0  |*[x,y,z,1] * |  0  0  1  0  | = 【a*x + cy + tx,bx + dy + ty,0,1】*            | tx  ty 0  1  |*             -            -*/// left bottomverts[index] = vl * a + c * vb + tx;verts[index + 1] = vl * b + d * vb + ty;index += floatsPerVert;// right bottom verts[index] = vr * a + c * vb + tx;verts[index + 1] = vr * b + d * vb + ty;index += floatsPerVert;// left topverts[index] = vl * a + c * vt + tx;verts[index + 1] = vl * b + d * vt + ty;index += floatsPerVert;// right topverts[index] = vr * a + c * vt + tx;verts[index + 1] = vr * b + d * vt * ty;}}packToDynamicAtlas(comp: cc.RenderComponent, frame: any) {if (CC_TEST) return;if (!frame._original && cc.dynamicAtlasManager && frame._texture.packable) {let packedFrame = cc.dynamicAtlasManager.insertSpriteFrame(frame);// @ts-ignoreif (packedFrame) {frame._setDynamicAtlasFrame(packedFrame);}}// @ts-ignorelet material = comp._materials[0];if (!material) return;if (material.getProperty('texture') !== frame._texture) {// @ts-ignorecomp._vertsDirty = true;// @ts-ignorecomp._updateMaterial();}}protected updateUVs(comp: cc.RenderComponent) {if (CC_EDITOR) {Editor.warn("updateUVS...");}// 4个顶点的uv坐标 对应左下,右下,左上,右上let uv = [0, 0, 1, 0, 0, 1, 1, 1];// if (comp instanceof cc.Sprite) {//     // @ts-ignore//     uv = comp._spriteFrame.uv;// }let uvOffset = this.uvOffset;let floatsPerVert = this.floatsPerVert;let verts = this._renderData.vDatas[0];for (let i = 0; i < 4; i++) {let srcOffset = i * 2;let dstOffset = floatsPerVert * i + uvOffset;verts[dstOffset] = uv[srcOffset];verts[dstOffset + 1] = uv[srcOffset + 1];}}// 每帧调用public fillBuffers(comp: cc.RenderComponent, render: cc.ModelBatcher) {if (CC_EDITOR) {// Editor.warn("fillBuffers...");}// if(r)if (render.worldMatDirty) {this.updateWorldVerts(comp);}let renderData = this._renderData;let vData = renderData.vDatas[0];let iData = renderData.iDatas[0];// 获取meshBufferlet buffer = this.getBuffer();let offsetInfo = buffer.request(this.verticesCount, this.indicesCount);let vertexOffset = offsetInfo.byteOffset >> 2,vbuf = buffer._vData;if (vData.length + vertexOffset > vbuf.length) {vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset);} else {vbuf.set(vData, vertexOffset);}let ibuf = buffer._iData, indiceOffset = offsetInfo.indiceOffset, vertexId = offsetInfo.vertexOffset;for (let i = 0, l = iData.length; i < l; i++) {ibuf[indiceOffset++] = vertexId + iData[i];}}protected updateVerts(comp: cc.RenderComponent) {if (CC_EDITOR) {Editor.warn("updateVerts...");}let node: cc.Node = comp.node,cw = node.width,ch = node.height,// (锚点x * cw,锚点y * ch)appx = node.anchorX * cw,appy = node.anchorY * ch,l, b, r, t;// 左边界l = -appx;// 下边界b = -appy;// 右边界r = cw - appx;// 上边界t = ch - appy;this._local[0] = l;this._local[1] = b;this._local[2] = r;this._local[3] = t;this.updateWorldVerts(comp);}protected updateRenderData(comp: cc.RenderComponent) {if (comp._vertsDirty) {this.updateUVs(comp);this.updateVerts(comp);comp._vertsDirty = false;}}
}

2:定义自己的MySpriteAssembler2D

import MyAssembler2D from "./MyAssembler2D";export default class MySimpleSpriteAssembler2D extends MyAssembler2D {protected updateRenderData(sprite: cc.Sprite) {// @ts-ignorethis.packToDynamicAtlas(sprite, sprite._spriteFrame);super.updateRenderData(sprite);}/*** 将图片的uv数据复制到 renderData.vData中* @param  {cc.Sprite} sprite* @returns void*/updateUVs(sprite: cc.Sprite): void {if (CC_EDITOR) {Editor.log("updateUVs...");}// @ts-ignorelet uv = sprite._spriteFrame.uv;let uvOffset = this.uvOffset;let floatsPerVert = this.floatsPerVert;let verts = this._renderData.vDatas[0];for (let i = 0; i < 4; i++) {let srcOffset = i * 2;let dstOffset = floatsPerVert * i + uvOffset;verts[dstOffset] = uv[srcOffset];verts[dstOffset + 1] = uv[srcOffset + 1];}}/*** 更新图片的渲染顶点数据* @param  {cc.Sprite} sprite* @returns void*/protected updateVerts(sprite: cc.Sprite): void {if (CC_EDITOR) {Editor.log("updateVerts ");}let node = sprite.node,cw = node.width, ch = node.height,appx = node.anchorX * cw, appy = node.anchorY * ch, l, b, r, t;if (sprite.trim) {l = -appx;b = -appy;r = cw - appx;t = ch - appy;} else {let frame = sprite.spriteFrame,// @ts-ignoreow = frame._originalSize.width, oh = frame._originalSize.height,// @ts-ignorerw = frame._rect.width, rh = frame._rect.height,// @ts-ignoreoffset = frame._offset, scaleX = cw / ow, scaleY = ch / oh;let trimLeft = offset.x + (ow - rw) / 2;let trimRight = offset.x - (ow - rw) / 2;let trimBottom = offset.y + (oh - rh) / 2;let trimTop = offset.y - (oh - rh) / 2;l = trimLeft * scaleX - appx;b = trimBottom * scaleY - appy;r = trimRight * scaleX + cw - appx;t = trimTop * scaleY + ch - appy;}this._local[0] = l;this._local[1] = b;this._local[2] = r;this._local[3] = t;this.updateWorldVerts(sprite);}
}

 3:定义自己的合批Assembler: BatchAssembler2D

import { recordData } from "../../bingoslots/scripts/server_data/MockData";
import MySimpleSpriteAssembler2D from "./MySimpleSpriteAssembler2D";/*** * 分层合批处理assembler(引擎内置为深度遍历的方式不方便合批处理)* */
// @ts-ignore 渲染掩码用于检测当前节点的渲染标志位是否含有 render和postRender标志位
const RENDER_MASK = cc.RenderFlow.FLAG_RENDER | cc.RenderFlow.FLAG_POST_RENDER;
// @ts-ignore 透明度掩码用于检测当前节点的渲染标志位是否含有 Opacity 和WorldTransform标志位
const PROP_DIRTY_MASK = cc.RenderFlow.FLAG_OPACITY | cc.RenderFlow.FLAG_WORLD_TRANSFORM;let BATCH_OPTIMIZE_SWITCH: boolean = true;export default class BatchingAssembler2D extends MySimpleSpriteAssembler2D {protected _layers: any[];fillBuffers(comp: cc.RenderComponent, render: cc.ModelBatcher) {super.fillBuffers(comp, render);if (CC_EDITOR || CC_NATIVERENDERER) return;if (!BATCH_OPTIMIZE_SWITCH) return;let layer = [];this._layers = [layer];// @ts-ignorelet worldTransformFlag = render.worldMatDirty ? cc.RenderFlow.FLAG_WORLD_TRANSFORM : 0;// @ts-ignorelet worldOpacityFlag = render.parentOpacityDirty ? cc.RenderFlow.FLAG_OPACITY_COLOR : 0;let dirtyFlag = worldTransformFlag | worldOpacityFlag;comp.node['__myDirtyFlag'] = dirtyFlag;// 广度优先遍历let queue: cc.Node[] = [];queue.push(comp.node);let depth = 0, end = 1, iter = 0;let myRenderFlag;while (iter < queue.length) {let node = queue[iter++];dirtyFlag = node['__myDirtyFlag'];for (let c of node.children) {// @ts-ignoreif (!c._activeInHierarchy || c.opacity == 0) {continue;}// @ts-ignoremyRenderFlag = c._renderFlag & RENDER_MASK;if (myRenderFlag > 0) {// 携带了渲染的标志位// 移除子节点的renderFlag,是renderFlow不执行它的render函数c["__myRenderFlag"] = myRenderFlag;// @ts-ignore 移除之前有的渲染标志位c._renderFlag &= ~myRenderFlag;// 将本来有渲染标志位的节点给加入到层级队列里面layer.push(c);}// @ts-ignore 逐层传递父节点的dirtyFlagc["__myDirtyFlag"] = dirtyFlag | (c._renderFlag & PROP_DIRTY_MASK);// 将遍历的子节点推入队列queue.push(c);}if (iter == end) {++depth;end = queue.length;layer = [];this._layers.push(layer);}}if (CC_EDITOR) {Editor.log("layers is ", this._layers);}console.log("layers is ", this._layers);}postFillBuffers(comp: cc.RenderComponent, render: cc.ModelBatcher) {if (CC_EDITOR) {// Editor.log("postFillbuffers");}let originWorldMatDirty = render.worldMatDirty;if (!BATCH_OPTIMIZE_SWITCH || !this._layers) return;BATCH_OPTIMIZE_SWITCH = false;let myRenderFlag, myDirtyFlag;for (let layer of this._layers) {if (layer.length == 0) {continue;}for (let c of layer) {myRenderFlag = c['__myRenderFlag'];myDirtyFlag = c['__myDirtyFlag'];render.worldMatDirty = myDirtyFlag > 0 ? 1 : 0;// 还原之前被修改的标志位c._renderFlag |= myRenderFlag;// @ts-ignorecc.RenderFlow.flows[myRenderFlag]._func(c);}}this._layers = null;BATCH_OPTIMIZE_SWITCH = true;render.worldMatDirty = originWorldMatDirty;}}

 4:定义自己的合批渲染组件:LayeredBatchingSprite

import BatchingAssembler2D from "./BatchingAssembler2D";
import MySimpleSpriteAssembler2D from "./MySimpleSpriteAssembler2D";const { ccclass } = cc._decorator;@ccclass
export default class LayeredBatchingSprite extends cc.Sprite {protected onEnable(): void {super.onEnable();if (!CC_EDITOR || !CC_NATIVERENDERER) {// @ts-ignore 添加一个postRender 渲染标志位 来执行 postFillBuffersthis.node._renderFlag |= cc.RenderFlow.FLAG_POST_RENDER;}}_resetAssembler() {this.setVertsDirty();let assembler = this._assembler = new BatchingAssembler2D();assembler.init(this);}
}

至此一个广度优先遍历的节点合批方案完成,希望给给为水友一点启发 

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...