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

    xiaoxiao2025-11-12  4

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

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

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

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

    一、绑定回调函数过程

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

    1 2 3 4 5 6 7 8 9 10 11 12 // 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函数。

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 // "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++层,这是一个重载过的函数,所以他的实现里面有很多参数个数的判断(关于重载问题请参考之前的章节)。过滤掉很多代码,我们直接看关键部分:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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。

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

    由这句模板函数来实现回调的绑定,四个参数依次是,JS上下文,cc.MenuItemImage对应的C++对象,回调函数,和回调函数调用时的this。

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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;      } }

    继续看bind_menu_item的实现。简单说一下,因为绑定的是一个JS函数,所以实际上,需要在SpiderMonkey里面做这个绑定操作。传进来的是一个C++对象(CCMenuItemImage类型),首先找到和这个C++对象对应的JS对象。如果找不到,就新建立一个。然后通过函数addCallBackAndThis执行绑定。

    1 2 3 4 5 6 7 8 9 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);      } }

    1 2 3 4 JSBool ScriptingCore::setReservedSpot(uint32_t i, JSObject *obj, jsval value) {      JS_SetReservedSlot(obj, i, value);      return  JS_TRUE; }

    最终我们看到,存储回调函数的方法是通过SpiderMonkey的ReservedSlot机制。0位存放的是回调函数,1位存放的是回调函数对应的this。

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

    二、调用回调函数过程

    现在我们看从C++层启动JS回调的过程。我们省略掉事件派发机制,直接看按键事件发生时的调用代码。在按键事件发生时,会调用MenuItemImage的父类MenuItem中的activate函数。该函数在CCMenuItem.cpp中。

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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事件,然后向对应的脚本引擎发送该事件。

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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; }

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

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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; }

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 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);      } }

    再次通过SpiderMonkey的ReservedSlot机制,取回相应的参数,最后通过JS_CallFunctionValue函数完成JS层回调函数的调用。

    下篇继续

    转载请注明原文地址: https://ju.6miu.com/read-1304121.html
    最新回复(0)