1、需求情况
书之国中需要一个人物捏脸系统,要求可以让用户自由选择身体不同部位的形象,比如头发、眼睛、眉毛、上衣、裤子等。已经支持RegionAttacment和MeshAttachment,而且在realtime模式下也可以正常工作。cocoscreator v2.4.4版本验证通过。推荐使用外部图片进行局部换装.
2、方案探索
2.1 多attachment切换
由于spine动画的结构为bone→slot→attachment(即附件、图片),我们可以在动画文件中针对同一个部位(同一个slot)下做多个attachment,然后根据用户的选择进行切换attachment就行。
优点:web、native等多端统一代码。
缺点:随着可换装的部位越多、同一个部位皮肤越多,动画文件变得越来越大,由于spine动画文件是一次性加载进内存等,导致占用内存较多,实例化速度变慢。
主要代码如下:
// 局部换装 skinname一般默认为default
changePartialCloth(skeleton: sp.Skeleton, slotName: string, targetSkinName:string, targetAttaName:string) {
// console.log('change cloth:', slotName, targetSkinName, targetAttaName);
const slot = skeleton.findSlot(slotName);
const skeletonData = skeleton.skeletonData.getRuntimeData();
const skin = skeletonData.findSkin(targetSkinName);
const slotIndex = skeletonData.findSlotIndex(slotName);
const attachment = skin.getAttachment(slotIndex, targetAttaName);
if (!slot || !attachment) {
cc.error(slot && attachment, "slots: " + slotName + ", attach: " + targetAttaName + " not exists!");
return;
}
slot.setAttachment(attachment);
// 如果spine使用了private或者shared等缓存模式,则需要更新缓存。
skeleton.invalidAnimationCache();
}
2.2 使用外部图片更新局部皮肤
由于attachemnt即是图片资源在spine内的表达,我们可以通过加载一张外部图片来更新attachment达到局部换装功能。
优点:spine动画每个部位可以只做一个attachment,这样动画文件结构简单,体积较小,内存占用较小加载速度也较快。
缺点:一是由于引擎本身不提供此功能,需要自己动手实现,而且web端和native端需要两套代码,必须修改引擎代码并重新编译引擎。二是动画在使用realtime模式时修改一个动画会影响使用同一个动画文件创建的其他动画,不过这个可以通过复制一份skeletonData来解决。如果皮肤套件特别多,这种方式不失为最佳方案。
2.2.1 web端代码:
// 深拷贝一份skeletonData防止realtime下换装影响别的动画
copySkeletonData(skeleton: sp.Skeleton) {
// 复制一份skeletonData,换装时防止realtime模式下影响到别的动画
let spData: sp.SkeletonData = skeleton.skeletonData;
let copy = new sp.SkeletonData();
cc.js.mixin(copy, spData);
copy._uuid = spData._uuid + '_' + Date.now() + '_copy';
let old = copy.name;
let newName = copy.name + '_copy';
copy.name = newName;
copy.atlasText = copy.atlasText.replace(old, newName);
copy.textureNames[0] = newName + '.png';
copy.init && copy.init();
skeleton.skeletonData = copy;
}
// copySkeletonData(skeleton: sp.Skeleton) {
// // 复制一份skeletonData,换装时防止realtime模式下影响到别的动画
// let animation = skeleton.animation;
// let spData: sp.SkeletonData = skeleton.skeletonData;
// let copy = new sp.SkeletonData();
// cc.js.mixin(copy, spData);
// // @ts-ignore
// copy._uuid = Tool.uuid();
// let textureNames = copy['textureNames'];
// let copyTextureNames = [];
// for (let i = 0; i < textureNames.length; i++) {
// copy.atlasText = copy.atlasText.replace(textureNames[i], 'copy_' + textureNames[i]);
// copyTextureNames.push('copy_' + textureNames[i]);
// }
// copy['textureNames'] = copyTextureNames;
// // @ts-ignore
// copy.init && copy.init();
// skeleton.skeletonData = copy;
// skeleton.animation = animation;
// return copy;
// }
// 使用外部图片换装
changePartialWithExternalTexture(ani: sp.Skeleton, slotName: string, tex2d: cc.Texture2D) {
let slot: sp.spine.Slot = ani.findSlot(slotName);
let attach: sp.spine.RegionAttachment | sp.spine.MeshAttachment = slot.getAttachment() as (sp.spine.RegionAttachment | sp.spine.MeshAttachment);
let spineTexture: sp.SkeletonTexture = new sp.SkeletonTexture({
width: tex2d.width, height: tex2d.height });
spineTexture.setRealTexture(tex2d);
// 单张图片可以不用创建page
// let page = new sp.spine.TextureAtlasPage();
// page.