0
点赞
收藏
分享

微信扫一扫

【cocos2d-x从c++到js】回调函数1——按键回调

陌岛 2022-12-27 阅读 74


回调函数是界面交互和接入各种第三方SDK的关键所在,因为回调函数的C++代码是不能自动生成的,一切的一切,都需要手写完成。

比较不错的是,Cocos2d-x引擎对于回调函数提供了完整的包装机制。我们所需要做的就是了解这个机制,并使用他。学习引擎自己的代码例子,可以比较快速准确的上手这一机制。

首先,我们在Cocos2d-x 3.0 beta版中,使用他自带的工程创建工具,新建一个跨平台的JS项目。按照惯例,这是一个helloworld项目。在XCode运行时,我们可以看到:

​​

【cocos2d-x从c++到js】回调函数1——按键回调_赋值

​​

可以看到右下角的回调按钮。我们来看看他是怎么实现的。分成两个过程来做:

一、绑定回调函数过程

首先,我们要去找回调函数JS的绑定代码,在myApp.js中,init函数里面,可以看到如下代码:

​​// add a "close" icon to exit the progress. it's an autorelease object​​       


​​var​​ ​​closeItem = cc.MenuItemImage.create(​​


​​"res/CloseNormal.png"​​ ​​,​​


​​"res/CloseSelected.png"​​ ​​,​​


​​function​​ ​​() {​​


​​cc.log(​​ ​​"close button was clicked."​​ ​​);​​


​​},​​ ​​this​​ ​​);​​


​​closeItem.setAnchorPoint(cc.p(0.5, 0.5));​​


​​var​​ ​​menu = cc.Menu.create(closeItem);​​


​​menu.setPosition(cc.p(0, 0));​​


​​this​​ ​​.addChild(menu, 1);​​


​​closeItem.setPosition(cc.p(size.width - 20, 20));​​


cc.MenuItemImage.create函数的第三个参数,绑定了匿名回调函数。第四个参数,传入的是回调函数调用时的this(如果不理解JS的this机制,请先阅读一些JS的资料)。这些都是意图和作用很明显的JS代码,不用细说。


然后,我们去看底层对应执行的C++代码。在cocos2d_specifics.cpp文件中,找到js_cocos2dx_CCMenuItemImage_create函数。


​​// "create" in JS​​       


​​// cc.MenuItemImage.create( normalImage, selectedImage, [disabledImage], callback_fn, [this]​​


​​JSBool js_cocos2dx_CCMenuItemImage_create(JSContext *cx, uint32_t argc, jsval *vp)​​


​​{​​


​​if​​ ​​(argc >= 2 && argc <= 5) {​​


​​jsval *argv = JS_ARGV(cx, vp);​​


​​JSStringWrapper arg0(argv[0]);​​


​​JSStringWrapper arg1(argv[1]);​​


​​JSStringWrapper arg2;​​


​​bool​​ ​​thirdArgIsString = ​​ ​​true​​ ​​;​​


​​jsval jsCallback = JSVAL_VOID;​​


​​jsval jsThis = JSVAL_VOID;​​


​​int​​ ​​last = 2;​​


​​if​​ ​​(argc >= 3) {​​


​​thirdArgIsString = argv[2].isString();​​


​​if​​ ​​(thirdArgIsString) {​​


​​arg2.set(argv[2], cx);​​


​​last = 3;​​


​​}​​


​​}​​


​​cocos2d::MenuItemImage* ret = cocos2d::MenuItemImage::create(arg0.get(), arg1.get(), std::string(arg2.get()));​​


​​if​​ ​​(argc >= 3) {​​


​​if​​ ​​(!thirdArgIsString) {​​


​​//cc.MenuItemImage.create( normalImage, selectedImage, callback_fn, [this] )​​


​​jsCallback = argv[last++];​​


​​if​​ ​​(argc == 4) {​​


​​jsThis = argv[last];​​


​​}​​


​​}​​


​​else​​ ​​{​​


​​//cc.MenuItemImage.create( normalImage, selectedImage, disabledImage, callback_fn, [this] )​​


​​if​​ ​​(argc >= 4) {​​


​​jsCallback = argv[last++];​​


​​if​​ ​​(argc == 5) {​​


​​jsThis = argv[last];​​


​​}​​


​​}​​


​​}​​


​​}​​


​​JSObject *obj = bind_menu_item<cocos2d::MenuItemImage>(cx, ret, jsCallback, jsThis);​​


​​JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));​​


