1 多attachment切换

 let goblingirl = this.spine2.findSlot("left-arm");let attachment = goblingirl.getAttachment();let gun = this.spine.findSlot('gun');gun.setAttachment(attachment);

优点:web、native等多端统一代码。
缺点:随着可换装的部位越多、同一个部位皮肤越多,动画文件变得越来越大,由于spine动画文件是一次性加载进内存等,导致占用内存较多,实例化速度变慢。

2 使用外部图片更新局部皮肤

由于attachemnt即是图片资源在spine内的表达,我们可以通过加载一张外部图片来更新attachment达到局部换装功能。
优点:spine动画每个部位可以只做一个attachment,这样动画文件结构简单,体积较小,内存占用较小加载速度也较快。
缺点:一是由于引擎本身不提供此功能,需要自己动手实现,而且web端和native端需要两套代码,必须修改引擎代码并重新编译引擎。二是动画在使用realtime模式时修改一个动画会影响使用同一个动画文件创建的其他动画,这个问题还需要研究。

2.1 web端代码:

updatePartialSkin(ani: sp.Skeleton, tex2d: cc.Texture2D, slotsName: string) {let slot: sp.spine.Slot = ani.findSlot(slotsName);let attachment: sp.spine.RegionAttachment = slot.getAttachment() as sp.spine.RegionAttachment;if (!slot || !attachment) {cc.error('error...');return;}let region: sp.spine.TextureAtlasRegion = attachment.region as sp.spine.TextureAtlasRegion;let skeletonTexture = new sp.SkeletonTexture();skeletonTexture.setRealTexture(this.tex2d);region.u = 0;region.v = 0;region.u2 = 1;region.v2 = 1;region.width = tex2d.width;region.height = tex2d.height;region.originalWidth = tex2d.width;region.originalHeight = tex2d.height;region.rotate = false;region.texture = skeletonTexture;region.page = null;attachment.width = region.width;attachment.height = region.height;attachment.setRegion(region);// mark: 不需要创建新的sp.spine.TextureAtlasRegion, 直接更新原attachment下的region即可。// let region: sp.spine.TextureRegion = this.createRegion(tex2d);// attachment.setRegion(region);// attachment.width = region.width;// attachment.height = region.height;attachment.updateOffset();slot.setAttachment(attachment);// skeleton如果使用了缓存模式则需要刷新缓存ani.invalidAnimationCache();
}createRegion(tex: cc.Texture2D): sp.spine.TextureAtlasRegion {cc.log('创建region');let skeletonTexture = new sp.SkeletonTexture();skeletonTexture.setRealTexture(tex);// mark: 可以不设置page// let page = new sp.spine.TextureAtlasPage();// page.name = tex.name;// page.uWrap = sp.spine.TextureWrap.ClampToEdge;// page.vWrap = sp.spine.TextureWrap.ClampToEdge;// page.texture = skeletonTexture;// page.texture.setWraps(page.uWrap, page.vWrap);// page.width = tex.width;// page.height = tex.height;let region = new sp.spine.TextureAtlasRegion();// region.page = page;region.width = tex.width;region.height = tex.height;region.originalWidth = tex.width;region.originalHeight = tex.height;region.rotate = false;region.u = 0;region.v = 0;region.u2 = 1;region.v2 = 1;region.texture = skeletonTexture;return region;
}

2.2 native端代码:

native端我们需要分别修改C++实现和jsb-adapter, C++实现我们要分别在 SkeletonRenderer.cpp和SkeletonCacheAnimation.cpp 添加对应的方法。C++代码在cocos2d-x目录下,我们可以git上下载对应版本的最新代码。

记得头文件要加声明

SkeletonRenderer.cpp

