前言
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.8
的JavaScript
库与3.6
的C
库比对后删去部分内容,即可使其支持读入.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一定能正常工作,若出现问题,可在浏览器中调试后找到不同的地方并改动
12
谢谢大佬分享,帮大忙了
追加一下。我测试的spine版本是3.6.58,还需要修改BinaryInput.prototype.readStringRef函数,将它改成BinaryInput.prototype.readString一样就可以
大佬,能不能问一下,pixi读取二进制文件时,还需要配置什么东西呀