更新Cocos2d-x 3.2 Spine runtime运行库到2.1以及相关lua binding的更新

Cocos2d-x整合的Spine运行库版本一直是1.1,在3.2的Cocos2d-x下测试Spine功能时,很快就发现了问题(用的还只是某个泄漏的1.7版本),在做动画时如果关键帧中有alpha的话,比如做淡入淡出动画效果,会发现只有在win32平台时显示效果和Spine Editor中看到的一致,其它平台,如iOS Android Mac等看到的都是alpha基本没有变化,而且有些像叠加出来的效果一样。看了一下官方运行库的github readme (https://github.com/EsotericSoftware/spine-runtimes),提到有可能是由于3.x对于图片的alpha预乘(Premultiplied Alpha)处理导致,可以尝试改变export时的图片选项,开启预乘,试了一下,发现依然不对,而且奇怪的是反复重加载错误Spine动画的场景时有一定可能性显示正确!这时第一反应就是整合的Spine runtime问题,于是准备更新到2.1…

先看了下github中cocos2d-x部分的setup说明,然后又对比了下已有的framework结构,感觉按照说明中的方法进行整合的话可能欠妥,我想寻找的是可以替换掉现在framework中1.1版本runtime的方法,而且说明中的方法也没有对于lua binding部分的说明,又看了下github中的lua runtime,发现是纯lua实现的除实际绘图以外的逻辑,而非对原生代码做binding方式,所以还是自己手动替换整合好了。
首先当然是拿到github上最新版的spine-runtime,这里要用的是spine-c和spine-cocos2dx的最新版,然后按照已有cocos2d-x的做法,将所有.c改为.cpp,然后混在一起替换到frameworks/cocos2d-x/cocos/editor-support/spine/位置,这里本来我以为1.1和2.1之间只是一些内部实现上的变动,但实际发现还有一定的重构,最直接的就是需要binding的CCSkeleton中的Skeleton重构成了SkeletonRenderer,所以最好还是先删掉现有的所有.h和.cpp,再复制入新版本,最后整理一下VCS中的变动结果,新加入了:MeshAttachment PolygonBatch SkeletonAnimation SkeletonRenderer SkinnedMeshAttachment 删掉了:CCSkeleton CCSkeletonAnimation,当然删掉的实际上只是被重构了。
然后根据source的变动,更新相关的Android.mk,Xcode项目以及VS项目CMake等等,保证Spine-runtime部分可以正常编译通过先!
再后面就是lua binding的问题了,由于runtime部分有一定的重构,不管是auto部分还是manual部分都无法直接编译通过了,首先修改tolua的auto脚本frameworks/cocos2d-x/tools/tolua/cocos2dx_spine.ini,直接贴出修改结果:

[cocos2dx_spine]
# the prefix to be added to the generated functions. You might or might not use this in your own
# templates
prefix = cocos2dx_spine

# create a target namespace (in javascript, this would create some code like the equiv. to `ns = ns || {}`)
# all classes will be embedded in that namespace
target_namespace = sp

android_headers = -I%(androidndkdir)s/platforms/android-14/arch-arm/usr/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.7/libs/armeabi-v7a/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.7/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.8/libs/armeabi-v7a/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.8/include
android_flags = -D_SIZE_T_DEFINED_ 

clang_headers = -I%(clangllvmdir)s/lib/clang/3.3/include 
clang_flags = -nostdinc -x c++ -std=c++11

cocos_headers = -I%(cocosdir)s/cocos -I%(cocosdir)s/cocos/editor-support -I%(cocosdir)s/cocos/platform/android

cocos_flags = -DANDROID

cxxgenerator_headers = 

# extra arguments for clang
extra_arguments = %(android_headers)s %(clang_headers)s %(cxxgenerator_headers)s %(cocos_headers)s %(android_flags)s %(clang_flags)s %(cocos_flags)s %(extra_flags)s 

# what headers to parse
headers = %(cocosdir)s/cocos/editor-support/spine/spine-cocos2dx.h

# what classes to produce code for. You can use regular expressions here. When testing the regular
# expression, it will be enclosed in "^$", like this: "^Menu*$".
classes = SkeletonRenderer SkeletonAnimation

# what should we skip? in the format ClassName::[function function]
# ClassName is a regular expression, but will be used like this: "^ClassName$" functions are also
# regular expressions, they will not be surrounded by "^$". If you want to skip a whole class, just
# add a single "*" as functions. See bellow for several examples. A special class name is "*", which
# will apply to all class names. This is a convenience wildcard to be able to skip similar named
# functions from all classes.

skip = SkeletonRenderer::[findBone findSlot getAttachment setAttachment update draw drawSkeleton createWith.* (s|g)etBlendFunc],
        SkeletonAnimation::[addAnimationState setAnimationStateData update createWith.* (s|g)etBlendFunc addAnimation getCurrent setAnimation on.*.Event set.*.Listener getState]

rename_functions = 

rename_classes = 

# for all class names, should we remove something when registering in the target VM?
remove_prefix = 

# classes for which there will be no "parent" lookup
classes_have_no_parents = 

# base classes which will be skipped when their sub-classes found them.
base_classes_to_skip =

# classes that create no constructor
# Set is special and we will use a hand-written constructor
abstract_classes = Skeleton SkeletonAnimation

# Determining whether to use script object(js object) to control the lifecycle of native(cpp) object or the other way around. Supported values are 'yes' or 'no'.
script_control_cpp = no

执行gen-bindings重新生成auto binding部分的代码,接下来处理manual binding部分,对于lua_cocos2dx_spine_manual.cpp中,主要是一些直接读写成员变量的部分换成了getter和setter,比如:self->timeScale = scale;换成self->setTimeScale(scale);等,按照编译错误修改一下便是,再有就是对于回调部分的处理,也有一些变化,修改LuaSkeletonAnimation.cpp中代码为:

 /****************************************************************************
 Copyright (c) 2013      Edward Zhou
 Copyright (c) 2013-2014 Chukong Technologies Inc.
 
 http://www.cocos2d-x.org
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ****************************************************************************/

#include "LuaSkeletonAnimation.h"
#include "cocos2d.h"
#include "LuaScriptHandlerMgr.h"
#include "CCLuaStack.h"
#include "CCLuaEngine.h"

using namespace spine;
USING_NS_CC;

static int SendSpineEventToLua(int nHandler, spine::SkeletonAnimation* node, int trackIndex, spEventType type, spEvent* event, int loopCount)
{
    if (nHandler <= 0) {
        return 0;
    }

    if (NULL == ScriptEngineManager::getInstance()->getScriptEngine()) {
        return 0;
    }

    LuaStack *pStack = LuaEngine::getInstance()->getLuaStack();
    if (NULL == pStack) {
        return 0;
    }

    lua_State *tolua_s = pStack->getLuaState();
    if (NULL == tolua_s) {
        return 0;
    }

    int nRet = 0;

	spTrackEntry* entry = spAnimationState_getCurrent(node->getState(), trackIndex);
	std::string animationName = (entry && entry->animation) ? entry->animation->name : "";
	std::string eventType = "";

	switch (type) {
	case SP_ANIMATION_START:
		eventType = "start";
		break;
	case SP_ANIMATION_END:
		eventType = "end";
		break;
	case SP_ANIMATION_COMPLETE:
		eventType = "complete";
		break;
	case SP_ANIMATION_EVENT:
		eventType = "event";
		break;
	}

	LuaValueDict spineEvent;
	spineEvent.insert(spineEvent.end(), LuaValueDict::value_type("type", LuaValue::stringValue(eventType)));
	spineEvent.insert(spineEvent.end(), LuaValueDict::value_type("trackIndex", LuaValue::intValue(trackIndex)));
	spineEvent.insert(spineEvent.end(), LuaValueDict::value_type("animation", LuaValue::stringValue(animationName)));
	spineEvent.insert(spineEvent.end(), LuaValueDict::value_type("loopCount", LuaValue::intValue(loopCount)));

	if (NULL != event) {
		LuaValueDict eventData;
		eventData.insert(eventData.end(), LuaValueDict::value_type("name", LuaValue::stringValue(event->data->name)));
		eventData.insert(eventData.end(), LuaValueDict::value_type("intValue", LuaValue::intValue(event->intValue)));
		eventData.insert(eventData.end(), LuaValueDict::value_type("floatValue", LuaValue::floatValue(event->floatValue)));
		eventData.insert(eventData.end(), LuaValueDict::value_type("stringValue", LuaValue::stringValue(event->stringValue)));
		spineEvent.insert(spineEvent.end(), LuaValueDict::value_type("eventData", LuaValue::dictValue(eventData)));
	}

	pStack->pushLuaValueDict(spineEvent);
    nRet = pStack->executeFunctionByHandler(nHandler, 1);
    pStack->clean();
    return nRet;

}

LuaSkeletonAnimation::LuaSkeletonAnimation (const char* skeletonDataFile, const char* atlasFile, float scale)
: spine::SkeletonAnimation(skeletonDataFile, atlasFile, scale)
{
	// this->setAnimationListener(this, animationStateEvent_selector(LuaSkeletonAnimation::animationStateEvent));
	this->setStartListener( [this] (int trackIndex) {
		// spTrackEntry* entry = spAnimationState_getCurrent(skeletonNode->getState(), trackIndex);
		// const char* animationName = (entry && entry->animation) ? entry->animation->name : 0;
		// log("%d start: %s", trackIndex, animationName);
		
		int nHandler = ScriptHandlerMgr::getInstance()->getObjectHandler((void*)this, ScriptHandlerMgr::HandlerType::EVENT_SPINE);
		if (0 != nHandler) {
			SendSpineEventToLua(nHandler, this, trackIndex, SP_ANIMATION_START, NULL, 0);
		}
	});
	this->setEndListener( [this] (int trackIndex) {
		// log("%d end", trackIndex);
		
		int nHandler = ScriptHandlerMgr::getInstance()->getObjectHandler((void*)this, ScriptHandlerMgr::HandlerType::EVENT_SPINE);
		if (0 != nHandler) {
			SendSpineEventToLua(nHandler, this, trackIndex, SP_ANIMATION_END, NULL, 0);
		}
	});
	this->setCompleteListener( [this] (int trackIndex, int loopCount) {
		// log("%d complete: %d", trackIndex, loopCount);
		int nHandler = ScriptHandlerMgr::getInstance()->getObjectHandler((void*)this, ScriptHandlerMgr::HandlerType::EVENT_SPINE);
		if (0 != nHandler) {
			SendSpineEventToLua(nHandler, this, trackIndex, SP_ANIMATION_COMPLETE, NULL, loopCount);
		}
	});
	this->setEventListener( [this] (int trackIndex, spEvent* event) {
		// log("%d event: %s, %d, %f, %s", trackIndex, event->data->name, event->intValue, event->floatValue, event->stringValue);
		int nHandler = ScriptHandlerMgr::getInstance()->getObjectHandler((void*)this, ScriptHandlerMgr::HandlerType::EVENT_SPINE);
		if (0 != nHandler) {
			SendSpineEventToLua(nHandler, this, trackIndex, SP_ANIMATION_EVENT, event, 0);
		}
	});
}


LuaSkeletonAnimation::~LuaSkeletonAnimation()
{
    ScriptHandlerMgr::getInstance()->removeObjectAllHandlers((void*)this);
}

LuaSkeletonAnimation* LuaSkeletonAnimation::createWithFile (const char* skeletonDataFile, const char* atlasFile, float scale)
{
	LuaSkeletonAnimation* node = new LuaSkeletonAnimation(skeletonDataFile, atlasFile, scale);
	node->autorelease();
	return node;
}

// void LuaSkeletonAnimation::animationStateEvent (spine::SkeletonAnimation* node, int trackIndex, spEventType type, spEvent* event, int loopCount)
// {
    // int nHandler = ScriptHandlerMgr::getInstance()->getObjectHandler((void*)this, ScriptHandlerMgr::HandlerType::EVENT_SPINE);
    // if (0 != nHandler) {
        // SendSpineEventToLua(nHandler, node, trackIndex, type, event, loopCount);
    // }
// }

这里只是重新实现了SP_ANIMATION_START, SP_ANIMATION_END等消息回调,还没有加入新版runtime加入的其它消息回调,如track部分的。
这样就完成了更新spine-runtime 2.1以及对应的lua binding。
PS: 看到cocos2d-x论坛上也有人提到了类似的问题,说还有cocos2d-x中对预乘实现的问题,具体如下,如果alpha动画依然不对的话,可以尝试更新下其中提到的相关文件:http://discuss.cocos2d-x.org/t/cocos2dx-3-2-editor-support-spine/15802

博主友情提示:

如您在评论中需要提及如QQ号、电子邮件地址或其他隐私敏感信息,欢迎使用>>博主专用加密工具v3<<处理后发布,原文只有博主可以看到。