​​return​​ ​​JS_TRUE;​​


​​}​​


​​JS_ReportError(cx, ​​ ​​"Invalid number of arguments. Expecting: 2 <= args <= 5"​​ ​​);​​


​​return​​ ​​JS_FALSE;​​


​​}​​


因为在C++层,这是一个重载过的函数,所以他的实现里面有很多参数个数的判断(关于重载问题请参考之前的章节)。过滤掉很多代码,我们直接看关键部分:

​​if​​         ​​(argc >= 3) {​​       


​​if​​ ​​(!thirdArgIsString) {​​


​​//cc.MenuItemImage.create( normalImage, selectedImage, callback_fn, [this] )​​


​​jsCallback = argv[last++];​​


​​if​​ ​​(argc == 4) {​​


​​jsThis = argv[last];​​


​​}​​


​​}​​


​​else​​ ​​{​​


​​//cc.MenuItemImage.create( normalImage, selectedImage, disabledImage, callback_fn, [this] )​​


​​if​​ ​​(argc >= 4) {​​


​​jsCallback = argv[last++];​​


​​if​​ ​​(argc == 5) {​​


​​jsThis = argv[last];​​


​​}​​


​​}​​


​​}​​


​​}​​


在这里我们从参数中取出回调函数和this,分别赋值给jsCallback和jsThis。

​​JSObject *obj = bind_menu_item<cocos2d::MenuItemImage>(cx, ret, jsCallback, jsThis);​​


​​template​​        ​​<​​        ​​class​​         ​​T>​​       


​​JSObject* bind_menu_item(JSContext *cx, T* nativeObj, jsval callback, jsval thisObj) { ​​


​​js_proxy_t *p = jsb_get_native_proxy(nativeObj);​​


​​if​​ ​​(p) {​​


​​addCallBackAndThis(p->obj, callback, thisObj);​​


​​return​​ ​​p->obj;​​


​​} ​​ ​​else​​ ​​{​​


​​js_type_class_t *classType = js_get_type_from_native<T>(nativeObj);​​


​​assert​​ ​​(classType);​​


​​JSObject *tmp = JS_NewObject(cx, classType->jsclass, classType->proto, classType->parentProto);​​


​​// bind nativeObj <-> JSObject​​


​​js_proxy_t *proxy = jsb_new_proxy(nativeObj, tmp);​​


​​JS_AddNamedObjectRoot(cx, &proxy->obj, ​​ ​​typeid​​ ​​(*nativeObj).name()); ​​


​​addCallBackAndThis(tmp, callback, thisObj);​​


​​return​​ ​​tmp;​​


​​}​​


​​}​​


addCallBackAndThis执行绑定。

​​static void addCallBackAndThis(JSObject *obj, jsval callback, jsval &thisObj)​​       


​​{​​


​​if​​ ​​(callback != JSVAL_VOID) {​​


​​ScriptingCore::getInstance()->setReservedSpot(0, obj, callback);​​


​​}​​


​​if​​ ​​(thisObj != JSVAL_VOID) {​​


​​ScriptingCore::getInstance()->setReservedSpot(1, obj, thisObj);​​


​​}​​


​​}​​


​​JSBool ScriptingCore::setReservedSpot(uint32_t i, JSObject *obj, jsval value) {​​       


​​JS_SetReservedSlot(obj, i, value);​​


​​return​​ ​​JS_TRUE;​​


​​}​​



好,到此为止,回调函数的绑定全部结束。

二、调用回调函数过程

MenuItemImage的父类MenuItem中的activate函数。该函数在CCMenuItem.cpp中。

​​void​​         ​​MenuItem::activate()​​       


​​{​​


​​if​​ ​​(_enabled)​​


​​{​​


​​if​​ ​​( _callback )​​


​​{​​


​​_callback(​​ ​​this​​ ​​);​​


​​}​​





​​if​​ ​​(kScriptTypeNone != _scriptType)​​


​​{​​


​​BasicScriptData data(​​ ​​this​​ ​​);​​


​​ScriptEvent scriptEvent(kMenuClickedEvent,&data);​​


​​ScriptEngineManager::getInstance()->getScriptEngine()->sendEvent(&scriptEvent);​​


​​}​​


​​}​​


​​}​​