void SkeletonRenderer::updateRegion(const std::string &slotName, cocos2d::middleware::Texture2D *texture) {// auto skeletonData = _skeleton->getData();// auto slotIndex = skeletonData->findSlotIndex(String(slotName.c_str()));// auto skin = skeletonData->findSkin(String("default"));// RegionAttachment * attachment = (RegionAttachment *)skin->getAttachment(slotIndex, String("cap_1"));Slot *slot = _skeleton->findSlot(slotName.c_str());RegionAttachment *attachment = (RegionAttachment *)slot->getAttachment();// Texture *texture2D = texture->getNativeTexture();// float width = texture2D->getWidth();// float height = texture2D->getHeight();float wide = texture->getPixelsWide();float high = texture->getPixelsHigh();attachment->setUVs(0, 0, 1, 1, false);attachment->setRegionWidth(wide);attachment->setRegionHeight(high);attachment->setRegionOriginalWidth(wide);attachment->setRegionOriginalHeight(high);attachment->setWidth(wide);attachment->setHeight(high);// attachment->setRegionOffsetX(0);// attachment->setRegionOffsetY(15);// texture->setPixelsWide(width);// texture->setPixelsHigh(height);// texture->setRealTextureIndex(1);AttachmentVertices *attachV = (AttachmentVertices *)attachment->getRendererObject();if (attachV->_texture == texture) {return;}CC_SAFE_RELEASE(attachV->_texture);attachV->_texture = texture;CC_SAFE_RETAIN(texture);V2F_T2F_C4B *vertices = attachV->_triangles->verts;for (int i = 0, ii = 0; i < 4; ++i, ii += 2){vertices[i].texCoord.u = attachment->getUVs()[ii];vertices[i].texCoord.v = attachment->getUVs()[ii + 1];}attachment->updateOffset();slot->setAttachment(attachment);
}

SkeletonCacheAnimation.cpp

void SkeletonCacheAnimation::updateRegion(const std::string &slotName, cocos2d::middleware::Texture2D *texture)
{_skeletonCache->updateRegion(slotName, texture);
}

修改C++代码后我们需要重新跑一般自动绑定脚本,生成js绑定接口,目录在cocos2dx/tools/tojs/genbindings.py,绑定成功后我们需要修改jsb adapter以提供给js层调用,
adapter在引擎安装目录下/Resources/builtin/jsb-adapter/engine/jsb-spine-skeleton.js ,添加如下方法:

skeleton.updateRegion = function (slotsName, jsbTex2d) {if (this._nativeSkeleton) {this._nativeSkeleton.updateRegion(slotsName, jsbTex2d);return true;}return false;
};

全部修改完成后我们需要在creator引擎中自定义cocos2d-x引擎,指向我们刚修改的cocos2d-x目录。如果想要在模拟器预览效果我们还需要重新编译模拟器

2.3 使用方法:

以上全部修改完成后,我们可以在js/ts代码中这样使用了:

changeClouth() {if (cc.sys.isNative) {cc.log('native 换肤.');let jsbTex = new middleware.Texture2D();jsbTex.setPixelsHigh(this.tex2d.height);jsbTex.setPixelsWide(this.tex2d.width);jsbTex.setNativeTexture(this.tex2d.getImpl());this.player.updateRegion("cap", jsbTex);} else {cc.log('web 换肤.');this.updatePartialSkin(this.player, this.tex2d, 'cap');}// 缓存模式下需要刷新缓存this.player.invalidAnimationCache();
}

认真测试一下,其实可以发现如果共用一个sp.SkeletonData,其中一个实例换装了,其他实例也换装了。

解决办法

给 spine 重新拷贝一份 skeletonData 数据,让他们不重复,

        let date = new Date();var spdata = this.spine.skeletonData;//spineComp某个sp.Skeleton组件var copy = new sp.SkeletonData()//拷贝一份纹理,避免重复纹理缓存cc.js.mixin(copy, spdata)copy._uuid = spdata._uuid + "_" + date.getTime() + "_copy";//增加一个时间戳 读取到毫秒应该不会重复吧?var old = copy.name;var newName = copy.name + '_copy'copy.name = newName;copy.atlasText = copy.atlasText.replace(old, newName)copy.textureNames[0] = newName + '.png'copy.init && copy.init()this.spine.skeletonData = copy;//重新设置一下数据

2.4 使用Spine挂点功能

