Cocos Creator JSB [Lv.1] (3)

摘要

承接上文 Cocos Creator JSB [Lv.1] (2)
在上文中,我们已经跟小姐姐进行了基本的互动。可是光说不练怎么行,本文中我们将要给小姐姐送礼物~

系列文章

正式开始

准备礼物

我们在C++层面创建一个礼物的类。
myjsb目录下创建Gift.hGift.cpp
Gift.h

#ifndef __CC_GIFT_H__
#define __CC_GIFT_H__

#include <string>
#include "base/ccMacros.h"

namespace cocos2d { namespace myjsb {		// 命名空间,cocos2d::myjsb

    class CC_DLL Gift		// 普通的带有构造函数的类。
    {
    public: 
        Gift();		// 无参构造函数。
        Gift(const std::string& name);	// 带1个参数的构造函数。
        virtual ~Gift();	// 析构函数。

		// 对应成员变量的 set 和 get 方法。
        inline const std::string& getName() const { return _name; };
        inline void setName(std::string& name) { _name = name; };
    protected:       
        std::string _name;	// 礼物的名称,会在构造函数中赋初始值。
    };

}}  // namespace cocos2d::myjsb

#endif    // __CC_GIFT_H__

Gift.cpp

#include "myjsb/Gift.h"

namespace cocos2d { namespace myjsb {		// 命名空间,cocos2d::myjsb

	// 无参构造函数。
    Gift::Gift(): 
    _name("")
    {
        // nothing.
    }

	// 带1个参数的构造函数。
    Gift::Gift(const std::string& name): 
    _name("")
    {
        _name = name;	// 初始化礼物名称。
    }

	// 析构函数。
    Gift::~Gift()
    {
        // nothing.
    }

}}  //  namespace cocos2d::myjsb

将这两个文件加入libcocos2d中,从而让它们编译进libcocos2d库中,
在这里插入图片描述在这里插入图片描述
jsb_cocos2dx_myjsb_auto.hpp

...
extern se::Object* __jsb_cocos2d_myjsb_Gift_proto;
extern se::Class* __jsb_cocos2d_myjsb_Gift_class;

bool js_register_cocos2d_myjsb_Gift(se::Object* obj);
bool register_all_myjsb(se::Object* obj);
// 析构函数的桥梁,构造函数的桥梁不用在这里声明。
SE_DECLARE_FINALIZE_FUNC(js_cocos2d_myjsb_Gift_finalize);
// 声明作为桥梁的函数。
// 桥梁函数的作用:
// 1、将js层面传递过来的参数从se::Value类型转换为C++的类型。
// 2、调用对应的(在js_register_xxx中绑定的)C++类的成员函数,并传递转换后的的参数。
// 3、得到返回值,并将其转换为se::Value类型。
// 4、存入s.rval中,s.rval中的值就是返回给js层面的返回值。
SE_DECLARE_FUNC(js_cocos2d_myjsb_Gift_getName);
SE_DECLARE_FUNC(js_cocos2d_myjsb_Gift_setName);
...

jsb_cocos2dx_myjsb_auto.cpp

...
#include "myjsb/Gift.h"
...
se::Object* __jsb_cocos2d_myjsb_Gift_proto = nullptr;
se::Class* __jsb_cocos2d_myjsb_Gift_class = nullptr;

