Spine是一款收费的且针对于游戏的2D的骨骼动画工具, 它支持Unity, Cocos2d等引擎, 支持语言有Lua, Js, ActionScript, C, C#等。
它会将角色的部位图片绑定到一个个互相作用的链接的骨骼上, 通过控制这些骨骼的位置,旋转和缩放等形成各种动画的显示。
同序列帧动画相比较有着如下的优势:
Spine骨骼动画的组成主要有:
Bone骨骼
:基本组成元素,骨骼之间存在父子关系, 父骨骼会带动其子骨骼。 每块骨骼都有它的名字和长度, 可以用于移动,旋转和缩放。
Slot插槽
:主要用于关联Attachment附件
, 每块Bone骨骼
可关联到多个插槽,而每个插槽会关联多个附件, 骨骼本身并没有实现显示功能,而是通过激活的附件进行显示。
Attachment附件
:主要用于显示,在同一时间只能激活一个附件。附件的类型有:
Skin皮肤
:主要用于通过新的附件来切换骨骼动画的表现。比如:男女皮肤的切换
Animation动画
:主要用于让骨骼动画的运动,它会包含一系列的时间轴(通道),比如:
一个骨骼动画对象可以拥有多个动画, 比如,走,跑,死亡等。它也支持同时播放多个动画,比如走动的过程中进行射击。
Spine骨骼动画工具导出的文件主要有json
和skel二进制
格式,它的主要导出文件有如下几种:
.png
文件:动画的各个部位的图片文件.atlas
文件:类似于图集的plist配置.json/.skel
文件:骨骼动画的配置文件// 以cocos2d-x CppTest为例,资源路径:../Resource/spine/spineboy.json
{
"bones": [
{ "name": "head", "parent": "neck", "length": 263.57, "x": 27.66, "y": -0.25, "rotation": 23.18, "color": "e0da19ff" },
],
"slots": [
{ "name": "gun", "bone": "gun", "attachment": "gun" },
],
"skins": {},
"events": {
"headBehind": { "int": 5, "float": 6, "string": "setup" },
},
"animations": {
"death": {},
"walk": {},
},
}
对于导出的这些文件都要放在同一个目录下,且spine允许不同的骨骼动画对象共用一个图集。
资料参考:
Spine官网
Spine官方文档
其他骨骼动画工具: DragonBones
在cocos2d-x中,骨骼动画主要的实现文件位于../editor-support/spine
中,它的主要类接口是:SkeletonAnimation
相关的tolua++接口可参考:
int lua_register_cocos2dx_spine_SkeletonRenderer(lua_State* tolua_S);
int lua_register_cocos2dx_spine_SkeletonAnimation(lua_State* tolua_S)
static void extendCCSkeletonAnimation(lua_State* L);
在骨骼动画的使用中,有个关键词需要说明下:track
它可以理解为通道,在skeletonAnimtion
中会拥有多条track
, 它指向一个叫做spTrackEntity
的容器,索引从0开始。每一条track可以同一时间内执行一个动画, 但一条track可以对应多个动画。
常用的接口:
//----------------------- SkeletonAnimation.h -----------------------
// 立即结束当前动画,开始另一个动画的播放
spTrackEntry* setAnimation (int trackIndex, const std::string& name, bool loop);
// 等当前动画结束单次循环结束后,开始另一个动画的播放
// 如果通过setAnimation设置A动画循环播放后,再通过addAnimation添加了B动画,
// 它就会在A单次循环播放结束后,开始B
spTrackEntry* addAnimation (int trackIndex, const std::string& name, bool loop, float delay = 0);
// 设置动画衔接,避免两个动画之间播放不连贯
void setMix (const std::string& fromAnimation, const std::string& toAnimation, float duration);
/*
回调相关,主要有两种:
1. 全局回调相关
2. 绑定SptrackEntry对象的回调相关
他们对应的事件都是:动画开始回调, 动画结束回调, 动画完成(一次循环), 动画事件回调相关
*/
void setStartListener (const StartListener& listener);
void setEndListener (const EndListener& listener);
void setCompleteListener (const CompleteListener& listener);
void setEventListener (const EventListener& listener);
void setTrackStartListener (spTrackEntry* entry, const StartListener& listener);
void setTrackEndListener (spTrackEntry* entry, const EndListener& listener);
void setTrackCompleteListener (spTrackEntry* entry, const CompleteListener& listener);
void setTrackEventListener (spTrackEntry* entry, const EventListener& listener);
//----------------------- SkeletonRenderer.h -----------------------
// 设置播放速度,默认为1.0 数值越大播放速度越快
void setTimeScale(float scale);
float getTimeScale() const;
// 更换皮肤相关,如果皮肤获取成功,则为true
bool setSkin (const std::string& skinName);
bool setSkin (const char* skinName);
// 更换组件相关, tolua++没有提供接口支持
bool setAttachment (const std::string& slotName, const std::string& attachmentName);
bool setAttachment (const std::string& slotName, const char* attachmentName);
// 设置插槽测试是否可见
void setDebugSlotsEnabled(bool enabled);
bool getDebugSlotsEnabled() const;
// 设置骨骼调试是否可见
void setDebugBonesEnabled(bool enabled);
bool getDebugBonesEnabled() const;
// 处理上个动画播放残影
void setToSetupPose ();
void setBonesToSetupPose ();
void setSlotsToSetupPose ();
// 设置颜色混合
// 可参考:https://forum.cocos.org/t/cocos2d-x-v3-3-blendprotocol-and-blendfunc/29618
virtual void setBlendFunc (const cocos2d::BlendFunc& blendFunc)override;
创建的主要接口是:SkeletonAnimation
-- 检测spine配置文件是否存在
local jsonPath = "res/spine/spineboy.json"
local isExsit = cc.FileUtils:getInstance():isFileExist(jsonPath)
if not isExsit then
print("Error createspine jsonFile is not exist")
return
end
-- 检测spine图集配置文件是否存在
local altasPath = "res/spine/spineboy.atlas"
local isExsit = cc.FileUtils:getInstance():isFileExist(altasPath)
if not isExsit then
print("Error createspine atlasFile is not exist")
return
end
--[[
@func: 创建骨骼动画
@param: 骨骼动画的配置文件
@param: 骨骼动画的图集配置文件
@param: 缩放相关
]]
local spine = sp.SkeletonAnimation:create(jsonPath, altasPath, 0.6)
if not spine then
return
end
spine:setPosition(display.center)
-- 设置缩放
spine:setScale(0.5)
-- 设置播放速度,数值越大,播放越快
spine:setTimeScale(1)
-- 设置当前播放动画,参数(通道索引,动画名,是否循环),fa
spine:setAnimation(0, "walk", true)
-- 设置衔接动画相关,参数(起始动画,结束动画,间隔秒数)
-- 主要用于避免不同动画之间的切换有卡顿
spine:setMix("walk", "jump", 0.2)
-- 添加动画,参数(通道索引,动画名,是否循环,延迟秒数默认为0)
spine:addAnimation(0, "jump", false, 3)
self:addChild(spine)
--[[
动画事件监听主要通过registerSpineEventHandler来实现, 事件类型主要有:
ANIMATION_START 动画开始
ANIMATION_END 单个动画结束
ANIMATION_COMPLETE 动画流程结束(一次循环)
ANIMATION_EVENT 动画事件
其回调接口event反馈的数据主要有:
event = {
animation = "", -- 动画名
loopCount = 0, -- 动画结束循环的次数
trackIndex = 0, -- 动画所处的通道
type = "", -- 动画监听的事件类型
}
]]
local callFunc = function(event)
dump(event, "spineEvent")
end
spine:registerSpineEventHandler(callFunc, sp.EventType.ANIMATION_START)
spine:registerSpineEventHandler(callFunc, sp.EventType.ANIMATION_END)
spine:registerSpineEventHandler(callFunc, sp.EventType.ANIMATION_COMPLETE)
spine:registerSpineEventHandler(callFunc, sp.EventType.ANIMATION_EVENT)
动画事件的销毁通过:unregisterSpineEventHandler
-- 动画不在使用后,一定要记得销毁回调事件,避免内存泄漏
function Debug:removeSpineHandler(spine)
if not spine then return end
if tolua.isnull(spine) then return end
spine:unregisterSpineEventHandler(sp.EventType.ANIMATION_START)
spine:unregisterSpineEventHandler(sp.EventType.ANIMATION_END)
spine:unregisterSpineEventHandler(sp.EventType.ANIMATION_COMPLETE)
spine:unregisterSpineEventHandler(sp.EventType.ANIMATION_EVENT)
end
拓展:切换动画,上个动画的残影还在,可以使用setToSetupPose
spine:setAnimation(0, "walk", true)
spine:setToSetupPose()
spine:setAnimation(0, "run", true)
拓展:动画的翻转直接使用setScaleX
local scaleX = spine:getScaleX()
spine:setScaleX(-scaleX)
切换皮肤接口:setSkin
参考文件使用,官方自带的goblins-ffd.json
关于皮肤的配置文件大概简介:
"skins": {
"default": {..}, // 默认
"goblin": {..}, // 男角色皮肤
"goblingirl": {..}, // 女角色皮肤
}
/*
需要注意的是:
在skin/default的配置, 有个字段叫做:skinnedmesh,可修改为:mesh。否则,C++运行的时候:
ASSERT FAILED ON LUA EXECUTE: Unknown attachment type: skinnedmesh
*/
示例代码:
local json, atlas = "spine/goblins-ffd.json", "spine/goblins-ffd.atlas"
local spine = sp.SkeletonAnimation:create(json, atlas, 1.5)
spine:setAnimation(0, "walk", true)
-- 设置皮肤
spine:setSkin("goblin")
self:addChild(skeletonNode)
更换组件的接口为:setAttachment
/*
@func: 设置组件
@param: 关节名
@param: 组件名
*/
bool setAttachment (const std::string& slotName, const std::string& attachmentName);
bool setAttachment (const std::string& slotName, const char* attachmentName);
如果在Lua中使用,需要补充tolua++的接口,接口的实现类似于setSkin
。大概步骤:
int lua_register_cocos2dx_spine_SkeletonRenderer(lua_State* tolua_S)
中声明tolua++方法:// lua_cocos2dx_spine_auto.cpp
tolua_function(tolua_S,"setSkin",lua_cocos2dx_spine_SkeletonRenderer_setSkin); tolua_function(tolua_S,"setAttachment",lua_cocos2dx_spine_SkeletonRenderer_setAttachment);
int lua_cocos2dx_spine_SkeletonRenderer_setAttachment(lua_State* tolua_S)
{
int argc = 0;
spine::SkeletonRenderer* cobj = nullptr;
bool ok = true;
#if COCOS2D_DEBUG >= 1
tolua_Error tolua_err;
#endif
#if COCOS2D_DEBUG >= 1
if (!tolua_isusertype(tolua_S,1,"sp.SkeletonRenderer",0,&tolua_err)) goto tolua_lerror;
#endif
cobj = (spine::SkeletonRenderer*)tolua_tousertype(tolua_S,1,0);
#if COCOS2D_DEBUG >= 1
if (!cobj)
{
tolua_error(tolua_S,"invalid 'cobj' in function 'lua_cocos2dx_spine_SkeletonRenderer_setAttachment'", nullptr);
return 0;
}
#endif
argc = lua_gettop(tolua_S)-1;
do{
if (argc == 2) {
std::string arg0;
std::string arg1;
ok &= luaval_to_std_string(tolua_S, 2,&arg0, "sp.SkeletonRenderer:setAttachment");
ok &= luaval_to_std_string(tolua_S, 3, &arg1, "sp.SkeletonRenderer:setAttachment");
if (!ok) {
break;
}
bool ret = cobj->setAttachment(arg0, arg1);
tolua_pushboolean(tolua_S,(bool)ret);
return 1;
}
}while(0);
luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d \n", "sp.SkeletonRenderer:setAttachment",argc, 1);
return 0;
#if COCOS2D_DEBUG >= 1
tolua_lerror:
tolua_error(tolua_S,"#ferror in function 'lua_cocos2dx_spine_SkeletonRenderer_setAttachment'.",&tolua_err);
#endif
return 0;
}
关于组件的配置文件,我们依然参考官方的goblins-ffd.json
文件的下skins
"skins": {
"default": {
"left hand item": { // 关节名
"dagger" : {}, // 组件名,匕首
"spear" : {}, // 组件名,长矛
},
"right hand item":{
"dagger" : {},
},
},
},
示例:
local json, atlas = "spine/goblins-ffd.json", "spine/goblins-ffd.atlas"
local spine = sp.SkeletonAnimation:create(json, atlas, 1.5)
spine:setAnimation(0, "walk", true)
spine:setSkin("goblin")
-- 设置组件(更换武器)
spine_2:setAttachment("left hand item", "dagger")
self:addChild(skeletonNode)