unity
运行替换插槽的测试例
可以看到切换插槽是可以参与渲染排序的,unity在这块的确做的非常完善,包括9.ReplaceSkin
也是经常用的功能。
应该是dragonBones官方对unity的支持非常到位,毕竟这个仓库是DragonBones官方维护的。
cocos
挂点
先查挂点数据是如何同步的,挂点都是在子节点上
查renderFlow意义不大
从Assembler入手,主体逻辑
js 复制代码 let ArmatureDisplay = cc.Class({ name: 'dragonBones.ArmatureDisplay', extends: RenderComponent, })
- extensions\dragonbones\webgl-assembler.js 绑定Assembler
js 复制代码 export default class ArmatureAssembler extends Assembler { updateRenderData (comp, batchData) {} fillBuffers (comp, renderer) { // 组装顶点的核心逻辑 // renderer._meshBuffer == cc.renderer._handle._meshBuffer 是等价的 _buffer = renderer._meshBuffer; // Traverse all armature. renderer.worldMatDirty++; // 这个worldMatDirty的作用参考扩展 // 这个函数会填充_buffer this.realTimeTraverse(armature, worldMat, 1.0); // sync attached node matrix, 同步挂点的逻辑,需要关注下attachUtil的逻辑 // 主要逻辑就是更新了attachNode的矩阵,并且标记对应的flag // 这样当再次渲染到这些node的时候,render_flow会自动更新相关的属性 comp.attachUtil._syncAttachedNode(); } } Assembler.register(Armature, ArmatureAssembler);
_syncAttachedNode () { let bone = isCached ? boneInfos[boneNode._boneIndex] : boneNode._bone; // 多了一个_bone属性,在组件初始化的时候会赋值好 }
dragonBones_ArmatureDisplay._armature 来源
组件初始化流程,在脚本初始化的时候,就会调用_buildArmature
函数,是初始化逻辑的一部分
js 复制代码 ctor () { this.attachUtil = new AttachUtil(); this._factory = dragonBones.CCFactory.getInstance(); // 工厂单例 }, _buildArmature(){ // 解析ske.json,armatureKey采用的是 ske_json_uuid # png_uuid // 调用dblib.parseDragonBonesData(ske_json_data, name) 将原始数据解析为 DragonBonesData 实例,并缓存到工厂中。 // 牵扯到dblib的就暂时不关心了,dblib提供了类似回调的机制,相关的适配逻辑代码在CCFactory.js this._armatureKey = this.dragonAsset.init(this._factory, atlasUUID); if (this.isAnimationCached()) { this._armature = this._armatureCache.getArmatureCache(this.armatureName, this._armatureKey, atlasUUID); } if (CC_EDITOR || this._cacheMode === AnimationCacheMode.REALTIME) { // 落在这个逻辑里面,需要看下_factory的来源 this._displayProxy = this._factory.buildArmatureDisplay(this.armatureName, this._armatureKey, "", atlasUUID); // 来源就在这里,需要往上看 this._armature = this._displayProxy._armature; } // 将挂点和bones关联起来,其中node._bone就是在这里面赋值的 this.attachUtil._associateAttachedNode(); }
最核心的逻辑: realTimeTraverse填充渲染数据
既然我们知道最终数据是填充到_buffer里面了,直接倒推逻辑即可
js 复制代码 export default class ArmatureAssembler extends Assembler { realTimeTraverse (armature, parentMat, parentOpacity){ // armature 来自 dragonBones_ArmatureDisplay._armature,它是db的数据类型 let slots = armature._slots; let vbuf, ibuf, uintbuf; let material; let vertices, indices; let slotColor; let slot; let slotMat; let slotMatm; let offsetInfo; for (let i = 0, l = slots.length; i < l; i++) { slot = slots[i]; // 需要看下这个来源 slotColor = slot._color; if (!slot._visible || !slot._displayData) continue; if (parentMat) { slot._mulMat(slot._worldMatrix, parentMat, slot._matrix); } else { Mat4.copy(slot._worldMatrix, slot._matrix); } if (slot.childArmature) { this.realTimeTraverse(slot.childArmature, slot._worldMatrix, parentOpacity * slotColor.a / 255); continue; } // 这里可以考虑把纹理换了 material = _getSlotMaterial(slot.getTexture(), slot._blendMode); if (!material) { continue; } if (_mustFlush || material.getHash() !== _renderer.material.getHash()) { _mustFlush = false; _renderer._flush(); _renderer.node = _node; _renderer.material = material; } _handleColor(slotColor, parentOpacity); slotMat = slot._worldMatrix; slotMatm = slotMat.m; vertices = slot._localVertices; // 顶点数据来源 _vertexCount = vertices.length >> 2; indices = slot._indices; // 顶点索引数据来源 _indexCount = indices.length; offsetInfo = _buffer.request(_vertexCount, _indexCount); // 申请缓冲区 _indexOffset = offsetInfo.indiceOffset; // 申请的缓冲区偏移 _vfOffset = offsetInfo.byteOffset >> 2; _vertexOffset = offsetInfo.vertexOffset; // 申请的缓冲区偏移 vbuf = _buffer._vData; ibuf = _buffer._iData; uintbuf = _buffer._uintVData; _m00 = slotMatm[0]; _m04 = slotMatm[4]; _m12 = slotMatm[12]; _m01 = slotMatm[1]; _m05 = slotMatm[5]; _m13 = slotMatm[13]; // 填充顶点缓冲区 for (let vi = 0, vl = vertices.length; vi < vl;) { // 运行时动态计算bones的xy、uv _x = vertices[vi++]; _y = vertices[vi++]; vbuf[_vfOffset++] = _x * _m00 + _y * _m04 + _m12; // x vbuf[_vfOffset++] = _x * _m01 + _y * _m05 + _m13; // y vbuf[_vfOffset++] = vertices[vi++]; // u vbuf[_vfOffset++] = vertices[vi++]; // v uintbuf[_vfOffset++] = _c; // color } // 填充顶点索引缓冲区 for (let ii = 0, il = indices.length; ii < il; ii ++) { ibuf[_indexOffset++] = _vertexOffset + indices[ii]; } } } }
CCFactory
js 复制代码 var BaseFactory = dragonBones.BaseFactory; var CCFactory = dragonBones.CCFactory = cc.Class({ // 继承自dragonBones.Factory,所以它有db_lib.buildArmature等函数 extends: BaseFactory, ctor () { let eventManager = new dragonBones.CCArmatureDisplay(); this._dragonBones = new dragonBones.DragonBones(eventManager); // 这个就是DragonBones官方的lib库 }, update (dt) { this._dragonBones.advanceTime(dt); // 让时间往前走,驱动龙骨的更新 }, buildArmatureDisplay (armatureName, dragonBonesName, skinName, textureAtlasName) { // 通过缓存的 DragonBonesData 实例和 TextureAtlasData 实例创建一个骨架。 // buildArmature也会回调到CCFactory的_buildArmature // buildArmature内部也会处理slot let armature = this.buildArmature( armatureName, dragonBonesName, // 这个就是_armatureKey skinName, textureAtlasName ); // _display其实就是CCArmatureDisplay,类似显示代理,从命名上也能看出来 return armature && armature._display; }, _buildSlot (dataPackage, slotData, displays) { let slot = BaseObject.borrowObject(dragonBones.CCSlot); let display = slot; slot.init(slotData, displays, display, display); return slot; }, _buildArmature (dataPackage) { // db_lib的类型 let armature = BaseObject.borrowObject(dragonBones.Armature); armature._skinData = dataPackage.skin; armature._animation = BaseObject.borrowObject(dragonBones.Animation); armature._animation._armature = armature; armature._animation.animations = dataPackage.armature.animations; armature._isChildArmature = false; // fixed dragonbones sort issue // armature._sortSlots = this._sortSlots; var display = new dragonBones.CCArmatureDisplay(); // display真正的来源 // 会设置display,同时会设置display.dbInit,也就是CCArmatureDisplay.dbInit armature.init(dataPackage.armature, display, display, this._dragonBones ); return armature; }, }
js 复制代码 dragonBones.CCArmatureDisplay = cc.Class({ name: 'dragonBones.CCArmatureDisplay', // db api // armature: 龙骨的类型Armature dbInit (armature) { this._armature = armature; }, }
CCSlot
ini 复制代码 vertices = slot._localVertices; // 顶点数据来源 indices = slot._indices; // 顶点索引数据来源 都是初始化进行了填充,indices是固定的[0, 1, 2, 1, 3, 2]
js 复制代码 _updateFrame () { // 这里面进行了初始化 }
换装
大致的关系如下图:
js 复制代码 var armatureDisplay = this.node.getComponent(dragonBones.ArmatureDisplay); const factory = dragonBones.CCFactory.getInstance(); const armatureKey = armatureDisplay.getArmatureKey(); const slot = armatureDisplay.armature().getSlot("2"); const b = factory.replaceSlotDisplay( armatureKey, // 实例的缓存名称 armatureDisplay.armatureName, // 骨架数据名称 "1", // 插槽数据名称 "1", // 显示对象数据名称 slot // 要把这个插槽的内容替换为 1,1 );
replaceSlotDisplay
的注释
js 复制代码 /** * - 用特定的显示对象数据替换特定插槽当前的显示对象数据。 * 用 "dragonBonesName/armatureName/slotName/displayName" 指定显示对象数据。 * @param dragonBonesName - DragonBonesData 实例的缓存名称。 * @param armatureName - 骨架数据名称。 * @param slotName - 插槽数据名称。 * @param displayName - 显示对象数据名称。 * @param slot - 插槽。 * @param displayIndex - 被替换的显示对象数据的索引。 (如果未设置,则替换当前的显示对象数据) * @example * <pre> * let slot = armature.getSlot("weapon"); * factory.replaceSlotDisplay("dragonBonesName", "armatureName", "slotName", "displayName", slot); * </pre> * @version DragonBones 4.5 * @language zh_CN */ BaseFactory.prototype.replaceSlotDisplay = function (dragonBonesName, armatureName, slotName, displayName, slot, displayIndex) {
总结
DragonBones组件会一次性将龙骨全部顶点数据提交到buffer,所以导致无法穿插节点,因为cocos是按照节点顺序进行渲染的。
扩展
worldMatDirty
js 复制代码 _proto._children = function (node) { let cullingMask = _cullingMask; let batcher = _batcher; let parentOpacity = batcher.parentOpacity; let opacity = (batcher.parentOpacity *= (node._opacity / 255)); let worldTransformFlag = batcher.worldMatDirty ? WORLD_TRANSFORM : 0; // 这里 let worldOpacityFlag = batcher.parentOpacityDirty ? OPACITY_COLOR : 0; let worldDirtyFlag = worldTransformFlag | worldOpacityFlag; let children = node._children; for (let i = 0, l = children.length; i < l; i++) { let c = children[i]; // Advance the modification of the flag to avoid node attribute modification is invalid when opacity === 0. c._renderFlag |= worldDirtyFlag; if (!c._activeInHierarchy || c._opacity === 0) continue; _cullingMask = c._cullingMask = c.groupIndex === 0 ? cullingMask : 1 << c.groupIndex; // TODO: Maybe has better way to implement cascade opacity let colorVal = c._color._val; c._color._fastSetA(c._opacity * opacity); flows[c._renderFlag]._func(c); c._color._val = colorVal; } batcher.parentOpacity = parentOpacity; this._next._func(node); };
_armatureKey