// 桥梁函数,new jsb.Gift 实际会调用到这里。
static bool js_cocos2d_myjsb_Gift_constructor(se::State& s) {
    const auto& args = s.args();	// 传递的参数。
    size_t argc = args.size();		// 参数的数量。
    CC_UNUSED bool ok = true;		// 参数转换是否成功的标志。

    do {
        if (argc == 0) {
        	// 调用不带参数的构造函数。
            cocos2d::myjsb::Gift* cobj = new (std::nothrow) cocos2d::myjsb::Gift();
            // 将 C++ 类的实例保存在 se::State 中,之后可以通过 s.nativeThisObject() 获取。
            s.thisObject()->setPrivateData(cobj);
            se::NonRefNativePtrCreatedByCtorMap::emplace(cobj);
            return true;
        } else if (argc == 1) {
            std::string name;
            // 传递过来的昵称是 se::Value 的形式,将其转换为 std::string 。
            ok &= seval_to_std_string(args[0], &name);
            SE_PRECONDITION2(ok, false, "js_cocos2d_myjsb_Gift_constructor : Error processing new value");

			// 调用带1个参数的构造函数。
            cocos2d::myjsb::Gift* cobj = new (std::nothrow) cocos2d::myjsb::Gift(name);
            // 将 C++ 类的实例保存在 se::State 中,之后可以通过 s.nativeThisObject() 获取。
            s.thisObject()->setPrivateData(cobj);
            se::NonRefNativePtrCreatedByCtorMap::emplace(cobj);
            return true;
        }
    } while(false);

	// 传递的参数不正确时,打印的提示信息。
    SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d or %d", (int)argc, 0, 1);
    return false;
}
SE_BIND_CTOR(js_cocos2d_myjsb_Gift_constructor, __jsb_cocos2d_myjsb_Gift_class, js_cocos2d_myjsb_Gift_finalize)

// 桥梁函数,js对象被回收时,实际会调用到这里。
static bool js_cocos2d_myjsb_Gift_finalize(se::State& s) {
    CCLOGINFO("jsbindings: finalizing JS object %p (cocos2d::myjsb::Gift)", s.nativeThisObject());

    auto iter = se::NonRefNativePtrCreatedByCtorMap::find(s.nativeThisObject());
    if (iter != se::NonRefNativePtrCreatedByCtorMap::end())
    {
        se::NonRefNativePtrCreatedByCtorMap::erase(iter);
        // s.nativeThisObject() 可以获取到绑定的 C++ 类的实例。
        cocos2d::myjsb::Gift* cobj = (cocos2d::myjsb::Gift*)s.nativeThisObject();
        // 删除 C++ 类的实例,从而触发类的析构函数。
        delete cobj;
    }

    return true;
}
SE_BIND_FINALIZE_FUNC(js_cocos2d_myjsb_Gift_finalize)

// 桥梁函数,let foo = gift.name 时,实际会调用到这里。
static bool js_cocos2d_myjsb_Gift_getName(se::State& s) {
	// s.nativeThisObject() 可以获取到绑定的 C++ 类的实例。
    cocos2d::myjsb::Gift* cobj = (cocos2d::myjsb::Gift*)s.nativeThisObject();
    SE_PRECONDITION2(cobj, false, "js_cocos2d_myjsb_Gift_getName : Invalid Native Object");

    const auto& args = s.args();	// 传递的参数。
    size_t argc = args.size();		// 参数的数量。
    CC_UNUSED bool ok = true;		// 参数转换是否成功的标志。

    if (argc == 0) {	// 需要0个参数。
	    // 不需要参数,也就没有了参数的转换部分。
        std::string result = cobj->getName();	// 调用类的成员函数,并得到返回值。
        // 将返回值转换为 se::Value 类型,并赋值到s.rval,s.rval中的值就是返回给js层面的返回值。
        ok &= std_string_to_seval(result, &s.rval());
        SE_PRECONDITION2(ok, false, "js_cocos2d_myjsb_Gift_getName : Error processing arguments");
        return true;
    }
    
    // 传递的参数不正确时,打印的提示信息。
    SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 0);
    return false;
}
SE_BIND_PROP_GET(js_cocos2d_myjsb_Gift_getName)

