让pixi-spine支持Spine 3.6的二进制文件读入

@Pelom  August 7, 2022

前言

PixiJS是一个超快的2D渲染库,其插件pixi-spine使其能操作Spine动画
Spine的配置文件描述了模型的骨骼、动作、皮肤等诸多信息,它有两种格式可供选择
一种是常见的.json格式,它是人类可读的,但具有较大的体积
另一种是二进制文件.skel格式,它具有较小的体积,但不是人类可读的;同时,.skel文件不具有自描述性,因此各个版本并不通用
由于Spine的WebGL运行库从3.8开始才支持二进制,也就是.skel文件的读取,pixi-spine也不支持3.6及以下版本的.skel文件(3.7版本格式与3.8相同,因此pixi-spine里将3.7视为3.8读入)

步骤

分析

从Github的官方仓库可以发现,3.6的其他语言的运行库有的是支持.skel文件读入的(比如spine-c运行库),这说明JavaScript库也是可以支持.skel文件读入的,只需要改动读入部分的代码

比对

负责处理二进制骨骼数据的类是SkeletonBinary,其中的函数负责从.skel文件中读取数据
3.8JavaScript库与3.6C库比对后删去部分内容,即可使其支持读入.skel

改动

以下改动基于npm@pixi-spine/all-3.8

SkeletonBinary.prototype.readSkeletonData

