cocos creator DragonBones 源码阅读

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: cocos creator DragonBones 源码阅读

unity

github.com/DragonBones…

image.png

运行替换插槽的测试例

image.png

可以看到切换插槽是可以参与渲染排序的,unity在这块的确做的非常完善,包括9.ReplaceSkin也是经常用的功能。

应该是dragonBones官方对unity的支持非常到位,毕竟这个仓库是DragonBones官方维护的。

cocos

挂点

先查挂点数据是如何同步的,挂点都是在子节点上

image.png

查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函数,是初始化逻辑的一部分

image.png

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 () {
    // 这里面进行了初始化
}

换装

大致的关系如下图:

image.png

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

image.png

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
6月前
|
XML 存储 JSON
CocosCreator 面试题(十五)Cocos Creator如何内置protobuf JS版本?
CocosCreator 面试题(十五)Cocos Creator如何内置protobuf JS版本?
177 0
|
JavaScript
[√]cocos creator 热更新源码剖析(3)
[√]cocos creator 热更新源码剖析
179 1
|
JavaScript C++
[√]cocos creator 热更新源码剖析(2)
[√]cocos creator 热更新源码剖析(2)
97 1
|
JavaScript C++
[√]cocos creator 热更新源码剖析(1)
[√]cocos creator 热更新源码剖析
155 1
|
6月前
|
编解码 前端开发 UED
CocosCreator 面试题(十一)Cocos Creator 屏幕适配
CocosCreator 面试题(十一)Cocos Creator 屏幕适配
264 0
|
6月前
|
计算机视觉
CocosCreator 面试题(十九) Cocos Creator 材质 shader 分别是什么?
CocosCreator 面试题(十九) Cocos Creator 材质 shader 分别是什么?
240 0
|
6月前
|
JavaScript 前端开发 Java
CocosCreator 面试题(十)Cocos Creator 内存管理
CocosCreator 面试题(十)Cocos Creator 内存管理
390 0
Cocos Creator3.8 项目实战(二)cocos creator编辑器中绑定事件引发的bug解决
Cocos Creator3.8 项目实战(二)cocos creator编辑器中绑定事件引发的bug解决
136 0
Cocos Creator3.8 项目实战(一)cocos creator prefab 无法显示内容解决
Cocos Creator3.8 项目实战(一)cocos creator prefab 无法显示内容解决
189 0
|
缓存 中间件 atlas
Cocos Creator3.8 项目实战(八)2D UI DrawCall优化详解(上)
Cocos Creator3.8 项目实战(八)2D UI DrawCall优化详解(上)
434 0