// 桥梁函数,gift.name = "foo" 时,实际会调用到这里。
static bool js_cocos2d_myjsb_Gift_setName(se::State& s) {
	// s.nativeThisObject() 可以获取到绑定的 C++ 类的实例。
    cocos2d::myjsb::Gift* cobj = (cocos2d::myjsb::Gift*)s.nativeThisObject();
    SE_PRECONDITION2(cobj, false, "js_cocos2d_myjsb_Gift_setName : Invalid Native Object");

    const auto& args = s.args();	// 传递的参数。
    size_t argc = args.size();		// 参数的数量。
    CC_UNUSED bool ok = true;		// 参数转换是否成功的标志。

    if (argc == 1) {	// 需要1个参数。
        std::string name;
        // 传递过来的昵称是 se::Value 的形式,将其转换为 std::string 。
        ok &= seval_to_std_string(args[0], &name);
        SE_PRECONDITION2(ok, false, "js_cocos2d_myjsb_Gift_setName : Error processing new value");
        cobj->setName(name);	// 调用类的成员函数,并传递转换后的参数。
        // 因为此成员函数没有返回值,所以不用对 s.rval 赋值。

        return true;
    }

	// 传递的参数不正确时,打印的提示信息。
    SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1);
    return true;
}
SE_BIND_PROP_SET(js_cocos2d_myjsb_Gift_setName)

bool js_register_cocos2d_myjsb_Gift(se::Object* obj)
{
    auto cls = se::Class::create("Gift", obj, nullptr, _SE(js_cocos2d_myjsb_Gift_constructor));

	// 这里就是定义,jsb.Gift要暴露出一个name属性,
	// let foo = gift.name时,在JSB中要调用 js_cocos2d_myjsb_Gift_getName
	// gift.name= "foo" 时,在JSB中要调用 js_cocos2d_myjsb_Gift_setName
    cls->defineProperty("name", _SE(js_cocos2d_myjsb_Gift_getName), _SE(js_cocos2d_myjsb_Gift_setName));
    // 这里定义js对象被垃圾回收时,需要调用js_cocos2d_myjsb_Gift_finalize
    // js_cocos2d_myjsb_Gift_finalize中做一些收尾工作,并delete掉C++类的实例,从而触发类的析构函数。
    // 构造函数的桥梁js_cocos2d_myjsb_Gift_constructor不需要在这里定义。
    cls->defineFinalizeFunction(_SE(js_cocos2d_myjsb_Gift_finalize));

    cls->install();
    // 将 se::Class::create 的 Gift 与 C++ 实际的 Gift 类对应起来。
    JSBClassType::registerClass<cocos2d::myjsb::Gift>(cls);

    __jsb_cocos2d_myjsb_Gift_proto = cls->getProto();
    __jsb_cocos2d_myjsb_Gift_class = cls;

    se::ScriptEngine::getInstance()->clearException();
    return true;
}

bool register_all_myjsb(se::Object* obj)
{
    ...
    // 以下函数被调用之后,jsb.Gift就可以使用了。
    js_register_cocos2d_myjsb_Gift(ns);
	...
}
...

这样我们的礼物就准备好了,可以在js脚本中按照如下方式使用,

let gift = new jsb.Gift()
gift.name = "爱马仕"

let another_gift = new jsb.Gift("兰博基尼")

怎么送

送给小姐姐的礼物要精心包装,还要知道小姐姐喜不喜欢。
所以我们在送礼物的接口,传递一个礼物的object,以及一个回调函数,让小姐姐在回调函数中,告诉我们她喜不喜欢这个礼物。

送礼物

创建一个输入框,用于输入礼物的名字。
创建一个按钮,用于送礼物。
创建一个心形图片以及一个文本框,用于显示小姐姐对我们的心动值~
在这里插入图片描述在这里插入图片描述在这里插入图片描述
JSBController.js中实现功能。

...
let _heartValue = 0		// 心动值。

