cocosLua 之 骨骼动画

Spine简介

Spine是一款收费的且针对于游戏的2D的骨骼动画工具, 它支持Unity, Cocos2d等引擎, 支持语言有Lua, Js, ActionScript, C, C#等。

它会将角色的部位图片绑定到一个个互相作用的链接的骨骼上, 通过控制这些骨骼的位置,旋转和缩放等形成各种动画的显示。

同序列帧动画相比较有着如下的优势:

  • 更少的美术资源:骨骼动画的资源是一块块小的角色部件
  • 更小的体积:帧动画需要提供每一帧图片。而骨骼动画只需要少量的图片资源
  • 更好的流畅性:骨骼动画使用差值算法计算中间帧,这能让动画总是保持流畅的效果
  • 动画重用:你可以更换角色的装备,甚至改变角色的样貌来达到动画重用的效果
  • 不同动画可混合使用:不同的骨骼动画可以被结合。比如可以转动头部、射击并且同时也在走路

Spine骨骼动画的组成主要有:

  • Bone骨骼:基本组成元素,骨骼之间存在父子关系, 父骨骼会带动其子骨骼。 每块骨骼都有它的名字和长度, 可以用于移动,旋转和缩放。

  • Slot插槽:主要用于关联Attachment附件, 每块Bone骨骼可关联到多个插槽,而每个插槽会关联多个附件, 骨骼本身并没有实现显示功能,而是通过激活的附件进行显示。

  • Attachment附件:主要用于显示,在同一时间只能激活一个附件。附件的类型有:

    • Region(图片)
    • Mesh(网格)
    • BoundingBox(边界框)
    • SkinnedMesh(蒙皮网格)
  • Skin皮肤:主要用于通过新的附件来切换骨骼动画的表现。比如:男女皮肤的切换

  • Animation动画:主要用于让骨骼动画的运动,它会包含一系列的时间轴(通道),比如:

    • 骨骼时间轴,描述骨骼如何运动
    • 插槽时间轴,描述颜色以及附件的变化,可实现帧动画
    • 事件时间轴,记录动画中触发的事件,以及事件携带的参数
    • 渲染顺序时间轴, 描述了插槽渲染顺序的变化

    一个骨骼动画对象可以拥有多个动画, 比如,走,跑,死亡等。它也支持同时播放多个动画,比如走动的过程中进行射击。

Spine骨骼动画工具导出的文件主要有jsonskel二进制格式,它的主要导出文件有如下几种:

  • .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 Spine

在cocos2d-x中,骨骼动画主要的实现文件位于../editor-support/spine中,它的主要类接口是:SkeletonAnimation

SkeletonAnimation
SkeletonRenderer
Node
BlendProtocol

相关的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;

cocosLua 的spine使用

创建的主要接口是: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)

cocosLua 骨骼动画更换皮肤

切换皮肤接口: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)

cocosLua 骨骼动画更换组件(武器)

更换组件的接口为: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)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹤九日

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值