关注点:
- 定制引擎
- 龙骨渲染流程
- 合批问题
- 龙骨格式
- 如何提交渲染
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);
js
_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