cc.Class({
    ...
    properties: {
        ...
        giftEditBox: cc.EditBox, 	// 输入框,用于输入礼物名称。
        heartValue: cc.Label, 		// 文本框,用于显示心动值。
    }, 

    onLoad () {
        ...
        // 初始化心动值并显示。
        _heartValue = 0
        this.heartValue.string = _heartValue
    }, 
	...
    sendGift () {
        let gift_name = this.giftEditBox.string		// 获取输入的礼物名称。
        let gift = new jsb.Gift(gift_name)		// 创建一个礼物。

		// 小姐姐收礼物,传递一个礼物object,并且传递一个回调函数。
		// msg: 小姐姐收到礼物后对我们说的话。
		// val: 小姐姐收到礼物后,心动值的变化。
        _goddess.receiveGift(gift, (msg, val) => {
        	// 把小姐姐说的话以及心动值的变化,格式化后,显示出来。
            this.txt.string = msg + '(' + val + ')'
            
            // 更新心动值。
            _heartValue += val
            this.heartValue.string = _heartValue
        })
    }, 
});

将节点挂载到脚本对应变量上。
在这里插入图片描述
指定按钮点击后的回调函数。
在这里插入图片描述
接下来,要在C++层面实现相应的函数。
Goddess.h

...
#include <functional>
...
#include "myjsb/Gift.h"

namespace cocos2d { namespace myjsb {		// 命名空间,cocos2d::myjsb

    class CC_DLL Goddess 
    {
    public: 
	    // 定义收礼物接口的回调函数。
	    // msg: 小姐姐收到礼物后对我们说的话。
		// val: 小姐姐收到礼物后,心动值的变化。
        typedef std::function<void(const std::string& msg, const int val)> ResultCallback;
		...
        void receiveGift(Gift& gift, const ResultCallback& callback);	// 收礼物。
    protected: 
        ...
    };

}}  // namespace cocos2d::myjsb

Goddess.cpp

...
#include <cstdlib>
#include <ctime>

namespace cocos2d { namespace myjsb {		// 命名空间,cocos2d::myjsb
	...
	// 简单增加了随机性,增加了一点点趣味性。
    void Goddess::receiveGift(Gift& gift, const ResultCallback& callback) {
	    // 小姐姐有可能对我们说的话。
        std::string txts[] = {"What is this?", "Just so so.", "That's great!"};
        std::string s = gift.getName();		// 获取礼物名称。
        int k = 0;		// 用于 txts 的索引。
        int v = 0;		// 心动值变化。

        if (s.empty()) {	// 如果没写礼物名字。
            s += txts[0];
            v = -10;
        } else {	// 如果写了礼物名字。
            srand((int)time(0));  // 产生随机种子  把0换成NULL也行
            // k是整数且,1 <= k <= 2
            k = (rand() % ((sizeof(txts) / sizeof(txts[0])) - 1)) + 1;
            // v是整数且,1 <= v <= 9
            v = ((rand() % 10) + 1);

            if (1 == k) {	// 如果是 "Just so so." (txts[1])
                v *= -1;	// 心动值变化就是负数。
            }

            s = s + ", " + txts[k];		// 简单组织要说的话。
        }

		// 调用回调函数。
        if (callback) {
            callback(s, v);
        }
    }
    
}}  //  namespace cocos2d::myjsb

jsb_cocos2dx_myjsb_auto.hpp

...
// 声明作为桥梁的函数。
// 桥梁函数的作用:
// 1、将js层面传递过来的参数从se::Value类型转换为C++的类型。
// 2、调用对应的(在js_register_xxx中绑定的)C++类的成员函数,并传递转换后的的参数。
// 3、得到返回值,并将其转换为se::Value类型。
// 4、存入s.rval中,s.rval中的值就是返回给js层面的返回值。
SE_DECLARE_FUNC(js_cocos2d_myjsb_Goddess_receiveGift);
...

jsb_cocos2dx_myjsb_auto.cpp

