重视点:
- 定制引擎
- 龙骨烘托流程
- 合批问题
- 龙骨格局
- 怎么提交烘托
unity
运转替换插槽的测试例
能够看到切换插槽是能够参加烘托排序的,unity在这块确实做的十分完善,包括9.ReplaceSkin
也是经常用的功用。
应该是dragonBones官方对unity的支持十分到位,究竟这个库房是DragonBones官方保护的。
cocos
挂点
先查挂点数据是怎么同步的,挂点都是在子节点上
查renderFlow意义不大
从Assembler入手,主体逻辑
let ArmatureDisplay = cc.Class({
name: 'dragonBones.ArmatureDisplay',
extends: RenderComponent,
})
- extensionsdragonboneswebgl-assembler.js 绑定Assembler
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
函数,是初始化逻辑的一部分
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里边了,直接倒推逻辑即可
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
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;
},
}
dragonBones.CCArmatureDisplay = cc.Class({
name: 'dragonBones.CCArmatureDisplay',
// db api
// armature: 龙骨的类型Armature
dbInit (armature) {
this._armature = armature;
},
}
CCSlot
vertices = slot._localVertices; // 极点数据来历
indices = slot._indices; // 极点索引数据来历
都是初始化进行了填充,indices是固定的[0,1,2,1,3,2]
_updateFrame () {
// 这儿边进行了初始化
}
换装
大致的联系如下图:
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
的注释
/**
* - 用特定的显现目标数据替换特定插槽当前的显现目标数据。
* 用 "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
_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