SkeletonBinary.prototype.readSkeletonData = function (binary) {
    var scale = this.scale;
    var skeletonData = new SkeletonData();
    skeletonData.name = ""; // BOZO
    var input = new base.BinaryInput(binary);
    skeletonData.hash = input.readString();
    skeletonData.version = input.readString();
    if (skeletonData.version === '3.8.75') {
        var error = "Unsupported skeleton data, 3.8.75 is deprecated, please export with a newer version of Spine.";
        console.error(error);
    }
    // skeletonData.x = input.readFloat();
    // skeletonData.y = input.readFloat();
    skeletonData.width = input.readFloat();
    skeletonData.height = input.readFloat();
    var nonessential = input.readBoolean();
    if (nonessential) {
        skeletonData.fps = input.readFloat();
        skeletonData.imagesPath = input.readString();
        // skeletonData.audioPath = input.readString();
    }
    var n = 0;
    // Strings.
    // n = input.readInt(true);
    // for (var i = 0; i < n; i++)
    //     input.strings.push(input.readString());
    // Bones.
    n = input.readInt(true);
    for (var i = 0; i < n; i++) {
        var name_1 = input.readString();
        var parent_1 = i == 0 ? null : skeletonData.bones[input.readInt(true)];
        var data = new BoneData(i, name_1, parent_1);
        data.rotation = input.readFloat();
        data.x = input.readFloat() * scale;
        data.y = input.readFloat() * scale;
        data.scaleX = input.readFloat();
        data.scaleY = input.readFloat();
        data.shearX = input.readFloat();
        data.shearY = input.readFloat();
        data.length = input.readFloat() * scale;
        data.transformMode = SkeletonBinary.TransformModeValues[input.readInt(true)];
        // data.skinRequired = input.readBoolean();
        if (nonessential)
            base.Color.rgba8888ToColor(data.color, input.readInt32());
        skeletonData.bones.push(data);
    }
    // Slots.
    n = input.readInt(true);
    for (var i = 0; i < n; i++) {
        var slotName = input.readString();
        var boneData = skeletonData.bones[input.readInt(true)];
        var data = new SlotData(i, slotName, boneData);
        base.Color.rgba8888ToColor(data.color, input.readInt32());
        var darkColor = input.readInt32();
        if (darkColor != -1)
            base.Color.rgb888ToColor(data.darkColor = new base.Color(), darkColor);
        data.attachmentName = input.readString();
        data.blendMode = SkeletonBinary.BlendModeValues[input.readInt(true)];
        skeletonData.slots.push(data);
    }
    // IK constraints.
    n = input.readInt(true);
    for (var i = 0, nn = void 0; i < n; i++) {
        var data = new IkConstraintData(input.readString());
        data.order = input.readInt(true);
        // data.skinRequired = input.readBoolean();
        nn = input.readInt(true);
        for (var ii = 0; ii < nn; ii++)
            data.bones.push(skeletonData.bones[input.readInt(true)]);
        data.target = skeletonData.bones[input.readInt(true)];
        data.mix = input.readFloat();
        // data.softness = input.readFloat() * scale;
        data.bendDirection = input.readByte();
        // data.compress = input.readBoolean();
        // data.stretch = input.readBoolean();
        // data.uniform = input.readBoolean();
        skeletonData.ikConstraints.push(data);
    }
    // Transform constraints.
    n = input.readInt(true);
    for (var i = 0, nn = void 0; i < n; i++) {
        var data = new TransformConstraintData(input.readString());
        data.order = input.readInt(true);
        // data.skinRequired = input.readBoolean();
        nn = input.readInt(true);
        for (var ii = 0; ii < nn; ii++)
            data.bones.push(skeletonData.bones[input.readInt(true)]);
        data.target = skeletonData.bones[input.readInt(true)];
        data.local = input.readBoolean();
        data.relative = input.readBoolean();
        data.offsetRotation = input.readFloat();
        data.offsetX = input.readFloat() * scale;
        data.offsetY = input.readFloat() * scale;
        data.offsetScaleX = input.readFloat();
        data.offsetScaleY = input.readFloat();
        data.offsetShearY = input.readFloat();
        data.rotateMix = input.readFloat();
        data.translateMix = input.readFloat();
        data.scaleMix = input.readFloat();
        data.shearMix = input.readFloat();
        skeletonData.transformConstraints.push(data);
    }
    // Path constraints.
    n = input.readInt(true);
    for (var i = 0, nn = void 0; i < n; i++) {
        var data = new PathConstraintData(input.readString());
        data.order = input.readInt(true);
        // data.skinRequired = input.readBoolean();
        nn = input.readInt(true);
        for (var ii = 0; ii < nn; ii++)
            data.bones.push(skeletonData.bones[input.readInt(true)]);
        data.target = skeletonData.slots[input.readInt(true)];
        data.positionMode = SkeletonBinary.PositionModeValues[input.readInt(true)];
        data.spacingMode = SkeletonBinary.SpacingModeValues[input.readInt(true)];
        data.rotateMode = SkeletonBinary.RotateModeValues[input.readInt(true)];
        data.offsetRotation = input.readFloat();
        data.position = input.readFloat();
        if (data.positionMode == base.PositionMode.Fixed)
            data.position *= scale;
        data.spacing = input.readFloat();
        if (data.spacingMode == exports.SpacingMode.Length || data.spacingMode == exports.SpacingMode.Fixed)
            data.spacing *= scale;
        data.rotateMix = input.readFloat();
        data.translateMix = input.readFloat();
        skeletonData.pathConstraints.push(data);
    }
    // Default skin.
    var defaultSkin = this.readSkin(input, skeletonData, true, nonessential);
    if (defaultSkin != null) {
        skeletonData.defaultSkin = defaultSkin;
        skeletonData.skins.push(defaultSkin);
    }
    // Skins.
    {
        var i = skeletonData.skins.length;
        base.Utils.setArraySize(skeletonData.skins, n = i + input.readInt(true));
        for (; i < n; i++)
            skeletonData.skins[i] = this.readSkin(input, skeletonData, false, nonessential);
    }
    // Linked meshes.
    n = this.linkedMeshes.length;
    for (var i = 0; i < n; i++) {
        var linkedMesh = this.linkedMeshes[i];
        var skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
        if (skin == null)
            throw new Error("Skin not found: " + linkedMesh.skin);
        var parent_2 = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
        if (parent_2 == null)
            throw new Error("Parent mesh not found: " + linkedMesh.parent);
        // linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform ? parent_2 : linkedMesh.mesh;
        linkedMesh.mesh.setParentMesh(parent_2);
        // linkedMesh.mesh.updateUVs();
    }
    this.linkedMeshes.length = 0;
    // Events.
    n = input.readInt(true);
    for (var i = 0; i < n; i++) {
        var data = new EventData(input.readString());
        data.intValue = input.readInt(false);
        data.floatValue = input.readFloat();
        data.stringValue = input.readString();
        // data.audioPath = input.readString();
        // if (data.audioPath != null) {
        //     data.volume = input.readFloat();
        //     data.balance = input.readFloat();
        // }
        skeletonData.events.push(data);
    }
    // Animations.
    n = input.readInt(true);
    for (var i = 0; i < n; i++)
        skeletonData.animations.push(this.readAnimation(input, input.readString(), skeletonData));
    return skeletonData;
};