Spine挂点功能是cocoscreator 2.3版本开始提供的,初衷是为了动态给动画添加部分节点,比如武器等,这里也可以非常规使用来做局部换皮。具体流程为生成挂点——>获取指定节点——>给该节点添加对应的子节点。

优点: 引擎提供的功能,三端表现统一,无需hack源码

缺点: 如果需要换装的图集过多无法合并到一张图集上,则每增加一个挂载节点都会增加一个drawcall,这里要特别注意。

             // this.ani: sp.Skeletonlet node = new cc.Node();let sp = node.addComponent(cc.Sprite);sp.spriteFrame = this.spf;let attachUtil = this.ani.attachUtil;// attachUtil.generateAttachedNodes("hair");attachUtil.generateAllAttachedNodes();let bones = attachUtil.getAttachedNodes('hair');bones[0].destroyAllChildren();bones[0].addChild(node);bones = attachUtil.getAttachedNodes('left_hand_a');let node2 = cc.instantiate(node);bones[0].destroyAllChildren();bones[0].addChild(node2);bones = attachUtil.getAttachedNodes('right_hand_a');let node3 = cc.instantiate(node);bones[0].destroyAllChildren();bones[0].addChild(node3);// attachUtil.destroyAttachedNodes('hair');// attachUtil.destroyAllAttachedNodes();

主要用到的接口在AttachUtil.js里都可以找到

