Cocos Creator JSB [Lv.1] (3)
目录
- 摘要
- 系列文章
- 正式开始
- 准备礼物
- 怎么送
- 送礼物
- seval_to_Gift
- 最终效果
- 在 Android 平台测试
- 划重点
摘要
承接上文 Cocos Creator JSB [Lv.1] (2)
在上文中,我们已经跟小姐姐进行了基本的互动。可是光说不练怎么行,本文中我们将要给小姐姐送礼物~
系列文章
- Cocos Creator JSB [Lv.1] (1)
- Cocos Creator JSB [Lv.1] (2)
- Cocos Creator JSB [Lv.1] (3)
正式开始
准备礼物
我们在C++
层面创建一个礼物的类。
在myjsb
目录下创建Gift.h
和Gift.cpp
。
Gift.h
#ifndef __CC_GIFT_H__
#define __CC_GIFT_H__#include <string>
#include "base/ccMacros.h"namespace cocos2d { namespace myjsb { // 命名空间,cocos2d::myjsbclass 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_setNamecls->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 = 0this.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 += valthis.heartValue.string = _heartValue})},
});
将节点挂载到脚本对应变量上。
指定按钮点击后的回调函数。
接下来,要在C++
层面实现相应的函数。
Goddess.h
...
#include <functional>
...
#include "myjsb/Gift.h"namespace cocos2d { namespace myjsb { // 命名空间,cocos2d::myjsbclass 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 <= 2k = (rand() % ((sizeof(txts) / sizeof(txts[0])) - 1)) + 1;// v是整数且,1 <= v <= 9v = ((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_receiveGiftcls->defineFunction("receiveGift", _SE(js_cocos2d_myjsb_Goddess_receiveGift));...
}
...
js_cocos2d_myjsb_Goddess_receiveGift
中的逻辑稍微有些复杂,简单总结一下:
js
层面调用jsb.Goddess.receiveGift(gift, jscallback)
- 传递过来的
gift
是se::Value
,需要将其转换成Goddess
类的实例。 - 传递过来的
jscallback
不能在C++
层面直接调用。 - 创建一个
c++callback
,在调用Goddess::receiveGift
函数时,传递这个c++callback
。 c++callback
被调用,拿到msg
和val
,将其均转换为se::Value
的形式。- 组装转换后的参数为
args
。 - 调用
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 平台测试,需要注意的点。
Cocos Creator JSB [Lv.1] (3)相关推荐
- Cocos Creator JSB [Lv.1] (1)
目录 摘要 系列文章 JSB能做什么 环境 资源 准备工作 正式开始 对工程做一些修改 如何互动 小姐姐现身 与小姐姐互动的桥梁 找到小姐姐 划重点 摘要 本系列文章主要实操JSB的手动绑定,在官方教 ...
- Cocos Creator JSB [Lv.2] TODO
目录 摘要 环境 资源 并未正式开始 思路 问题 参考 摘要 本文本来是想介绍JSB自动绑定,但在为自动绑定工具搭建运行环境时,发现对运行环境的要求有些苛刻.python,yaml,cheetah都需 ...
- Cocos Creator JSB [Lv.3]
目录 摘要 资源 正式开始 为什么要使用`JSB` `JSB`能做什么 进行`JSB`绑定需要做什么 构建工程 创建类 绑定(搭建桥梁) 实现自定义的类型转换(非必须) 注册 将新增的文件加入`lib ...
- java中JSB_深入解析Cocos Creator JSB绑定原理以及应用实践
背景 一直以来,ABCmouse 项目中的整体 JS/Native 通信调用结构都是基于 callStaticMethod evalString 的方式.通过 callStaticMethod 方法我 ...
- Cocos Creator jsb手动绑定C++
使用的是cocos creator 2.4.3版本:直接贴代码,结构如下: 在CocosDashboard\resources.editors\Creator\2.4.3\resources\coco ...
- Cocos Creator JSB绑定
至于绑定所需要的环境,自己百度,文章最后提供部分环境依赖 D:\CocosCreator 为Cocos Creator安装目录 在D:\CocosCreator\resources\cocos2d-x ...
- Cocos Creator JSB绑定 MAC系统环境
编译环境 python2.7(mac自带) 1.安装PyYAML mac: sudo easy_install pyyaml win: pip install pyyaml 2.安装Cheetah 下 ...
- Cocos Creator 2D摄像机 [Lv.1] 小视图
目录 摘要 环境 资源 准备工作 正式开始 对工程做一些修改 如何互动 上摄像机 缩放(看清黑头和脂肪粒) 动起来 辅助的边框 得有边界 用分组解决个bug 划重点 摘要 本系列文章主要实操2D摄像机 ...
- json list格式_来来来,一文让你读懂Cocos Creator如何读写JSON文件
前言 在游戏开发过程中,读取配置文件是必不可少的,而使用JSON做配置文件又比较常见,本文重点给大家讲述如何在Cocos Creator开发中读取和解析JSON数据文件以及如何写JSON文件. 一.J ...
最新文章
- 机器学习-线性回归LinearRegression
- python3 xrange *的作用
- 从hello server开始,到hello client结束
- RabbitMQ—重复消费、数据丢失和消息顺序性
- chattterbot配合UbuntuCorpusTrainer使用
- php把数字倒着展示,jQuery+PHP实现动态数字展示特效
- 什么是CPU密集型、IO密集型?
- 【RK3399Pro学习笔记】十四、ROS中tf坐标系广播与监听的编程实现
- python安装jupyterlab_超级详细 centos7 安装 jupyter lab
- mysql.createPool(db),Node.js中JavaScript操作MySQL的常用方法整理
- Jenkins持续集成学习-搭建jenkins问题汇总
- CentOS 7 配置免密码证书登录
- linux使用anaconda安装python包
- Cursor 详解及使用
- MCE公司:DDR1 和 DDR2 双靶点抑制剂的设计合成及其抗炎作用研究
- 从mysql学起_MySQL学习从这里出发!
- 让进度条颜色渐变的shader
- 谨赠20篇技术热文营造一个不一样的节日气氛!
- android7 显示到pc,安卓手机上的画面怎么投屏到Win7电脑上?超详细投屏方法看这里!...
- strcat、strcpy、strcmp三种函数用法