...
// 桥梁函数,jsb.Goddess.receiveGift() 时,实际会调用到这里。
static bool js_cocos2d_myjsb_Goddess_receiveGift(se::State &s) {
	// s.nativeThisObject() 可以获取到绑定的 C++ 类的实例。
    cocos2d::myjsb::Goddess *cobj = (cocos2d::myjsb::Goddess *) s.nativeThisObject();
    SE_PRECONDITION2(cobj, false, "js_cocos2d_myjsb_Goddess_receiveGift : Invalid Native Object");

    const auto& args = s.args();	// 传递的参数。
    size_t argc = args.size();		// 参数的数量。
    CC_UNUSED bool ok = true;		// 参数转换是否成功的标志。

    do {
        if (argc == 2) {	// 需要2个参数。
            cocos2d::myjsb::Gift gift;
            // 传递过来的礼物是 se::Value 的形式,将其转换为 Gift 类的实例 。
            // 由于 Gift 类是我们自创建的类,
            // cocos2dx 引擎中没有将 se::Value 转换为 Gift 类的实例的代码,需要我们在之后自己编写。
            ok &= seval_to_Gift(args[0], &gift);
            // C++ 无法直接调用 js 传递过来的回调函数,所以这里定义 C++ 层面的回调函数,
            // 参数的类型以及数量与 js 的回调函数保持一致。
            // Goddess::receiveGift中调用的回调函数,实际上就是这个callback。
            // 下面的工作实际上是让这个 callback 保存一个 lambda 表达式定义的函数。
            std::function<void(const std::string& msg, const int val)> callback;
            
            // args[1] 是 js 传递过来的 callback。
            if (args[1].isObject() && args[1].toObject()->isFunction()) {
                se::Value jsThis(s.thisObject());
                se::Value jsFunc(args[1]);

                // 如果目标类是一个单例则不能用 se::Object::attachObject 去关联
                // 必须使用 se::Object::root,无需关心 unroot,unroot 的操作会随着 lambda 的销毁触发 jsFunc 的析构,在 se::Object 的析构函数中进行 unroot 操作。
                // 如果使用 s.thisObject->attachObject(jsFunc.toObject);会导致对应的 func 和 target 永远无法被释放,引发内存泄露。
                jsFunc.toObject()->root(); 
                // lambda 中所做的工作:
                // 1、将 Goddess::receiveGift 中调用回调函数所传递过来的 msg 和 val 转换为 se::Value 。
                // 2、组装转换后的参数为 args 。
                // 3、通过 js 的对象,调用 js 指定的回调函数,并传递参数 args 。
                auto lambda = [=](const std::string& msg,
                                  const int val) -> void {
                    se::ScriptEngine::getInstance()->clearException();
                    se::AutoHandleScope hs;
                    CC_UNUSED bool ok = true;
                    se::ValueArray args;	// 提供给 js 回调函数的参数。

                    args.resize(2);		// 指定参数个数。
                    // msg 是 std::string ,将其转换为 se::Value 。
                    ok &= std_string_to_seval(msg, &args[0]);
                    // val 是 int ,将其转换为 se::Value 。
                    ok &= int32_to_seval(val, &args[1]);

                    se::Value rval;		// js 回调函数的返回值。
                    se::Object* thisObj = jsThis.isObject() ? jsThis.toObject() : nullptr;
                    se::Object* funcObj = jsFunc.toObject();
                    // 执行 js 回调函数。
                    bool succeed = funcObj->call(args, thisObj, &rval);
                    if (!succeed) {
                        se::ScriptEngine::getInstance()->clearException();
                    }
                };
                // Goddess::receiveGift 中执行的 callback 实际上是 lambda 中编写的代码。
                callback = lambda;
            } else {
                callback = nullptr;
            }

            SE_PRECONDITION2(ok, false, "js_cocos2d_myjsb_Goddess_receiveGift: Error processing arguments");
            cobj->receiveGift(gift, callback);	// 调用类的成员函数,并传递转换后的参数。
            return true;
        }
    } while(false);
    
	// 传递的参数不正确时,打印的提示信息。
    SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2);
    return false;
}
SE_BIND_FUNC(js_cocos2d_myjsb_Goddess_receiveGift);

bool js_register_cocos2d_myjsb_Goddess(se::Object* obj) {
    ...
    // 这里就是定义,jsb.Goddess要暴露出一个 receiveGift 函数,
	// jsb.Goddess.receiveGift() 时,在JSB中要调用 js_cocos2d_myjsb_Goddess_receiveGift
    cls->defineFunction("receiveGift", _SE(js_cocos2d_myjsb_Goddess_receiveGift));
	...
}
...

