cocos creator版本2.4.11
一個mask占用drawcall 3個以上,針對游戲中技能圖標,cd,以及多玩家頭像,是有很大優化空間
1.上代碼,只適合單獨圖片的,不適合在圖集中的圖片
const { ccclass, property } = cc._decorator;const gfx = cc.gfx;
cc.Class({extends: cc.Component,properties: {radius: 100, // 圓的半徑segments: 32, // 圓的細分段數(頂點數)/*** !#en The sprite frame of the sprite.* !#zh 精靈的精靈幀* @property spriteFrame* @type {SpriteFrame}* @example* sprite.spriteFrame = newSpriteFrame;*/spriteFrame: {default: null,type: cc.SpriteFrame},},onLoad() {let renderer = this.node.getComponent(cc.MeshRenderer);if (!renderer) {renderer = this.node.addComponent(cc.MeshRenderer);}renderer.mesh = null;this.renderer = renderer;let builtinMaterial = cc.MaterialVariant.createWithBuiltin("unlit");renderer.setMaterial(0, builtinMaterial);this._applySpriteFrame();this.setMesh();},setMesh(){// 創建 Meshlet mesh = new cc.Mesh();// 計算頂點和 UVlet positions = [];let uvs = [];let indices = [];let colors = [];// 圓心頂點positions.push(cc.v2(0, 0)); // 圓心uvs.push(cc.v2(0.5, 0.5)); // 圓心 UVcolors.push(cc.Color.WHITE); // 圓心顏色// 圓邊緣頂點for (let i = 0; i <= this.segments; i++) {let angle = (i / this.segments) * Math.PI * 2; // 計算角度let x = Math.cos(angle) * this.radius; // 計算 x 坐標let y = Math.sin(angle) * this.radius; // 計算 y 坐標positions.push(cc.v2(x, y)); // 添加頂點uvs.push(cc.v2((x / this.radius + 1) / 2, 1-(y / this.radius + 1) / 2)); // 添加 UVcolors.push(cc.Color.WHITE); // 添加顏色}// 設置索引(三角形扇)for (let i = 1; i <= this.segments; i++) {indices.push(0); // 圓心indices.push(i); // 當前頂點indices.push(i + 1); // 下一個頂點}mesh.init(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 },]), positions.length, true);mesh.setVertices(gfx.ATTR_POSITION, positions);mesh.setVertices(gfx.ATTR_UV0, uvs);mesh.setIndices(indices);this.renderer.mesh = mesh;},// 更新圖片_applySpriteFrame() {// cc.log('_applySpriteFrame');if (this.spriteFrame) {const renderer = this.renderer;let material = renderer._materials[0];// Reset materiallet texture = this.spriteFrame.getTexture();material.define("USE_DIFFUSE_TEXTURE", true);material.setProperty('diffuseTexture', texture);}}
});
這個js組件,綁定到節點上,把要渲染的spriteFrame掛在上面,運行就可以了,這種方式只適合單獨圖片,不適合圖集中的圖片
運行效果,下面是對比了這個圖片
說明:這種方式是直
接修改圖片的mesh網格結構,使用meshRenderer組件,不能掛載sprite組件,使用shader也可以達到效果,但是shader是在Gpu層修改顯示,圖片形狀沒有變,這個是運行的時候直接修改形狀,而且shader修改的話會有問題,例如打斷動態合批,如果項目勾選了動態合批或者圖片在圖集中,shader修改是無效的
這種方式可以降低mask增加的drawcall
2.工具式的,直接調用,升級版,可以修改圖集中的某個圖片的顯示
const { ccclass, property } = cc._decorator;const gfx = cc.gfx;
cc.Class({extends: cc.Component,properties: {radius: 100, // 圓的半徑segments: 32, // 圓的細分段數(頂點數)/*** !#en The sprite frame of the sprite.* !#zh 精靈的精靈幀* @property spriteFrame* @type {spriteFrame}*/spriteFrame: {default: null,type: cc.spriteFrame,},},/**設置數據顯示 需要等spriteFrame加載完成后調用,可以拿到實際的圖片* radius: 半徑* segments: 圓細分段數,越多會越圓滑,但是性能消耗會更大* node:節點,這里需要使用mesheRenderer組件,所以需要把sprite剔除* isAtlas:是否是圖集中的圖片*/setDataShow(node, radius, segments, isAtlas) {// MeshRendererlet renderer = this.node.getComponent(cc.MeshRenderer);if (!renderer) {renderer = this.node.addComponent(cc.MeshRenderer);}renderer.mesh = null;this.renderer = renderer;let builtinMaterial = cc.MaterialVariant.createWithBuiltin("unlit");renderer.setMaterial(0, builtinMaterial);renderer.enabled = false;this.radius = radius;this.segments = segments;let sp = node.getComponent(cc.Sprite);if (sp) {this.spriteFrame = sp.spriteFrame;node.removeComponent(cc.Sprite);}// 把圖片加載到renderer上的材質this.applySpriteFrame();// 設置meshif (isAtlas) {// 大圖集中的texturethis.setMeshByAtlas();} else {// 單個圖片this.setMesh();}// 這里必須延遲一幀,不然不會刷新mesh,顯示不出來圖片setTimeout(() => {if(cc.isValid(renderer)){renderer.enabled = true;}}, 100);},/**更新mesh,在圖集中的 */setMeshByAtlas() {let uv = this.spriteFrame.uv;// 創建 Meshlet mesh = new cc.Mesh();// 計算頂點和 UVlet positions = [];let uvs = [];let indices = [];let colors = [];// 圓心頂點positions.push(cc.v2(0, 0)); // 圓心uvs.push(cc.v2((uv[6] + uv[0]) / 2, (uv[7] + uv[1]) / 2)); // 圓心 UV(取中心點)colors.push(cc.Color.WHITE); // 圓心顏色// 圓邊緣頂點for (let i = 0; i <= this.segments; i++) {let angle = (i / this.segments) * Math.PI * 2; // 計算角度let x = Math.cos(angle) * this.radius; // 計算 x 坐標let y = Math.sin(angle) * this.radius; // 計算 y 坐標positions.push(cc.v2(x, y)); // 添加頂點// 計算 UV 坐標(根據圖集的 UV 信息進行映射)let u = (x / this.radius + 1) / 2; // 歸一化到 [0, 1]let v = (y / this.radius + 1) / 2; // 歸一化到 [0, 1]let uvX = uv[0] + (uv[2] - uv[0]) * u; // 根據圖集 UV 計算實際 UVlet uvY = uv[1] + (uv[5] - uv[1]) * v; // 根據圖集 UV 計算實際 UVuvs.push(cc.v2(uvX, uvY)); // 添加 UVcolors.push(cc.Color.WHITE); // 添加顏色}// 設置索引(三角形扇)for (let i = 1; i <= this.segments; i++) {indices.push(0); // 圓心indices.push(i); // 當前頂點indices.push(i + 1); // 下一個頂點}mesh.init(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 },]), positions.length, true);mesh.setVertices(gfx.ATTR_POSITION, positions);mesh.setVertices(gfx.ATTR_UV0, uvs);mesh.setIndices(indices);this.renderer.mesh = mesh;},// 更新mesh,單獨圖片的setMesh() {// 創建 Meshlet mesh = new cc.Mesh();// 計算頂點和 UVlet positions = [];let uvs = [];let indices = [];let colors = [];// 圓心頂點positions.push(cc.v2(0, 0)); // 圓心uvs.push(cc.v2(0.5, 0.5)); // 圓心 UVcolors.push(cc.Color.WHITE); // 圓心顏色// 圓邊緣頂點for (let i = 0; i <= this.segments; i++) {let angle = (i / this.segments) * Math.PI * 2; // 計算角度let x = Math.cos(angle) * this.radius; // 計算 x 坐標let y = Math.sin(angle) * this.radius; // 計算 y 坐標positions.push(cc.v2(x, y)); // 添加頂點uvs.push(cc.v2((x / this.radius + 1) / 2, (y / this.radius + 1) / 2)); // 添加 UVcolors.push(cc.Color.WHITE); // 添加顏色}// 設置索引(三角形扇)for (let i = 1; i <= this.segments; i++) {indices.push(0); // 圓心indices.push(i); // 當前頂點indices.push(i + 1); // 下一個頂點}mesh.init(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 },]), positions.length, true);mesh.setVertices(gfx.ATTR_POSITION, positions);mesh.setVertices(gfx.ATTR_UV0, uvs);mesh.setIndices(indices);this.renderer.mesh = mesh;},// 更新圖片applySpriteFrame() {// cc.log('_applySpriteFrame');if (this.spriteFrame) {const renderer = this.renderer;let material = renderer._materials[0];// Reset materialmaterial.define("USE_DIFFUSE_TEXTURE", true);material.setProperty('diffuseTexture', this.spriteFrame.getTexture());}},});
外部調用這個組件的方法,setDataShow傳對應的參數就可以,節點上需要掛sprite組件,sprite更新圖片或者初始化加載的時候,調用這個方法setDataShow,同時兼容刪除節點的sprite組件,如果不想掛載sprite組件,默認直接掛上meshRenderer組件,需要自己修改下代碼,把參數node直接改成傳對應的spriteFrame圖片?
Cocos Creator 的紋理坐標系(UV 坐標系)的 Y 軸方向是?從上到下?的,如果結果圖片y是反向的,可以設代碼修改uvs中的y的取值
-
將?
v
?的計算改為?1 - (y / radius + 1) / 2
,即對 Y 方向取反。