SkeletonBinary.prototype.readSkin

SkeletonBinary.prototype.readSkin = function (input, skeletonData, defaultSkin, nonessential) {
    var skin = null;
    var slotCount = 0;
    // if (defaultSkin) {
        slotCount = input.readInt(true);
        if (slotCount == 0)
            return null;
        skin = new Skin("default");
    // }
    // else {
    //     skin = new Skin(input.readString());
    //     skin.bones.length = input.readInt(true);
    //     for (var i = 0, n = skin.bones.length; i < n; i++)
    //         skin.bones[i] = skeletonData.bones[input.readInt(true)];
    //     for (var i = 0, n = input.readInt(true); i < n; i++)
    //         skin.constraints.push(skeletonData.ikConstraints[input.readInt(true)]);
    //     for (var i = 0, n = input.readInt(true); i < n; i++)
    //         skin.constraints.push(skeletonData.transformConstraints[input.readInt(true)]);
    //     for (var i = 0, n = input.readInt(true); i < n; i++)
    //         skin.constraints.push(skeletonData.pathConstraints[input.readInt(true)]);
    //     slotCount = input.readInt(true);
    // }
    for (var i = 0; i < slotCount; i++) {
        var slotIndex = input.readInt(true);
        for (var ii = 0, nn = input.readInt(true); ii < nn; ii++) {
            var name_2 = input.readString();
            var attachment = this.readAttachment(input, skeletonData, skin, slotIndex, name_2, nonessential);
            if (attachment != null)
                skin.setAttachment(slotIndex, name_2, attachment);
        }
    }
    return skin;
};

IkConstraintTimeline.prototype.setFrame

IkConstraintTimeline.prototype.setFrame = function (frameIndex, time, mix/*, softness*/, bendDirection/*, compress, stretch*/) {
    frameIndex *= IkConstraintTimeline.ENTRIES;
    this.frames[frameIndex] = time;
    this.frames[frameIndex + IkConstraintTimeline.MIX] = mix;
    // this.frames[frameIndex + IkConstraintTimeline.SOFTNESS] = softness;
    this.frames[frameIndex + IkConstraintTimeline.BEND_DIRECTION] = bendDirection;
    // this.frames[frameIndex + IkConstraintTimeline.COMPRESS] = compress ? 1 : 0;
    // this.frames[frameIndex + IkConstraintTimeline.STRETCH] = stretch ? 1 : 0;
};

并修改调用该方法处的传参

注意

以上改动只解决了读入.skel文件的问题,并不能保证Spine一定能正常工作,若出现问题,可在浏览器中调试后找到不同的地方并改动

引用


添加新评论

  1. 12

    12

    Reply
  2. 晴初

    谢谢大佬分享,帮大忙了

    Reply
  3. 晴初

    追加一下。我测试的spine版本是3.6.58,还需要修改BinaryInput.prototype.readStringRef函数,将它改成BinaryInput.prototype.readString一样就可以

    Reply
  4. ly

    大佬,能不能问一下,pixi读取二进制文件时,还需要配置什么东西呀

    Reply