/*** !#en Traverse all bones to generate the minimum node tree containing the given bone names, NOTE that make sure the skeleton has initialized before calling this interface.* !#zh 遍历所有插槽,生成包含所有给定插槽名称的最小节点树,注意,调用该接口前请确保骨骼动画已经初始化好。* @method generateAttachedNodes* @param {String} boneName* @return {Node[]} attached node array*/generateAttachedNodes (boneName) {let targetNodes = [];if (!this._inited) return targetNodes;let rootNode = this._prepareAttachNode();if (!rootNode) return targetNodes;let res = [];let bones = this._skeleton.bones;for (let i = 0, n = bones.length; i < n; i++) {let bone = bones[i];let boneData = bone.data;if (boneData.name == boneName) {res.push(bone);}}let buildBoneTree = function (bone) {if (!bone) return;let boneData = bone.data;let boneNode = this._getNodeByBoneIndex(boneData.index);if (boneNode) return boneNode;boneNode = this._buildBoneAttachedNode(bone, boneData.index);let parentBoneNode = buildBoneTree(bone.parent) || rootNode;boneNode.parent = parentBoneNode;return boneNode;}.bind(this);for (let i = 0, n = res.length; i < n; i++) {let targetNode = buildBoneTree(res[i]);targetNodes.push(targetNode);}this._sortNodeArray();return targetNodes;},/*** !#en Destroy attached node which you want.* !#zh 销毁对应的挂点* @method destroyAttachedNodes* @param {String} boneName*/destroyAttachedNodes (boneName) {if (!this._inited) return;let nodeArray = this._attachedNodeArray;let markTree = function (rootNode) {let children = rootNode.children;for (let i = 0, n = children.length; i < n; i++) {let c = children[i];if (c) markTree(c);}rootNode._toRemove = true;}for (let i = 0, n = nodeArray.length; i < n; i++) {let boneNode = nodeArray[i];if (!boneNode || !boneNode.isValid) continue;let delName = boneNode.name.split(ATTACHED_PRE_NAME)[1];if (delName === boneName) {markTree(boneNode);boneNode.removeFromParent(true);boneNode.destroy();nodeArray[i] = null;}}this._rebuildNodeArray();},/*** !#en Traverse all bones to generate a tree containing all bones nodes, NOTE that make sure the skeleton has initialized before calling this interface.* !#zh 遍历所有插槽,生成包含所有插槽的节点树,注意,调用该接口前请确保骨骼动画已经初始化好。* @method generateAllAttachedNodes* @return {cc.Node} root node*/generateAllAttachedNodes () {if (!this._inited) return;// clear all recordsthis._boneIndexToNode = {};this._attachedNodeArray.length = 0;let rootNode = this._prepareAttachNode();if (!rootNode) return;let bones = this._skeleton.bones;for (let i = 0, n = bones.length; i < n; i++) {let bone = bones[i];let boneData = bone.data;let parentNode = null;if (bone.parent) {let parentIndex = bone.parent.data.index;parentNode = this._boneIndexToNode[parentIndex];} else {parentNode = rootNode;}if (parentNode) {let boneNode = parentNode.getChildByName(ATTACHED_PRE_NAME + boneData.name);if (!boneNode || !boneNode.isValid) {boneNode = this._buildBoneAttachedNode(bone, boneData.index);parentNode.addChild(boneNode);} else {this._buildBoneRelation(boneNode, bone, boneData.index);}}}return rootNode;},/*** !#en Destroy all attached node.* !#zh 销毁所有挂点* @method destroyAllAttachedNodes*/destroyAllAttachedNodes () {this._attachedRootNode = null;this._attachedNodeArray.length = 0;this._boneIndexToNode = {};if (!this._inited) return;let rootNode = this._skeletonNode.getChildByName(ATTACHED_ROOT_NAME);if (rootNode) {rootNode.removeFromParent(true);rootNode.destroy();rootNode = null;}}

2.5 隐藏骨骼最简单的办法

直接改变透明度

 let slot: sp.spine.Slot = this.skeleton.findSlot(soltName);let attachment: sp.spine.RegionAttachment = slot.getAttachment() as sp.spine.RegionAttachment;if (!slot || !attachment) {continue;}let color = slot.color;color.a = 0;

3. 自定义引擎遇到的问题

3.1

F:\cocos\cocos2d-xs-erlf\tools\tojs\genbindings.py   运行问题   参考README.mdown 文档

On Windows:

  • Make sure that you have installed android-ndk-r16 or later.

  • Download python2.7.3 (32bit) from (http://www.python.org/ftp/python/2.7.3/python-2.7.3.msi).

  • Add the installed path of python (e.g. C:\Python27) to windows environment variable named 'PATH'.

  • Download pyyaml from http://pyyaml.org/download/pyyaml/PyYAML-3.11.win32-py2.7.exe and install it.

  • Download Cheetah-2.4.4.tar.gz, extract and install it by python setup.py. ( python setup.py install)

  • Set environment variables (NDK_ROOT) and PYTHON_BIN

  • Go to "cocos2d-x/tools/tojs" folder, and run "genbindings.py". The generated codes will be under "cocos\scripting\auto-generated\js-bindings".

3.2

用  node-v10.20.1-x64

3.3

C:\Users\Administrator\AppData\Roaming\npm    加入Path 环境变量

3.4

编译模拟器 问题

gulp gen-simulator
gulp update-simulator-config

编译不过

F:\ccc\CocosDashboard_1.0.8\resources\.editors\Creator\2.4.3\resources\cocos2d-xself\tools\simulator\frameworks\runtime-src\proj.win32\simulator.sln

打开 vs 工程(目前需要 vs2017) , 修改windowsSdk 为自己的版本 

修改完后,记得清理下项目工程,避免其它报错,然后再进行上面两句话的编译

cocoscreator 中 spine局部换皮相关推荐

  1. cocoscreator中spine局部换皮的探索

    1.需求情况 书之国中需要一个人物捏脸系统,要求可以让用户自由选择身体不同部位的形象,比如头发.眼睛.眉毛.上衣.裤子等.已经支持RegionAttacment和MeshAttachment,而且在r ...

  2. Unity Spine 局部换皮

    1.Spine元素主要包含皮肤(Skin).骨骼(Bone).插槽(Slot).附件(Attachment).及附件下的图片. 2.而皮肤(Skin)包含了插槽信息.附件信息,如果我们有两套相同构成的 ...

  3. spine 动态换皮功能

    前言: cocos2dx 中spine 的换皮功能是一种静态的方法,也就是在创建 spine 动画的时候就必须将所有的皮肤全部加载,然后在代码中直接换皮,并且这种换皮是整体的切换,对于我们实际开发中这 ...

  4. 梦幻西游人物局部换色补完

    梦幻西游人物局部换色补完 作者:leexuany(小宝) 小宝上次写文章简单介绍了梦幻西游中人物局部换色使用的方法,但由于没有具体的调色板变换算法,到头来也只是纸上谈兵.所以小宝花了几天时间跟踪梦幻的 ...

  5. Spine动画局部换装(切换武器)

    最近在用spine动画做微信小游戏,涉及到角色的武器升级后会切换到另一形态,就研究了一下spine的换装,搜了一些资料发现专门介绍的资料有点少,而且spine官网上说的也不太清楚,好在最后搞出来了,记 ...

  6. laya龙骨换装_分享:Dragonbones/Spine的换肤操作

    目前LayaAir下支持龙骨的局部换肤(根据插槽索引换肤.根据插槽name换肤.纹理换肤.网格换肤).全局换肤 需注意: 1.Dragonbones不支持全局换肤,Spine支持全局换肤 2.使用到I ...

  7. 【CocosCreator入门】CocosCreator组件 | Spine(骨骼动画)组件

            Cocos Creator 是一款流行的游戏开发引擎,具有丰富的组件和工具,其中Spine 是一个基于骨骼系统的 2D 动画工具,它可以让开发者通过对骨架和关键帧的调整来制作出更加自然 ...

  8. Egret之龙骨卡槽(slot)换皮

    龙骨的图片是绑定在卡槽上的.并且是一对一的关系.所以可以通过对骨架的卡槽上绑定的图片的更换来实现另一种换皮的效果. 换皮的核心代码: //针对slot设置其新内容private setNewSlot( ...

  9. 木兰编程语言python_国产编程语言木兰换皮Python 中科院重罚当事人

    最近打着中科院计算所出品.完全自主开发旗号的国产编程语言"木兰"引发广泛关注,但很快被发现是基于Python语言套壳.换皮而来的产物.面对质疑,中科院计算所编译实验室员工.&quo ...

最新文章

  1. Django-View中绕过RSCF验证
  2. linux合并vi的两个文件,两个文件的对比查看及合并工具:vimdiff-文件合并
  3. visual studio无法更新_微软发布 VS Code Python 四月更新
  4. 如何定制化SAP Spartacus的页面路由Route
  5. sublime python配置运行
  6. thinkphp单入口和多入口的访问方法
  7. Java快速入门学习笔记8 | Java语言中的数组
  8. java在线支付---09,10,11,12_在线支付_分析易宝支付网关的应答协议与处理代码,完成用于处理支付响应的Servlet的初步编写和调试,完成处理支付网关响应结果的Servlet,支付实现
  9. struts配置json需要的jar包
  10. Java面试题超详细讲解系列之六【网络协议篇】
  11. 计算机无法卸载软件,电脑安装的软件无法卸载怎么办?
  12. uniapp解决h5打包空白的问题
  13. MyBatis 自定义插件
  14. 宁芝84静电容(蓝牙双模)键盘说明书
  15. 双系统笔记本 android,安卓不止平板有 教你使用笔记本装安卓,和WINDOWS一起双系统亦可以的哦!(图解)...
  16. 水印watermark
  17. java集合系列——java集合概述(一)
  18. 会议OA之我的会议(查询)
  19. html 5 触摸屏事件
  20. Windows快捷键操作

热门文章

  1. 100个Python实用程序-1.批量生成试卷文件
  2. 香港大学计算机硕士一定要面试吗,大干货!!!香港硕士有哪些专业需要笔试面试...
  3. java计算机毕业设计客服管理系统源码+mysql数据库+系统+lw文档+部署
  4. TikTok广告实操案例数据分析(一)
  5. 无需编程的BEAM昆虫积木机器人~适合小孩子的益智DIY小制作
  6. 隧道人员定位考勤软件详细介绍
  7. FIDO身份认证与个人信息保护法
  8. mysql驱动表优化_Mysql 驱动表查询优化
  9. itextpdf给pdf添加水印
  10. 思维决定命运,自律即为自由