非常简单,首先判断按键是否可用。然后如果有C++层回调就调用。如果有脚本层(JS或lua)回调,就包装一个kMenuClickedEvent事件,然后向对应的脚本引擎发送该事件。

​​int ScriptingCore::sendEvent(ScriptEvent* evt)​​       


​​{​​


​​if​​ ​​(NULL == evt)​​


​​return​​ ​​0;​​





​​JSAutoCompartment ac(_cx, _global);​​





​​switch​​ ​​(evt->type)​​


​​{​​


​​case​​ ​​kNodeEvent:​​


​​{​​


​​return​​ ​​handleNodeEvent(evt->data);​​


​​}​​


​​break​​ ​​;​​


​​case​​ ​​kMenuClickedEvent:​​


​​{​​


​​return​​ ​​handleMenuClickedEvent(evt->data);​​


​​}​​


​​break​​ ​​;​​


​​case​​ ​​kTouchEvent:​​


​​{​​


​​return​​ ​​handleTouchEvent(evt->data);​​


​​}​​


​​break​​ ​​;​​


​​case​​ ​​kTouchesEvent:​​


​​{​​


​​return​​ ​​handleTouchesEvent(evt->data);​​


​​}​​


​​break​​ ​​;​​


​​case​​ ​​kKeypadEvent:​​


​​{​​


​​return​​ ​​handleKeypadEvent(evt->data);​​


​​}​​


​​break​​ ​​;​​


​​case​​ ​​kAccelerometerEvent:​​


​​{​​


​​return​​ ​​handleAccelerometerEvent(evt->data);​​


​​}​​


​​break​​ ​​;​​


​​default​​ ​​:​​


​​break​​ ​​;​​


​​}​​





​​return​​ ​​0;​​


​​}​​




ScriptingCore::sendEvent进行事件分发。kMenuClickedEvent事件派发给handleMenuClickedEvent函数来处理。

​​int ScriptingCore::handleMenuClickedEvent(void* data)​​       


​​{​​


​​if​​ ​​(NULL == data)​​


​​return​​ ​​0;​​





​​BasicScriptData* basicScriptData = static_cast<BasicScriptData*>(data);​​


​​if​​ ​​(NULL == basicScriptData->nativeObject)​​


​​return​​ ​​0;​​





​​MenuItem* menuItem = static_cast<MenuItem*>(basicScriptData->nativeObject);​​





​​js_proxy_t * p = jsb_get_native_proxy(menuItem);​​


​​if​​ ​​(!p) ​​ ​​return​​ ​​0;​​


​​jsval retval;​​


​​jsval dataVal;​​


​​js_proxy_t *proxy = jsb_get_native_proxy(menuItem);​​


​​dataVal = (proxy ? OBJECT_TO_JSVAL(proxy->obj) : JSVAL_NULL);​​


​​executeJSFunctionFromReservedSpot(​​ ​​this​​ ​​->_cx, p->obj, dataVal, retval);​​


​​return​​ ​​1;​​


​​}​​



​​static​​         ​​void​​         ​​executeJSFunctionFromReservedSpot(JSContext *cx, JSObject *obj,​​       


​​jsval &dataVal, jsval &retval) {​​


​​jsval func = JS_GetReservedSlot(obj, 0);​​


​​if​​ ​​(func == JSVAL_VOID) { ​​ ​​return​​ ​​; }​​


​​jsval thisObj = JS_GetReservedSlot(obj, 1);​​


​​JSAutoCompartment ac(cx, obj);​​





​​if​​ ​​(thisObj == JSVAL_VOID) {​​


​​JS_CallFunctionValue(cx, obj, func, 1, &dataVal, &retval);​​


​​} ​​ ​​else​​ ​​{​​


​​assert​​ ​​(!JSVAL_IS_PRIMITIVE(thisObj));​​


​​JS_CallFunctionValue(cx, JSVAL_TO_OBJECT(thisObj), func, 1, &dataVal, &retval);​​


​​}​​


​​}​​


JS_CallFunctionValue函数完成JS层回调函数的调用。




下篇继续

举报

相关推荐

0 条评论