//*// import { cclegacy, Color, Director, director, Game, game, gfx, Material, MeshBuffer, Node, renderer, resources, sp, Texture2D, UIRenderer } from 'cc'; import { EDITOR, JSB } from 'cc/env'; let _uid = 0; let _texid = 0; let _hash = -1; let _texCount = 0; let _spineCache = []; //是否使用,shader传参变色效果 //如不需要,请设置false , 节省shader计算 const useShaderVar = true; declare module "cc" { interface Component { shaderVar: number; // 类型,0 不变,1 置灰,2闪白?,3待定?,4待定? // 参数整数部分为类型,小数部分为插值, // 例如 1.999,代表是 ,近似置灰100% // 例如 2.5 代表是,闪白50%插值 // 默认原色 <=1 } } let _spineTexture: Texture2D = null; let _spineBuffers: Float32Array = null; let _texMaps: Map = new Map(); let _materialMaps: Map = new Map(); const _region = new gfx.BufferTextureCopy(); _region.texExtent.width = 128; _region.texExtent.height = 128; _region.texOffset.x = 0; _region.texOffset.y = 0; //@ts-ignore Material.prototype.spineCache = []; export const SpineBatch: any = { enable: false, parent: null, next: function () { _texMaps.clear(); _texCount = 0; _texid = 0; _hash = -1; }, reset: function () { this.next(); _uid = 0; }, active: function () { return this.enable && this.parent; }, update: function () { if (!this.active()) return; // _spineTexture?.uploadData(_spineBuffers); for (let value of _materialMaps.values()) { if (value) { value.n = 0; } } if (_uid > 0 && _spineTexture) { //@ts-ignore const gfxDevice = _spineTexture._getGFXDevice(); const gfxTexture = _spineTexture.getGFXTexture(); const hight = (~~((_uid * 4 - 1) / 128)) + 1; _region.texExtent.height = hight > 128 ? 128 : hight; gfxDevice.copyBuffersToTexture([_spineBuffers], gfxTexture, [_region]); } } }; const _getMaterialForBlendAndTint = function (comp: any, mat: any) { // _mat = mat; //保存当前 let key = _hash = mat.hash; let caches = _materialMaps.get(key); if (caches === undefined) { caches = { mats: [], n: 0 }; _materialMaps.set(key, caches); } let inc = caches.n++; let materialCache = caches.mats; let inst: any = materialCache[inc]; if (inst !== undefined) { _spineCache = inst.spineCache; return inst; } let type = key & 0xf; let src = (key >> 4) & 0xf; let dst = (key >> 8) & 0xf; const material = SpineBatch.parent; inst = new renderer.MaterialInstance({ parent: material }); materialCache[inc] = inst; inst.overridePipelineStates({ blendState: { blendColor: Color.WHITE, targets: [{ blendEq: gfx.BlendOp.ADD, blendAlphaEq: gfx.BlendOp.ADD, blendSrc: src, blendDst: dst, blendSrcAlpha: src, blendDstAlpha: dst, }], }, }); inst.recompileShaders({ TWO_COLORED: (type === 1) ? true : false, USE_LOCAL: false, USE_SHADER_VAR: useShaderVar }); inst.setProperty('spineData', _spineTexture); _spineCache = inst['spineCache'] = []; inst.addRef(); return inst; } //预加载多纹理材质 const loadMaterial = function () { if (SpineBatch.parent) return; resources.load("multSpines/Mult-spine", Material, (err, material) => { if (!err) { //material.passes[0].tryCompile(); SpineBatch.parent = material; material material.addRef(); } }); } const initSpineDatas = function () { SpineBatch.enable = false; const size = 128; const gfxDevice = director.root!.device; const hasFeatureFloatTexture = gfxDevice.getFormatFeatures(gfx.Format.RGBA32F) & gfx.FormatFeatureBit.SAMPLED_TEXTURE; if (hasFeatureFloatTexture) { SpineBatch.enable = true; _spineTexture = new Texture2D(); _spineBuffers = new Float32Array(size * size * 4); _spineTexture.setFilters(Texture2D.Filter.NEAREST, Texture2D.Filter.NEAREST); _spineTexture.reset({ width: size, height: size, format: Texture2D.PixelFormat.RGBA32F, mipmapLevel: 0 }); _spineTexture.uploadData(_spineBuffers); } } const injectSpine = function () { const Skeleton: any = sp.Skeleton.prototype; Skeleton.shaderVar = 0; //shader自定义参数 Skeleton.isMult = false; //是否多纹理合批 //强制关闭合批 Object.defineProperty(Skeleton, "enableBatch", { get: function () { if (this.customMaterial) return this._enableBatch; return false; }, set: function (value: boolean) { if (this.customMaterial) { if (value !== this._enableBatch) { this._enableBatch = value; this._updateBatch(); } } } }); //获取共享材质 const getMaterialForBlendAndTint = Skeleton.getMaterialForBlendAndTint; Skeleton.getMaterialForBlendAndTint = function (src: any, dst: any, type: any) { if (!SpineBatch.active() || this.customMaterial) { this.isMult = false; return getMaterialForBlendAndTint.call(this, src, dst, type); } let hash = type | (src << 4) | (dst << 8); this.isMult = true; return { hash: hash }; } //绘画数据处理 const render = Skeleton._render; Skeleton._render = function (batcher: any): void { if (!this.isMult) return render.call(this, batcher); let indicesCount = 0; if (this.renderData && this._drawList.length > 0) { const rd = this.renderData; const chunk = rd.chunk; const accessor = chunk.vertexAccessor; const meshBuffer = rd.getMeshBuffer()!; const origin = meshBuffer.indexOffset; //@ts-ignore let floatStride = meshBuffer.vertexFormatBytes >> 2; let isMultDraw = this._drawList.length > 1; let offset = chunk.vertexOffset; let length = rd.vertexCount; let vbuff = rd.chunk.vb; let ibuff = rd.indices; let isCommit = false; // Fill index buffer for (let i = 0; i < this._drawList.length; i++) { const dc = this._drawList.data[i]; if (dc.texture) { batcher.commitMiddleware(this, meshBuffer, origin + dc.indexOffset, dc.indexCount, dc.texture, dc.material, this._enableBatch); isCommit = true; if (isMultDraw) { let j = dc.indexOffset; length = dc.indexCount; let id = _uid * 10 + _texid; while (length--) { vbuff[(ibuff[j++] - offset) * floatStride + 2] = id; //-offset } } } indicesCount += dc.indexCount; } if (!isMultDraw && isCommit) { let id = _uid * 10 + _texid; for (let i = 0, j = 0; i < length; i++) { vbuff[j + 2] = id; j += floatStride; } } if (isCommit) { let i = 16 * _uid++; let datas = _spineBuffers; let m = this.node.worldMatrix; datas[i] = m.m00; datas[i + 1] = m.m01; datas[i + 2] = m.m02; datas[i + 3] = this.shaderVar; //m.m03; datas[i + 4] = m.m04; datas[i + 5] = m.m05; datas[i + 6] = m.m06; datas[i + 7] = m.m07; datas[i + 8] = m.m08; datas[i + 9] = m.m09; datas[i + 10] = m.m10; datas[i + 11] = m.m11; datas[i + 12] = m.m12; datas[i + 13] = m.m13; datas[i + 14] = m.m14; datas[i + 15] = m.m15; } const subIndices = rd.indices!.subarray(0, indicesCount); accessor.appendIndices(chunk.bufferId, subIndices); accessor.getMeshBuffer(chunk.bufferId).setDirty(); } } } //多纹理合批流程 const injectBatch = function () { const Batcher2D: any = cclegacy.internal.Batcher2D.prototype; const commitMiddleware = Batcher2D.commitMiddleware; Batcher2D.commitMiddleware = function ( comp: UIRenderer, meshBuffer: MeshBuffer, indexOffset: number, indexCount: number, tex: any, mat: any, enableBatch: boolean, ) { //@ts-ignore if (!comp.isMult) { return commitMiddleware.call(this, comp, meshBuffer, indexOffset, indexCount, tex, mat, enableBatch); } // check if need merge draw batch const texture = tex.getGFXTexture(); let tid = texture.objectID; _texid = _texMaps.get(tid); let isFlush = (_texid === undefined && _texCount >= 8); if (!isFlush && this._middlewareBuffer === meshBuffer && _hash === mat.hash && this._middlewareIndexStart + this._middlewareIndexCount === indexOffset && this._currLayer === comp.node.layer) { this._middlewareIndexCount += indexCount; } else { this.autoMergeBatches(this._currComponent!); this.resetRenderStates(); this._currComponent = comp; this._currTexture = texture; this._currSampler = tex.getGFXSampler(); this._currTextureHash = tex.getHash(); this._currLayer = comp.node.layer; this._currSamplerHash = this._currSampler.hash; this._currHash = 0; this._currTransform = null;//enableBatch ? null : comp.node; this._middlewareEnableBatch = false;//enableBatch; this._middlewareBuffer = meshBuffer; this._middlewareIndexStart = indexOffset; this._middlewareIndexCount = indexCount; this._currMaterial = _getMaterialForBlendAndTint(comp, mat); } this._currIsMiddleware = true; if (_texCount === 0 || _texid === undefined) { _texid = _texCount++; _texMaps.set(tid, _texid); } if (_spineCache[_texid] !== tid) { _spineCache[_texid] = tid; this._currMaterial.setProperty("texture" + _texid, tex); } } const mergeBatchesForMiddleware = Batcher2D.mergeBatchesForMiddleware; Batcher2D.mergeBatchesForMiddleware = function (renderComp: UIRenderer) { if (this._currIsMiddleware) SpineBatch.next(); mergeBatchesForMiddleware.call(this, renderComp); } } //游戏启动前,务必加载多纹理材质 game.once(Game.EVENT_GAME_INITED, () => { if (EDITOR || JSB || !sp.Skeleton) return; initSpineDatas(); if (SpineBatch.enable) { loadMaterial(); injectSpine(); injectBatch(); } }); game.on(Game.EVENT_RESTART, () => { if (EDITOR || JSB || !sp.Skeleton) return; if (SpineBatch.enable) { SpineBatch.parent = null; loadMaterial(); } }); director.on(Director.EVENT_AFTER_DRAW, (dt) => { SpineBatch.reset(); }); director.on(Director.EVENT_BEFORE_COMMIT, (dt) => { SpineBatch.update(); }); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //以下代码,仅供测试demo使用,请不要在项目中使用 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const changeBatch = function (node: Node, batch: boolean) { if (!node) return; let skeleton = node.getComponent(sp.Skeleton); if (skeleton) { skeleton.enableBatch = batch; skeleton.material = null; //@ts-ignore skeleton._updateBatch(); } let childs = node.children; for (let i = 0; i < childs.length; i++) { changeBatch(childs[i], batch); } } export const switchMultSpine = function (useMult: boolean, useBatch: boolean) { SpineBatch.enable = useMult; const Skeleton: any = sp.Skeleton.prototype; if (!useMult) { Object.defineProperty(Skeleton, "enableBatch", { get: function () { return this._enableBatch; }, set: function (value: boolean) { if (value !== this._enableBatch) { this._enableBatch = value; this._updateBatch(); } } }); } else { Object.defineProperty(Skeleton, "enableBatch", { get: function () { if (this.customMaterial) return this._enableBatch; return false; }, set: function (value: boolean) { if (this.customMaterial) { if (value !== this._enableBatch) { this._enableBatch = value; this._updateBatch(); } } } }); } let scene = director.getScene() as Node; if (scene) { changeBatch(scene, useBatch); } } // */