此前在社区“白嫖”了不少大佬的干货,一直心怀感激。今天我也来贡献一份自己的经验,和大家分享一个全新的 Spine 局部换装思路。
我的做法是采用类似动态合图的方式来处理 Spine 的图集。对于需要替换的部位,我直接用小图覆盖到图集的对应位置。而且,整个过程不会对原始的 Spine 数据造成任何污染,保证了数据的纯净性。
话不多说,直接上代码,还请各位大佬品鉴:
testChangleSpine(spinePath: string, attName: string, texture: cc.Texture2D) {
cc.resources.load(spinePath, sp.SkeletonData, (err, sk) => {
if (err) {
console.error('Failed to load costume data:', err);
return;
}
let newSk = this.spineChangePart(sk, attName, texture); // 局部换装
let node = new cc.Node();
let spine = node.addComponent(sp.Skeleton);
spine.premultipliedAlpha = false;
spine.skeletonData = newSk;
this.node.addChild(node); // 将新节点添加到当前节点下
spine.setAnimation(0, 'run', true); // 播放动画
});
}
/**
* 局部换装
* @param sk 原始骨骼数据
* @param attName 附件名称
* @param texture 替换的小图片
*/
spineChangePart(sk: sp.SkeletonData, attName: string, texture: cc.Texture2D) {
let date = new Date();
let newSk = new sp.SkeletonData(); // 创建新的骨骼数据,不污染原始数据方式。
// sk._getAtlas(); ( 原生直接报错,无法使用,求大佬补全)
// let region = sk._atlasCache.findRegion(attName); // 查找附件的 region
// @ts-ignore
newSk._uuid = sk._uuid + "_" + date.getTime() + "_copy"
newSk.skeletonJson = sk.skeletonJson;
// 处理二进制数据
// @ts-ignore
if (sk._nativeAsset) {
// @ts-ignore
newSk._nativeAsset = sk._nativeAsset;
// @ts-ignore
newSk._nativeUrl = sk.nativeUrl;
}
newSk.atlasText = sk.atlasText;
// @ts-ignore
newSk.textureNames = sk.textureNames; // 替换纹理名称
let orginTex: cc.Texture2D = sk.textures[0]; // 获取原始纹理
let newTex = new cc.RenderTexture(); // 创建新纹理
newTex.initWithSize(orginTex.width, orginTex.height); // 初始化新纹理
//@ts-ignore
newTex.drawTextureAt(orginTex, 0, 0); // 将原始纹理复制到新纹理上
let regionPos = this.getRegionXY(attName, sk.atlasText); // 解析atlas字符串获取附件的坐标
// @ts-ignore
newTex.drawTextureAt(texture, regionPos.x, regionPos.y); // 将小图纹理绘制到指定位置
newSk.textures[0] = newTex; // 设置纹理
return newSk;
}
/**
* 解析附件的坐标数据 sk._getAtlas() 无法使用,所以只能自己解析
* @param attName
* @param atlasText
* @returns
*/
getRegionXY(attName: string, atlasText: string) {
const lines = atlasText.split('\n');
let foundStatus = false;
let pos = cc.v2();
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line === attName) {
foundStatus = true;
} else if (foundStatus && line.startsWith("xy:")) {
const xyValues = line.split(":")[1].trim();
let p = xyValues.split(",");
pos.x = parseFloat(p[0]);
pos.y = parseFloat(p[1]);
break;
}
}
return pos;
}
在不再使用 Spine 动画节点时,应该手动释放相关资源,避免内存泄漏
// 移除节点
node.destroy();
// 释放资源
cc.assetManager.releaseAsset(newSk);
cc.assetManager.releaseAsset(newSk.textures[0]);
希望这段代码能给大家带来一些启发,如果有任何问题或者建议,欢迎各位大佬在评论区留言交流!


