• lua 使用 spine 的一些问题


    本文转自http://blog.csdn.net/dinko321/article/details/44176041

    一、基本使用:

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. self.skeletonNode = sp.SkeletonAnimation:create("sptest/spineboy.json", "sptest/spineboy.atlas", 0.3)  
    2.  self.skeletonNode:setAnimation(0, "walk", true)  
    3.    
    4.  self.skeletonNode:setMix("walk", "jump", 0.2)  
    5.  self.skeletonNode:setMix("jump", "run", 0.2)  
    6.    
    7.  self.skeletonNode:setAnimation(0, "walk", false)  
    8.  self.skeletonNode:addAnimation(0, "hit", false)  
    9.     
    10.  self.skeletonNode:setDebugBonesEnabled(true)  
    11.   
    12.  self.skeletonNode:registerSpineEventHandler(function (event)  
    13.      print(string.format("[spine] %d start: %s", event.trackIndex,event.animation))  
    14.  end, sp.EventType.ANIMATION_START)  

    1、一开始,要set一个animation,第一个参数是track(后面细说)。

    2、播放其他动画是用add

    3、当set的动画播放完毕停止之后(非循环),再add动画是不会动的。

    4、mix 就是让2个动画直接的衔接更平滑,最后一个参数是过渡时间

    二、track的概念

    做音频的有个概念,叫音轨,就是track,基本比如录歌,有个主唱,有和声,有吉他,有贝斯。那么把这些分别录制,再一起放出来,就是一首歌,分别录制的这些,就是一个个独立的音轨,一起放出来就是我们最后听到的歌曲。

    对比PS,这个概念叫图层。

    所以其实可以把不同的动作放到不同的track中去做。

    三、回调中的处理以及bug

    1、回调

    正常情况下,都会对一个动画的完结做一些处理。比如我需要的就是,当动作A完成后,播放动作C。基本写法如下:

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. self.skeletonNode:registerSpineEventHandler(function (event)     
    2.     if event.animation == "hit" then  
    3.         print("====== hit end ====")  
    4.     end  
    5. end, sp.EventType.ANIMATION_END)  

    现在的问题是,我需要skeletonNode才能播放这个动画。怎么才能拿到这个skeletonNode,想当然的以为应该这么写

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. self.skeletonNode:registerSpineEventHandler(function (event)  
    2.     if event.animation == "hit" then  
    3.         self.skeletonNode:setAnimation(0, "hit", false)  
    4.     end    
    5. end, sp.EventType.ANIMATION_END)  

    试验发现可行,但是依据是什么呢?追踪发现,这个地方是调用的一个Cpp的绑定,找到lua_cocos2dx_spine_manual.cpp如下

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. int tolua_Cocos2d_CCSkeletonAnimation_registerSpineEventHandler00(lua_State* tolua_S)  
    2. {  
    3.     {  
    4.         LuaSkeletonAnimation* self    = (LuaSkeletonAnimation*)  tolua_tousertype(tolua_S,1,0);  
    5.         if (NULL != self ) {  
    6.             int handler = (  toluafix_ref_function(tolua_S,2,0));  
    7.             spEventType eventType = static_cast<spEventType>((int)tolua_tonumber(tolua_S, 3, 0));  
    8.               
    9.             switch (eventType) {  
    10.                 case spEventType::SP_ANIMATION_END:  
    11.                     {  
    12.                         self->setEndListener([=](int trackIndex){  
    13.                             executeSpineEvent(self, handler, eventType, trackIndex);  
    14.                         });  
    15.                         ScriptHandlerMgr::getInstance()->addObjectHandler((void*)self, handler, ScriptHandlerMgr::HandlerType::EVENT_SPINE_ANIMATION_END);  
    16.                     }  
    17.                     break;  
    18.                 default:  
    19.                     break;  
    20.             }  
    21.         }  
    22.     }  
    23.     return 0;  
    24. }  

    代码舍弃了一些无关紧要的细节。然后就会发现,这里取了3个值,一个是self,一个是handler,就是我们传入的回调函数,还有一个eventType,然后用Cpp注册了这个回调,回调触发的时候,执行了executeSpineEvent,此函数如下:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. int executeSpineEvent(LuaSkeletonAnimation* skeletonAnimation, int handler, spEventType eventType, int trackIndex , int loopCount = 0, spEvent* event = nullptr )  
    2. {  
    3.     int ret = 0;  
    4.       
    5.     spTrackEntry* entry = spAnimationState_getCurrent(skeletonAnimation->getState(), trackIndex);  
    6.     std::string animationName = (entry && entry->animation) ? entry->animation->name : "";  
    7.     std::string eventTypeName = "";  
    8.       
    9.     switch (eventType) {  
    10.         case spEventType::SP_ANIMATION_END:  
    11.             {  
    12.                 eventTypeName = "end";  
    13.             }  
    14.             break;  
    15.         default:  
    16.             break;  
    17.     }  
    18.       
    19.     LuaValueDict spineEvent;  
    20.     spineEvent.insert(spineEvent.end(), LuaValueDict::value_type("type", LuaValue::stringValue(eventTypeName)));  
    21.     spineEvent.insert(spineEvent.end(), LuaValueDict::value_type("trackIndex", LuaValue::intValue(trackIndex)));  
    22.     spineEvent.insert(spineEvent.end(), LuaValueDict::value_type("animation", LuaValue::stringValue(animationName)));  
    23.     spineEvent.insert(spineEvent.end(), LuaValueDict::value_type("loopCount", LuaValue::intValue(loopCount)));  
    24.       
    25.     stack->pushLuaValueDict(spineEvent);  
    26.     ret = stack->executeFunctionByHandler(handler, 1);  
    27.     return ret;  
    28. }  

    然后结合quick里面关于handler的实现,类似cocos2dx在C++11以前的实现,所以这个地方是obj:function()的调用方法,这个obj就是self。所以其实回调的写法完整的应该是:

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. self.skeletonNode:registerSpineEventHandler(handler(self,function (self,event)  
    2.     if event.animation == "hit" then  
    3.         self.skeletonNode:setAnimation(0, "walk", true)  
    4.     end      
    5. end), sp.EventType.ANIMATION_END)  

    至于怎么隐式转换实现的这个function到handler,我还不知道。。。。。。希望知道的告知。。。。


    2、bug
    然后就会发现,这么写,程序会掉,回调会不停的触发。这么写,指的是在回调里面setAnimation。后面面会说解法,此部分可以直接跳过。

    从lua是找不出问题了,直接去C++。

    在C++里面写一个类似的程序,在回调里面调用setAnimation,会发现如下调用关系。

    回调调用set:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. skeletonNode->setEndListener( [this] (int trackIndex) {  
    2.        spTrackEntry* entry = spAnimationState_getCurrent(skeletonNode->getState(), trackIndex);  
    3.        const char* animationName = (entry && entry->animation) ? entry->animation->name : 0;  
    4.        if (strcmp(animationName, "hit")==0)  
    5.        {  
    6.            skeletonNode->setAnimation(0, "hit", false);  
    7.        }  
    8. });  

    node的set的实现:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. spTrackEntry* SkeletonAnimation::setAnimation (int trackIndex, const std::string& name, bool loop) {  
    2.     spAnimation* animation = spSkeletonData_findAnimation(_skeleton->data, name.c_str());  
    3.     if (!animation) {  
    4.         log("Spine: Animation not found: %s", name.c_str());  
    5.         return 0;  
    6.     }  
    7.     return spAnimationState_setAnimation(_state, trackIndex, animation, loop);  
    8. }  

    上面那个是通过名字取了动画,然后进入下一级调用:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. spTrackEntry* spAnimationState_setAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop) {  
    2.     _spAnimationState* internal = SUB_CAST(_spAnimationState, self);  
    3.   
    4.     spTrackEntry* entry;  
    5.     spTrackEntry* current = _spAnimationState_expandToIndex(self, trackIndex);  
    6.     if (current) _spAnimationState_disposeAllEntries(self, current->next);  
    7. <span style="white-space:pre">    </span>... ...  
    8. }  

    后面的选择性忽略了,因为在if这里就蹦了,然后一路追下去,会发现是最后在free的时候,free了一个没有alloc的指针,这个指针就是上面的current->next 。

    这个bug,从目前的情况看,大概就是,set新的animation的时候,会把当前的animation做清理,清理的是current的next,因为current已经播放,结果这个next空了。

    至于为什么空,不知道,太麻烦了,空了看。。。

    3、关于2的解法

    利用前面说的track,换一个track。

    之前说了,bug的主要原因是当前的track的动画需要做清理,只要不在已经setAnimation的track上set新的就行了。

    换个track,set或者add都可以。

    4、补充解法:

    又跟了一下,发现现象很诡异,

    不过如果在end回调里面,用定时器,把操作延时0.001秒或者随便多少反正就是延时到下一帧去做,就可以正常。

    未验证问题,还没弄清楚的:

    1、不同的track动画直接的的mix效果

    2、function到handler的隐式转化

    3、spine的free的next的错误的根源

  • 相关阅读:
    flash actionscript MovieClip(电影剪辑)控制
    浅谈测试驱动开发(TDD)(转)
    双缓冲渲染
    可以控制多层嵌套的movieClip播放和暂停
    flash actionscript MovieClip(电影剪辑)控制
    栈(stack)
    CDC::GetDeviceCaps()物理长度与屏幕像素间的转换
    转载学习结构体和union大小的问题
    GetDeviceCaps参数
    链表(list)
  • 原文地址:https://www.cnblogs.com/liuqing0328/p/4900884.html
Copyright © 2020-2023  润新知