评论

收藏

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

游戏开发 游戏开发 发布于:2021-06-27 15:15 | 阅读数:233 | 评论:0

  回调函数是界面交互和接入各种第三方SDK的关键所在,因为回调函数的C++代码是不能自动生成的,一切的一切,都需要手写完成。
比较不错的是,Cocos2d-x引擎对于回调函数提供了完整的包装机制。我们所需要做的就是了解这个机制,并使用他。学习引擎自己的代码例子,可以比较快速准确的上手这一机制。
首先,我们在Cocos2d-x 3.0 beta版中,使用他自带的工程创建工具,新建一个跨平台的JS项目。按照惯例,这是一个helloworld项目。在XCode运行时,我们可以看到: DSC0000.jpg
可以看到右下角的回调按钮。我们来看看他是怎么实现的。分成两个过程来做:

一、绑定回调函数过程
首先,我们要去找回调函数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 &gt;= 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 &gt;= 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 &gt;= 4) {
          jsCallback = argv[last++];
          if (argc == 5) {
            jsThis = argv[last];
          }
        }
      }
    }
    JSObject *obj = bind_menu_item&lt;cocos2d::MenuItemImage&gt;(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 &lt;= args &lt;= 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 &gt;= 4) {
          jsCallback = argv[last++];
          if (argc == 5) {
            jsThis = argv[last];
          }
        }
      }
    }</pre><span style="background-color:#FFFFFF;">在这里我们从参数中取出回调函数和this,分别赋值给</span><span style="font-family:consolas, 'bitstream vera sans mono', 'courier new', courier, monospace;line-height:17.600000381469727px;background-color:#FFFFFF;">jsCallback和jsThis。</span><br /><br /><pre class="brush:cpp;toolbar:false;">JSObject *obj = bind_menu_item&lt;cocos2d::MenuItemImage&gt;(cx, ret, jsCallback, jsThis);</pre>由这句模板函数来实现回调的绑定,四个参数依次是,JS上下文,cc.MenuItemImage对应的C++对象,回调函数,和回调函数调用时的this。<span style="color:#008200;font-family:consolas, 'bitstream vera sans mono', 'courier new', courier, monospace;line-height:17.600000381469727px;background-color:#ffffff;"></span><br /><pre class="brush:cpp;toolbar:false;">template&lt;class T&gt;
  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-&gt;obj, callback, thisObj);
    return p-&gt;obj;
  } else {
    js_type_class_t *classType = js_get_type_from_native&lt;T&gt;(nativeObj);
    assert(classType);
    JSObject *tmp = JS_NewObject(cx, classType-&gt;jsclass, classType-&gt;proto, classType-&gt;parentProto);
    // bind nativeObj &lt;-&gt; JSObject
    js_proxy_t *proxy = jsb_new_proxy(nativeObj, tmp);
    JS_AddNamedObjectRoot(cx, &amp;proxy-&gt;obj, typeid(*nativeObj).name());     
    addCallBackAndThis(tmp, callback, thisObj);
    return tmp;
  }
  }继续看bind_menu_item的实现。简单说一下,因为绑定的是一个JS函数,所以实际上,需要在SpiderMonkey里面做这个绑定操作。传进来的是一个C++对象(CCMenuItemImage类型),首先找到和这个C++对象对应的JS对象。如果找不到,就新建立一个。然后通过函数addCallBackAndThis执行绑定。
static void addCallBackAndThis(JSObject obj, jsval callback, jsval &thisObj)
{
if(callback != JSVAL_VOID) {
    ScriptingCore::getInstance()-&gt;setReservedSpot(0, obj, callback);
  }
  if(thisObj != JSVAL_VOID) {
    ScriptingCore::getInstance()-&gt;setReservedSpot(1, obj, thisObj);
  }
  }
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中。
void MenuItem::activate()
{
if (_enabled)
 {
    if( _callback )
    {
      _callback(this);
    }
                                                               
    if (kScriptTypeNone != _scriptType)
    {
      BasicScriptData data(this);
      ScriptEvent scriptEvent(kMenuClickedEvent,&amp;data);
      ScriptEngineManager::getInstance()-&gt;getScriptEngine()-&gt;sendEvent(&amp;scriptEvent);
    }
  }
  }
非常简单,首先判断按键是否可用。然后如果有C++层回调就调用。如果有脚本层(JS或lua)回调,就包装一个kMenuClickedEvent事件,然后向对应的脚本引擎发送该事件。
int ScriptingCore::sendEvent(ScriptEvent* evt)
{
if (NULL == evt)
    return 0;
                                
  JSAutoCompartment ac(_cx, _global);
                                   
  switch (evt-&gt;type)
  {
    case kNodeEvent:
      {
        return handleNodeEvent(evt-&gt;data);
      }
      break;
    case kMenuClickedEvent:
      {
        return handleMenuClickedEvent(evt-&gt;data);
      }
      break;
    case kTouchEvent:
      {
        return handleTouchEvent(evt-&gt;data);
      }
      break;
    case kTouchesEvent:
      {
        return handleTouchesEvent(evt-&gt;data);
      }
      break;
    case kKeypadEvent:
      {
        return handleKeypadEvent(evt-&gt;data);
      }
      break;
    case kAccelerometerEvent:
      {
        return handleAccelerometerEvent(evt-&gt;data);
      }
      break;
    default:
      break;
  }
                                   
  return 0;
  }JS通过ScriptingCore::sendEvent进行事件分发。kMenuClickedEvent事件派发给handleMenuClickedEvent函数来处理。
int ScriptingCore::handleMenuClickedEvent(void data)
{
if (NULL == data)
    return 0;
                              
  BasicScriptData* basicScriptData = static_cast&lt;BasicScriptData*&gt;(data);
  if (NULL == basicScriptData-&gt;nativeObject)
    return 0;
                              
  MenuItem* menuItem = static_cast&lt;MenuItem*&gt;(basicScriptData-&gt;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-&gt;obj) : JSVAL_NULL);
  executeJSFunctionFromReservedSpot(this-&gt;_cx, p-&gt;obj, dataVal, retval);
  return 1;
  }
static void executeJSFunctionFromReservedSpot(JSContext cx, JSObject *obj,
                        jsval &amp;dataVal, jsval &amp;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, &amp;dataVal, &amp;retval);
  } else {
    assert(!JSVAL_IS_PRIMITIVE(thisObj));
    JS_CallFunctionValue(cx, JSVAL_TO_OBJECT(thisObj), func, 1, &amp;dataVal, &amp;retval);
  }
}
再次通过SpiderMonkey的ReservedSlot机制,取回相应的参数,最后通过JS_CallFunctionValue函数完成JS层回调函数的调用。




下篇继续
关注下面的标签,发现更多相似文章