Cocos2d-x Lua注册回调到C++

2015年03月23日 10:54 0 点赞 0 评论 更新于 2025-11-21 18:13

思路

与其他编程语言类似,绑定回调的核心在于当任务执行到特定情形时,调用对应的回调方法。在本文中,将Lua回调注册到C++的核心思路是:当C代码执行到特定情形时,调用Lua的方法。

这里采用直接使用lua_stack调用Lua方法的方式,而未使用Cocos2d-x封装的dispatcher,原因是熟悉其使用格式较为繁琐。

主要步骤如下:

  1. 缓存Lua函数在Lua环境中的引用。
  2. 在C代码中以C的方式设置好回调。
  3. 当C代码的回调函数执行时,调用Lua函数。

实现

C代码绑定回调,调用Lua函数

void ArmatureNode::registerMovementEventHandler(int handler)
{
unregisterMovementEventHandler(); // 移除之前注册的监听
_movementHandler = handler; // 缓存lua函数的引用,后续会详细说明
auto dispatcher = getCCEventDispatcher();
auto f = [this](cocos2d::EventCustom *event) // 注册C代码形式的回调,使用lambda函数
{
auto eventData = static_cast<dragonBones::EventData*>(event->getUserData());
auto type = static_cast<int>(eventData->getType());
auto movementId = eventData->animationState->name;
auto lastState = eventData->armature->getAnimation()->getLastAnimationState();
auto stack = cocos2d::LuaEngine::getInstance()->getLuaStack();
stack->pushObject(this, "db.ArmatureNode");
stack->pushInt(type);
stack->pushString(movementId.c_str(), movementId.size());
// 通过LuaStack调用lua里的函数,最后一个参数设置参数个数
stack->executeFunctionByHandler(_movementHandler, 3);
};
dispatcher->addCustomEventListener(dragonBones::EventData::COMPLETE, f);
}

void ArmatureNode::unregisterMovementEventHandler(void)
{
if (0 != _movementHandler)
{
cocos2d::LuaEngine::getInstance()->removeScriptHandler(_movementHandler); // 移除lua函数的绑定
_movementHandler = 0;
}
}

提供Lua函数绑定到C的方法

上述函数直接使用Cocos中的genbinding.py无法正确生成Lua中可调用的接口,需要手动编写绑定方法。这里会用到Cocos2d-x中提供的toluafix_ref_function方法,该方法会将Lua栈中的一个方法转换为一个整数,以便在C++中调用,后续会详细说明。

int tolua_db_DBCCArmature_registerMovementEventHandler(lua_State* tolua_S)
{
if (nullptr == tolua_S)
return 0;
int argc = 0;
dragonBones::ArmatureNode* self = nullptr;
self = static_cast<dragonBones::ArmatureNode*>(tolua_tousertype(tolua_S, 1, 0)); // 第一个参数,即lua里的self
argc = lua_gettop(tolua_S) - 1;
if (1 == argc)
{
// 第二个参数,即Lua里的function,通过toluafix_ref_function函数映射成一个Int值
int handler = toluafix_ref_function(tolua_S, 2, 0);
self->registerMovementEventHandler(handler);
return 0;
}
return 0;
}

将绑定方法绑定到Lua环境里

int extends_ArmatureNode(lua_State* tolua_S)
{
lua_pushstring(tolua_S, "db.ArmatureNode"); // 之前db.ArmatureNode是通过脚本绑定在lua里,这里只做扩展
lua_rawget(tolua_S, LUA_REGISTRYINDEX);
if (lua_istable(tolua_S, -1))
{
lua_pushstring(tolua_S, "registerMovementEventHandler");
lua_pushcfunction(tolua_S, tolua_db_DBCCArmature_registerMovementEventHandler);
lua_rawset(tolua_S, -3);
}
lua_pop(tolua_S, 1);
return 0;
}

Lua里设置回调到C++

local arm = db.ArmatureNode:create("Dragon")
local animation = arm:getAnimation()
animation:gotoAndPlay("walk")
arm:registerMovementEventHandler(
function(...)
print(...)
end
)

测试

打印回调输出,测试通过,输出结果为 userdata 8 walk

其他

toluafix_ref_function 以及 toluafix_get_function_by_refid

这两个方法相互对应。toluafix_ref_function方法在注册表上将一个Lua的function与一个function_id生成映射;toluafix_get_function_by_refid方法可以通过前一个方法生成的function_id将绑定的Lua function放到栈顶。

TOLUA_API int toluafix_ref_function(lua_State* L, int lo, int def)
{
if (!lua_isfunction(L, lo)) return 0;
s_function_ref_id++; // function_id 加1
lua_pushstring(L, TOLUA_REFID_FUNCTION_MAPPING); // 在注册表上,存放luafunction映射table的key压栈
lua_rawget(L, LUA_REGISTRYINDEX); // 获取方法映射表,放在栈顶
lua_pushinteger(L, s_function_ref_id); // function_id压栈
lua_pushvalue(L, lo); // lo有效处索引处是lua方法,lua方法拷贝,压栈
lua_rawset(L, -3); // 生成映射
lua_pop(L, 1);
return s_function_ref_id;
}

TOLUA_API void toluafix_get_function_by_refid(lua_State* L, int refid)
{
lua_pushstring(L, TOLUA_REFID_FUNCTION_MAPPING); // 存放luafunction映射table的key压栈
lua_rawget(L, LUA_REGISTRYINDEX); // 获取方法映射表,放在栈顶
lua_pushinteger(L, refid); // function_id压栈
lua_rawget(L, -2); // 获取到的luafunction放到栈顶
lua_remove(L, -2);
}

executeFunctionByHandler

executeFunctionByHandler方法只是通过toluafix_get_function_by_refid获取到function,然后通过lua_pcall方法调用,这里不再展示具体代码。