js_cocos2d_myjsb_Goddess_receiveGift中的逻辑稍微有些复杂,简单总结一下:

  1. js层面调用jsb.Goddess.receiveGift(gift, jscallback)
  2. 传递过来的giftse::Value,需要将其转换成Goddess类的实例。
  3. 传递过来的jscallback不能在C++层面直接调用。
  4. 创建一个c++callback,在调用Goddess::receiveGift函数时,传递这个c++callback
  5. c++callback被调用,拿到msgval,将其均转换为se::Value的形式。
  6. 组装转换后的参数为args
  7. 调用jscallback并传递args

seval_to_Gift

上边有提到,Gift类是我们自创建的类,cocos2dx引擎中没有将se::Value转换为Gift类的实例的代码,所以我们需要自己来编写。
cocos/scripting/js-bindings/manual/jsb_conversions.hpp

...
#include "myjsb/Gift.h"
...
bool seval_to_Gift(const se::Value& v, cocos2d::myjsb::Gift* pt);

cocos/scripting/js-bindings/manual/jsb_conversions.cpp

...
bool seval_to_Gift(const se::Value& v, cocos2d::myjsb::Gift* pt)
{
    assert(pt != nullptr);
    SE_PRECONDITION2(v.isObject(), false, "Convert parameter to Gift failed!");
    se::Object* obj = v.toObject();		// 获取 js 对象。
    se::Value name;
    bool ok = obj->getProperty("name", &name);		// 获取 js 对象中的 name 属性。
    SE_PRECONDITION3(ok && name.isString(), false, *pt = cocos2d::myjsb::Gift::Null);
    std::string n = name.toString();	// 转换为 std::string 。
    pt->setName(n);		// 通过 Gift 类的实例的成员方法,设置成员变量的值。
    return true;
}

上面用到了一个Gift类的默认值Null,需要我们在Gift类中定义。
Gift.h

...
namespace cocos2d { namespace myjsb {

    class CC_DLL Gift
    {
    public: 
        /** A predefined Value that has not value. */
        static const Gift Null;
        ...
    protected:       
        ...
    };

}}  // namespace cocos2d::myjsb
...

Gift.cpp

...
namespace cocos2d { namespace myjsb {

    const Gift Gift::Null;
    ...
}}  //  namespace cocos2d::myjsb

最终效果

至此,送礼物的功能就实现完成了。赶快构建工程,编译,运行起来,看看最终效果。
在这里插入图片描述
哇啊!!!心动值总是负的!讨小姐姐的欢心,好难 o(╥﹏╥)o

在 Android 平台测试

如果你想在Android平台测试,实现的代码都是一样的,但是新增的这几个文件

  • cocos/myjsb/Goddess.h
  • cocos/myjsb/Goddess.cpp
  • cocos/myjsb/Gift.h
  • cocos/myjsb/Gift.cpp
  • cocos/scripting/js-bindings/auto/jsb_cocos2dx_myjsb_auto.hpp
  • cocos/scripting/js-bindings/auto/jsb_cocos2dx_myjsb_auto.cpp

加入libcocos2d的方式不太一样。
Android平台,需要修改发布路径/jsb-default/frameworks/cocos2d-x/cocos/Android.mk

LOCAL_SRC_FILES := \
...
myjsb/Goddess.cpp \
myjsb/Gift.cpp \
scripting/js-bindings/auto/jsb_cocos2dx_myjsb_auto.cpp \
...

LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) \
					...
                    $(LOCAL_PATH)/myjsb \
                    ...

划重点

  • 绑定构造函数和析构函数的写法。
  • js_xxx_constructor不用在jsb_xxx_auto.hpp中声明。
  • 绑定成员函数(有参数,参数传递js object,参数传递回调函数,无返回值)的写法。
  • se::Value转换为自创建类的实例的方式。
  • 在 Android 平台测试,需要注意的点。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值