Cocos Creator:Cocos Creator 中的 Swig 工作流教程

推荐:将NSDT场景编辑器加入你的3D工具链
3D工具集:NSDT简石数字孪生

Cocos Creator 中的 Swig 工作流教程

如何在引擎中绑定新模块

添加新的模块接口文件

  • 将新的模块接口文件添加到目录中,例如native/tools/swig-confignew-engine-module.i
  • 将 swig-interface-template.i 中的内容复制到 new-engine-module.i
  • 添加必要的配置,您可以参考目录中现有的文件或参考以下部分.inative/tools/swig-config

修改 swig-config.js

// ......
// Engine Module Configuration
const configList = [
    [ '2d.i', 'jsb_2d_auto.cpp' ],
    // ......
    [ 'renderer.i', 'jsb_render_auto.cpp' ],
    [ 'new-engine-module.i', 'jsb_new_engine_module_auto.cpp' ], // Add this line
];
//......

生成绑定

cd engine/native/tools/swig-config
node genbindings.js

修改engine/native/cocos/CMakeLists.txt

######## auto
cocos_source_files(
    NO_WERROR   NO_UBUILD   cocos/bindings/auto/jsb_new_engine_module_auto.cpp # Add this line
                            cocos/bindings/auto/jsb_new_engine_module_auto.h # Add this line
    NO_WERROR   NO_UBUILD   cocos/bindings/auto/jsb_cocos_auto.cpp
                            cocos/bindings/auto/jsb_cocos_auto.h
    ......

将新模块注册到脚本引擎

打开并执行以下修改jsb_module_register.cpp

......
#if CC_USE_PHYSICS_PHYSX
    #include "cocos/bindings/auto/jsb_physics_auto.h"
#endif
#include "cocos/bindings/auto/jsb_new_engine_module_auto.h" // Add this line

bool jsb_register_all_modules() {
    se::ScriptEngine *se = se::ScriptEngine::getInstance();
    ......
    se->addRegisterCallback(register_all_my_new_engine_module); // Add this line

    se->addAfterCleanupHook([]() {
        cc::DeferredReleasePool::clear();
        JSBClassType::cleanup();
    });
    return true;   
}

如何在开发人员的项目中绑定新模块

假设我们有一个位于目录的 Cocos Creator 项目。/Users/james/NewProject

在 Cocos Creator 的构建面板中构建一个原生项目,我们得到目录。/Users/james/NewProject/native

绑定简单类

创建简单类

在 中创建头文件,其内容为/Users/james/NewProject/native/engine/Classes/MyObject.h

// MyObject.h
#pragma once
#include "cocos/cocos.h"
namespace my_ns {
class MyObject {
public:
    MyObject() = default;
    MyObject(int a, bool b) {}
    virtual ~MyObject() = default;
    void print() {
        CC_LOG_DEBUG("==> a: %d, b: %d\n", _a, (int)_b);
    }

    float publicFloatProperty{1.23F};
private:
    int _a{100};
    bool _b{true};
};
} // namespace my_ns {

编写接口文件

创建一个名为 file in 的接口my-module.i/Users/james/NewProject/tools/swig-config

// my-module.i
%module(target_namespace="my_ns") my_module

// Insert code at the beginning of generated header file (.h)
%insert(header_file) %{
#pragma once
#include "bindings/jswrapper/SeApi.h"
#include "bindings/manual/jsb_conversions.h"

#include "MyObject.h" // Add this line
%}

// Insert code at the beginning of generated source file (.cpp)
%{
#include "bindings/auto/jsb_my_module_auto.h"
%}

%include "MyObject.h"

编写 swig 配置文件

创建一个名为 swig-config.js 的文件/Users/james/NewProject/tools/swig-config

// swig-config.js
'use strict';
const path = require('path');
const configList = [
    [ 'my-module.i', 'jsb_my_module_auto.cpp' ],
];

const projectRoot = path.resolve(path.join(__dirname, '..', '..'));
const interfacesDir = path.join(projectRoot, 'tools', 'swig-config');
const bindingsOutDir = path.join(projectRoot, 'native', 'engine', 'common', 'bindings', 'auto');
// includeDirs means header search path for Swig parser
const includeDirs = [
    path.join(projectRoot, 'native', 'engine', 'common', 'Classes'),
];

module.exports = {
    interfacesDir,
    bindingsOutDir,
    includeDirs,
    configList
};

为项目生成绑定

$ cd /Users/james/NewProject/tools/swig-config
$ node < Engine Root >/native/tools/swig-config/genbindings.js

如果成功,包含JS绑定代码的文件()将在目录下生成jsb_my_module_auto.cpp/.h/Users/james/NewProject/native/engine/bindings/auto

修改项目的 CMakeLists.txt

打开 、添加及其绑定代码/Users/james/NewProject/native/engine/common/CMakeLists.txtMyObject.h

include(${COCOS_X_PATH}/CMakeLists.txt)

list(APPEND CC_COMMON_SOURCES
    ${CMAKE_CURRENT_LIST_DIR}/Classes/Game.h
    ${CMAKE_CURRENT_LIST_DIR}/Classes/Game.cpp
    ############### Add the following lines ##############
    ${CMAKE_CURRENT_LIST_DIR}/Classes/MyObject.h 
    ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_my_module_auto.h
    ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_my_module_auto.cpp
    ########################################################
)

修改/Users/james/NewProject/native/engine/mac/CMakeLists.txt

cmake_minimum_required(VERSION 3.8)
# ......
cc_mac_before_target(${EXECUTABLE_NAME})
add_executable(${EXECUTABLE_NAME} ${CC_ALL_SOURCES})
############### Add the following lines ##############
target_include_directories(${EXECUTABLE_NAME} PRIVATE
    ${CC_PROJECT_DIR}/../common
)
########################################################
cc_mac_after_target(${EXECUTABLE_NAME})

打开项目

macOS:/Users/james/NewProject/build/mac/proj/NewProject.xcodeproj

窗户:< A specific directory >/NewProject/build/win64/proj/NewProject.sln

将新模块注册到脚本引擎

修改:Game.cpp

#include "Game.h"
#include "bindings/auto/jsb_my_module_auto.h" // Add this line
//......
int Game::init() {
  // ......
  se::ScriptEngine::getInstance()->addRegisterCallback(register_all_my_module); // Add this line
  BaseGame::init();
  return 0;
}
// ......

测试绑定

在项目目录的根目录中添加一个文件,使 TS 编译器知道我们的绑定类。my-module.d.ts

// my-module.d.ts
declare namespace my_ns {
class MyObject {
    constructor();
    constructor(a: number, b: number);

    publicFloatProperty : number;
    print() : void;
}
}

修改文件/Users/james/NewProject/temp/tsconfig.cocos.json

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "target": "ES2015",
    "module": "ES2015",
    "strict": true,
    "types": [
      "./temp/declarations/cc.custom-macro",
      "./temp/declarations/jsb",
      "./temp/declarations/cc",
      "./temp/declarations/cc.env",
      "./my-module" // Add this line
    ],
    // ......
    "forceConsistentCasingInFileNames": true
  }
}

在 Cocos Creator 中打开 NewProject,在场景中创建一个立方体对象,并将脚本附加到立方体,脚本的内容是

import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('MyComponent')
export class MyComponent extends Component {
    start() {
        const myObj = new my_ns.MyObject();
        myObj.print(); // Invoke native print method
        console.log(`==> myObj.publicFloatProperty: ${myObj.publicFloatProperty}`); // Get property defined in native
    }
}

运行项目,如果成功,可以在控制台找到以下日志

17:31:44 [DEBUG]: ==> a: 100, b: 1
17:31:44 [DEBUG]: D/ JS: ==> myObj.publicFloatProperty: 1.2300000190734863

章节结语

在本节中,我们学习了如何使用工具绑定一个简单的类,将其公共方法和属性导出到 JS。本节还介绍了绑定本机类的整个流程。从下一节开始,我们将重点使用更多功能来满足 JS 绑定的更多需求,例如:SwigSwig

  • 如何导入依赖的头文件
  • 如何忽略类、方法、属性
  • 如何重命名类、方法、属性
  • 如何定义将 c++ getter 和 setter 绑定为 JS 属性的属性
  • 如何在 .i 文件中配置 C++ 模块

导入依赖的头文件

假设我们让 MyObject 类继承自 MyRef 类。但是我们不想绑定MyRef类。

// MyRef.h
#pragma once
namespace my_ns {
class MyRef  {
public:
    MyRef() = default;
    virtual ~MyRef() = default;
    void addRef() { _ref++; }
    void release() { --_ref; }
private:
    unsigned int _ref{0};
};
} // namespace my_ns {
// MyObject.h
#pragma once
#include "cocos/cocos.h"
#include "MyRef.h"
namespace my_ns {
// MyObject inherits from MyRef
class MyObject : public MyRef {
public:
    MyObject() = default;
    MyObject(int a, bool b) {}
    virtual ~MyObject() = default;
    void print() {
        CC_LOG_DEBUG("==> a: %d, b: %d\n", _a, (int)_b);
    }

    float publicFloatProperty{1.23F};
private:
    int _a{100};
    bool _b{true};
};
} // namespace my_ns {

当Swig解析MyObject.h时,它将不知道是什么,它将在控制台中输出警告。MyRef

.../Classes/MyObject.h:7: Warning 401: Nothing known about base class 'MyRef'. Ignored.

解决这个问题很简单,我们需要通过使用指令让 Swig 知道 MyRef 的存在。%import

// ......
// Insert code at the beginning of generated source file (.cpp)
%{
#include "bindings/auto/jsb_my_module_auto.h"
%}

%import "MyRef.h" // Add this line to fix the warning
%include "MyObject.h"

虽然 Swig 现在不报错,但绑定代码不会被编译,错误是:

MyRefCompileError

我们将在下一节中通过指令解决此问题。%ignore

忽略类、方法、属性

忽略类

在上一节中,我们在 中遇到了编译错误。由于 MyRef 不应该被绑定,我们可以使用指令来忽略它。js_register_my_ns_MyObject%ignore

// my-module.i
// ......
%ignore my_ns::MyRef; // Add this line
%import "MyRef.h"
%include "MyObject.h"

再次生成绑定,编译正常。

// jsb_my_module_auto.cpp
bool js_register_my_ns_MyObject(se::Object* obj) {
    auto* cls = se::Class::create("MyObject", obj, nullptr, _SE(js_new_MyObject)); // parentProto will be set to nullptr
    cls->defineProperty("publicFloatProperty", _SE(js_my_ns_MyObject_publicFloatProperty_get), _SE(js_my_ns_MyObject_publicFloatProperty_set)); 
    cls->defineFunction("print", _SE(js_my_ns_MyObject_print)); 
  // ......
}

忽略方法和属性

我们向类中添加一个新方法和一个新属性。methodToBeIgnoredpropertyToBeIgnoredMyObject

// MyObject.h
#pragma once
#include "cocos/cocos.h"
#include "MyRef.h"
namespace my_ns {
// MyObject inherits from MyRef
class MyObject : public MyRef {
public:
// .....
    void methodToBeIgnored() {} // Add this line
    float propertyToBeIgnored{345.123F}; // Add this line
// ......
    float publicFloatProperty{1.23F};
private:
    int _a{100};
    bool _b{true};
};
} // namespace my_ns {

重新生成绑定,我们将得到并绑定。methodToBeIgnoredpropertyToBeIgnored

// jsb_my_module_auto.cpp
bool js_register_my_ns_MyObject(se::Object* obj) {
    auto* cls = se::Class::create("MyObject", obj, nullptr, _SE(js_new_MyObject)); 
    cls->defineProperty("propertyToBeIgnored", _SE(js_my_ns_MyObject_propertyToBeIgnored_get), _SE(js_my_ns_MyObject_propertyToBeIgnored_set)); // this property should not be bound
    cls->defineProperty("publicFloatProperty", _SE(js_my_ns_MyObject_publicFloatProperty_get), _SE(js_my_ns_MyObject_publicFloatProperty_set)); 
    cls->defineFunction("print", _SE(js_my_ns_MyObject_print)); 
    cls->defineFunction("methodToBeIgnored", _SE(js_my_ns_MyObject_methodToBeIgnored)); // this method should not be bound
    // ......
}

修改以跳过绑定它们。my-module.i

// my-module.i
// ......

%ignore my_ns::MyRef;
%ignore my_ns::MyObject::methodToBeIgnored; // Add this line
%ignore my_ns::MyObject::propertyToBeIgnored; // Add this line

%import "MyRef.h"
%include "MyObject.h"

重新生成绑定,它们现在被忽略。

// jsb_my_module_auto.cpp
bool js_register_my_ns_MyObject(se::Object* obj) {
    auto* cls = se::Class::create("MyObject", obj, nullptr, _SE(js_new_MyObject)); 
    cls->defineProperty("publicFloatProperty", _SE(js_my_ns_MyObject_publicFloatProperty_get), _SE(js_my_ns_MyObject_publicFloatProperty_set)); 
    cls->defineFunction("print", _SE(js_my_ns_MyObject_print)); 
// ......
}

重命名类、方法、属性

Swig 定义了一个指令来重命名类、方法或属性。为了演示,我们再次修改MyObject。%rename

// MyObject.h
#pragma once
#include "cocos/cocos.h"
#include "MyRef.h"
namespace my_ns {
// MyObject inherits from MyRef
class MyObject : public MyRef {
public:
// ......
    void methodToBeRenamed() { // Add this method
        CC_LOG_DEBUG("==> hello MyObject::methodToBeRenamed");
    }
    int propertyToBeRenamed{1234}; // Add this property

    float publicFloatProperty{1.23F};
private:
    int _a{100};
    bool _b{true};
};
} // namespace my_ns {

生成绑定,我们得到:

// jsb_my_module_auto.cpp
bool js_register_my_ns_MyObject(se::Object* obj) {
    auto* cls = se::Class::create("MyObject", obj, nullptr, _SE(js_new_MyObject)); 
    cls->defineProperty("propertyToBeRenamed", _SE(js_my_ns_MyObject_propertyToBeRenamed_get), _SE(js_my_ns_MyObject_propertyToBeRenamed_set)); 
    cls->defineProperty("publicFloatProperty", _SE(js_my_ns_MyObject_publicFloatProperty_get), _SE(js_my_ns_MyObject_publicFloatProperty_set)); 

    cls->defineFunction("print", _SE(js_my_ns_MyObject_print)); 
    cls->defineFunction("methodToBeRenamed", _SE(js_my_ns_MyObject_methodToBeRenamed));

如果我们想重命名为 和重命名为 ,请按如下方式修改:propertyToBeRenamedcoolPropertymethodToBeRenamedcoolMethodmy-module.i

// my-module.i
// ......
%ignore my_ns::MyRef;
%ignore my_ns::MyObject::methodToBeIgnored;
%ignore my_ns::MyObject::propertyToBeIgnored;
%rename(coolProperty) my_ns::MyObject::propertyToBeRenamed; // Add this line
%rename(coolMethod) my_ns::MyObject::methodToBeRenamed; // Add this line

%import "MyRef.h"
%include "MyObject.h"

如果我们想将类重命名为 ,我想你已经知道该怎么做了。是的,添加此行:MyObjectMyCoolObject

%rename(MyCoolObject) my_ns::MyObject;

重新生成绑定,获取导出到 JS 的正确名称。

// jsb_my_module_auto.cpp
// MyCoolObject, coolProperty, coolMethod are all what we want now.
bool js_register_my_ns_MyObject(se::Object* obj) {
    auto* cls = se::Class::create("MyCoolObject", obj, nullptr, _SE(js_new_MyCoolObject));
    cls->defineProperty("coolProperty", _SE(js_my_ns_MyCoolObject_coolProperty_get), _SE(js_my_ns_MyCoolObject_coolProperty_set)); 
    cls->defineProperty("publicFloatProperty", _SE(js_my_ns_MyCoolObject_publicFloatProperty_get), _SE(js_my_ns_MyCoolObject_publicFloatProperty_set)); 
    cls->defineFunction("print", _SE(js_my_ns_MyCoolObject_print)); 
    cls->defineFunction("coolMethod", _SE(js_my_ns_MyCoolObject_coolMethod)); 
    // ......
}

测试它,更新和.代码示例如下:my-module.d.tsMyComponent.ts

// my-module.d.ts
declare namespace my_ns {
class MyCoolObject {
    constructor();
    constructor(a: number, b: number);

    publicFloatProperty : number;
    print() : void;
    coolProperty: number;
    coolMethod() : void;
}
}
// MyComponent.ts
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('MyComponent')
export class MyComponent extends Component {
    start() {
        const myObj = new my_ns.MyCoolObject(); // Renamed to MyCoolObject
        myObj.print();
        console.log(`==> myObj.publicFloatProperty: ${myObj.publicFloatProperty}`);
        // Add the follow lines
        console.log(`==> old: myObj.coolProperty: ${myObj.coolProperty}`); 
        myObj.coolProperty = 666;
        console.log(`==> new: myObj.coolProperty: ${myObj.coolProperty}`);
        myObj.coolMethod();
    }
}

构建并运行项目,获取日志:

17:53:28 [DEBUG]: ==> a: 100, b: 1
17:53:28 [DEBUG]: D/ JS: ==> myObj.publicFloatProperty: 1.2300000190734863
17:53:28 [DEBUG]: D/ JS: ==> old: myObj.coolProperty: 1234
17:53:28 [DEBUG]: D/ JS: ==> new: myObj.coolProperty: 666
17:53:28 [DEBUG]: ==> hello MyObject::methodToBeRenamed

定义属性

%attribute指令用于绑定C++getter 和 setter 函数作为 JS 属性。

用法

定义一个属性(JS属性),而不setter

%attribute(your_namespace::your_class_name, cpp_member_variable_type, js_property_name, cpp_getter_function_name)

使用 和 定义属性(JS 属性)gettersetter

%attribute(your_namespace::your_class_name, cpp_member_variable_type, js_property_name, cpp_getter_function_name, cpp_setter_function_name)

定义一个属性(JS属性),而不getter

%attribute_writeonly(your_namespace::your_class_name, cpp_member_variable_type, js_property_name, cpp_setter_function_name)

演示

为了演示,我们为 MyObject 类添加了两个新方法。

// MyObject.h
#pragma once
#include "cocos/cocos.h"
#include "MyRef.h"
namespace my_ns {
// MyObject inherits from MyRef
class MyObject : public MyRef {
public:
// ......
    void setType(int v) { _type = v; CC_LOG_DEBUG("==> setType: v: %d", v); } // Add this line
    int getType() const { return _type; } // Add this line

    float publicFloatProperty{1.23F};
private:
    int _a{100};
    bool _b{true};
    int _type{333};
};
} // namespace my_ns {
// my-module.i
// ......
%attribute(my_ns::MyObject, int, type, getType, setType); // Add this line

%import "MyRef.h"
%include "MyObject.h"
// jsb_my_module_auto.cpp
bool js_register_my_ns_MyObject(se::Object* obj) {
// ......
    cls->defineProperty("type", _SE(js_my_ns_MyCoolObject_type_get), _SE(js_my_ns_MyCoolObject_type_set)); 
// ......
}
// MyComponent.ts
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('MyComponent')
export class MyComponent extends Component {
    start() {
        const myObj = new my_ns.MyCoolObject();
        myObj.print();
        console.log(`==> myObj.publicFloatProperty: ${myObj.publicFloatProperty}`);
        console.log(`==> old: myObj.coolProperty: ${myObj.coolProperty}`);
        myObj.coolProperty = 666;
        console.log(`==> new: myObj.coolProperty: ${myObj.coolProperty}`);
        myObj.coolMethod();
        console.log(`==> old: myObj.type: ${myObj.type}`);
        myObj.type = 888;
        console.log(`==> new: myObj.type: ${myObj.type}`);
    }
}

生成并运行项目

18:09:53 [DEBUG]: ==> a: 100, b: 1
18:09:53 [DEBUG]: D/ JS: ==> myObj.publicFloatProperty: 1.2300000190734863
18:09:53 [DEBUG]: D/ JS: ==> old: myObj.coolProperty: 1234
18:09:53 [DEBUG]: D/ JS: ==> new: myObj.coolProperty: 666
18:09:53 [DEBUG]: ==> hello MyObject::methodToBeRenamed
18:09:53 [DEBUG]: D/ JS: ==> old: myObj.type: 333
18:09:53 [DEBUG]: ==> setType: v: 888 // Cool, C++ setType is invoked
18:09:53 [DEBUG]: D/ JS: ==> new: myObj.type: 888 // Cool, C++ getType is invoked, 888 is return from C++

%attribute_writeonly 指令

%attribute_writeonly指令是我们在 SWIG 后端添加的扩展,它用于C++类只有一个函数而没有函数的目的。Cocossetget

在 中,有:native/tools/swig-config/cocos.i

%attribute_writeonly(cc::ICanvasRenderingContext2D, float, width, setWidth);
%attribute_writeonly(cc::ICanvasRenderingContext2D, float, height, setHeight);
%attribute_writeonly(cc::ICanvasRenderingContext2D, float, lineWidth, setLineWidth);
%attribute_writeonly(cc::ICanvasRenderingContext2D, ccstd::string&, fillStyle, setFillStyle);
%attribute_writeonly(cc::ICanvasRenderingContext2D, ccstd::string&, font, setFont);

这是 JS 中的类似功能:

Object.defineProperty(MyNewClass.prototype, 'width', {
  configurable: true,
  enumerable: true,
  set(v) {
    this._width = v;
  },
    // No get() for property
});

引用类型

如果C++函数返回引用数据类型或函数访问引用数据类型,请不要忘记在 %attribute 或 %attribute_writeonly 指令中添加后缀。下面是一个示例。getset&ccstd::string&

%attribute_writeonly(cc::ICanvasRenderingContext2D, ccstd::string&, fillStyle, setFillStyle);

如果缺少,则在调用绑定函数时将创建一个临时实例。&ccstd::string

%arg() 指令

有时,C++变量的类型是由C++模板描述的,例如:

class MyNewClass {
  public:
        const std::map<std::string, std::string>& getConfig() const { return _config; }
      void setConfig(const std::map<std::string, std::string> &config) { _config = config; }
  private:
      std::map<std::string, std::string> _config;
};

我们可以编写一个像这样的文件:%attribute.i

%attribute(MyNewClass, std::map<std::string, std::string>&, config, getConfig, setConfig);

调用 时会出现错误。node genbindings.js

Error: Macro '%attribute_custom' expects 7 arguments

这是因为不知道如何处理逗号()中的,它会将其拆分为两部分:swig,std::map<std::string, std::string>&

  1. 标准::地图<标准::字符串
  2. std::string>&

因此,%attribute 指令的这一行将使用 6 个参数而不是 5 个参数进行分析。

为了避免混淆,我们需要使用指令来告诉这是一个完整的声明。swig%argswigstd::map<std::string, std::string>&

%attribute(MyNewClass, %arg(std::map<std::string, std::string>&), config, getConfig, setConfig);

重新运行,将不再报告错误。node genbindings.js

不添加const

在上面的示例中,用作 %attribue 指令中的C++数据类型。您可以考虑在 like 之前添加前缀。如果这样做,您将创建一个仅绑定 .这显然不是我们所期望的。如果我们需要一个只读属性,就不要分配函数。%arg(std::map<std::string, std::string>&)conststd::map%arg(const std::map<std::string, std::string>&)configMyNewClass::getConfigset

// Don't assign setConfig means the property doesn't need a setter.
%attribute(MyNewClass, %arg(std::map<std::string, std::string>&), config, getConfig);

因此,为了简单起见,切勿在编写指令时添加前缀。const%attribute

在 .i 文件中配置 C++ 模块

有时,是否编译类取决于是否启用了宏。例如,我们在MyFeatureObjectMyObject.h

// MyObject.h
#pragma once
#include "cocos/cocos.h"
#include "MyRef.h"

#ifndef USE_MY_FEATURE
#define USE_MY_FEATURE 1 // Enable USE_MY_FEATURE
#endif

namespace my_ns {

#if USE_MY_FEATURE
class MyFeatureObject {
public:
    void foo() {
        CC_LOG_DEBUG("==> MyFeatureObject::foo");
    }
};
#else
class MyFeatureObject;
#endif

// MyObject inherits from MyRef
class MyObject : public MyRef {
public:
//......
    MyFeatureObject* getFeatureObject() {
#if USE_MY_FEATURE // getFeatureObject only returns valid value when USE_MY_FEATURE is enabled
        if (_featureObject == nullptr) {
            _featureObject = new MyFeatureObject();
        }
#endif
        return _featureObject;
    }
private:
    int _a{100};
    bool _b{true};
    int _type{333};
    MyFeatureObject* _featureObject{nullptr}; // Add this line
};
} // namespace my_ns {
// my-module.i
// ......
%rename(MyCoolObject) my_ns::MyObject;

%attribute(my_ns::MyObject, int, type, getType, setType);

%module_macro(USE_MY_FEATURE) my_ns::MyFeatureObject; // Add this line to let Swig know the generated code for MyFeatureObject needs to be wrapped by USE_MY_FEATURE macro
%module_macro(USE_MY_FEATURE) my_ns::MyObject::getFeatureObject; // Add this line to let Swig know the generated code for MyObject::getFeatureObject should be wrapped by USE_MY_FEATURE macro

#define USE_MY_FEATURE 1 // Must be 1 to trick Swig that we need to generate binding code
// even this macro is disabled in C++. NOTE: this line should be after %module_macro

%import "MyRef.h"
%include "MyObject.h"
// my-module.d.ts
declare namespace my_ns {
class MyFeatureObject {
    foo() : void;
}

class MyCoolObject {
    constructor();
    constructor(a: number, b: number);

    publicFloatProperty : number;
    print() : void;
    coolProperty: number;
    coolMethod() : void;
    type: number;
    getFeatureObject() : MyFeatureObject;
}
}
// MyComponent.ts
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('MyComponent')
export class MyComponent extends Component {
    start() {
        const myObj = new my_ns.MyCoolObject();
        myObj.print();
        console.log(`==> myObj.publicFloatProperty: ${myObj.publicFloatProperty}`);
        console.log(`==> old: myObj.coolProperty: ${myObj.coolProperty}`);
        myObj.coolProperty = 666;
        console.log(`==> new: myObj.coolProperty: ${myObj.coolProperty}`);
        myObj.coolMethod();
        console.log(`==> old: myObj.type: ${myObj.type}`);
        myObj.type = 888;
        console.log(`==> new: myObj.type: ${myObj.type}`);
        const featureObj = myObj.getFeatureObject();
        console.log(`==> featureObj: ${featureObj}`);
        if (featureObj) {
            featureObj.foo();
        }
    }
}

生成绑定后,绑定代码如下:

#if USE_MY_FEATURE // NOTE THAT, all binding code of MyFeatureObject is wrapped by USE_MY_FEATURE macro

se::Class* __jsb_my_ns_MyFeatureObject_class = nullptr;
se::Object* __jsb_my_ns_MyFeatureObject_proto = nullptr;
SE_DECLARE_FINALIZE_FUNC(js_delete_my_ns_MyFeatureObject) 

static bool js_my_ns_MyFeatureObject_foo(se::State& s)
{
// ......
}
// ......
bool js_register_my_ns_MyFeatureObject(se::Object* obj) {
    auto* cls = se::Class::create("MyFeatureObject", obj, nullptr, _SE(js_new_my_ns_MyFeatureObject)); 
// ......
}

#endif // USE_MY_FEATURE

// ......
static bool js_my_ns_MyCoolObject_getFeatureObject(se::State& s)
{
#if USE_MY_FEATURE // getFeatureObject function is also wrapped by USE_MY_FEATURE
// ......
    ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/);
    SE_PRECONDITION2(ok, false, "MyCoolObject_getFeatureObject, Error processing arguments");
    SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); 
#endif // USE_MY_FEATURE
    return true;
}
SE_BIND_FUNC(js_my_ns_MyCoolObject_getFeatureObject) 

// ......
bool register_all_my_module(se::Object* obj) {
    // Get the ns
    se::Value nsVal;
    if (!obj->getProperty("my_ns", &nsVal, true))
    {
        se::HandleObject jsobj(se::Object::createPlainObject());
        nsVal.setObject(jsobj);
        obj->setProperty("my_ns", nsVal);
    }
    se::Object* ns = nsVal.toObject();
    /* Register classes */
#if USE_MY_FEATURE
    js_register_my_ns_MyFeatureObject(ns); // js_register_my_ns_MyFeatureObject is wrapped by USE_MY_FEATURE
#endif // USE_MY_FEATURE
    js_register_my_ns_MyObject(ns); 
    return true;
}

生成并运行项目,输出如下:

18:32:20 [DEBUG]: D/ JS: ==> featureObj: [object Object] // featureObj is valid if USE_MY_FEATURE macro is enabled
18:32:20 [DEBUG]: ==> MyFeatureObject::foo // Invoke C++ foo method

当我们不需要时,将宏分配给 0,代码如下:MyFeatureObject

// MyObject.h
#pragma once
#include "cocos/cocos.h"
#include "MyRef.h"

#ifndef USE_MY_FEATURE
#define USE_MY_FEATURE 0 // Disable USE_MY_FEATURE
#endif

生成并运行项目,可以看到以下输出。

18:54:00 [DEBUG]: D/ JS: ==> featureObj: undefined // getFeatureObject returns undefined if USE_MY_FEATURE is disabled.

多摇摆模块配置

让我们创建另一个头文件。MyAnotherObject.h

// MyAnotherObject.h
#pragma once
namespace my_another_ns {
struct MyAnotherObject {
    float a{135.246};
    int b{999};
};
} // namespace my_another_ns {

更新:MyObject.h

// MyObject.h
//......
class MyObject : public MyRef {
public:
// ......
    void helloWithAnotherObject(const my_another_ns::MyAnotherObject &obj) {
        CC_LOG_DEBUG("==> helloWithAnotherObject, a: %f, b: %d", obj.a, obj.b);
    }
// ......
};
} // namespace my_ns {

创造/Users/james/NewProject/tools/swig-config/another-module.i

// another-module.i
%module(target_namespace="another_ns") another_module

// Insert code at the beginning of generated header file (.h)
%insert(header_file) %{
#pragma once
#include "bindings/jswrapper/SeApi.h"
#include "bindings/manual/jsb_conversions.h"

#include "MyAnotherObject.h" // Add this line
%}

// Insert code at the beginning of generated source file (.cpp)
%{
#include "bindings/auto/jsb_another_module_auto.h"
%}

%include "MyAnotherObject.h"

修改/Users/james/NewProject/tools/swig-config/swig-config.js

'use strict';

const path = require('path');

const configList = [
    [ 'my-module.i', 'jsb_my_module_auto.cpp' ],
    [ 'another-module.i', 'jsb_another_module_auto.cpp' ], // Add this line
];

const projectRoot = path.resolve(path.join(__dirname, '..', '..'));
const interfacesDir = path.join(projectRoot, 'tools', 'swig-config');
const bindingsOutDir = path.join(projectRoot, 'native', 'engine', 'common', 'bindings', 'auto');
const includeDirs = [
    path.join(projectRoot, 'native', 'engine', 'common', 'Classes'),
];

module.exports = {
    interfacesDir,
    bindingsOutDir,
    includeDirs,
    configList
};

修改/Users/james/NewProject/native/engine/common/CMakeLists.txt

# /Users/james/NewProject/native/engine/common/CMakeLists.txt
list(APPEND CC_COMMON_SOURCES
    ${CMAKE_CURRENT_LIST_DIR}/Classes/Game.h
    ${CMAKE_CURRENT_LIST_DIR}/Classes/Game.cpp
    ${CMAKE_CURRENT_LIST_DIR}/Classes/MyObject.h
    ${CMAKE_CURRENT_LIST_DIR}/Classes/MyAnotherObject.h # Add this line
    ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_my_module_auto.h
    ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_my_module_auto.cpp
    ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_another_module_auto.h # Add this line
    ${CMAKE_CURRENT_LIST_DIR}/bindings/auto/jsb_another_module_auto.cpp # Add this line
)

再次生成绑定。

更新:Game.cpp

#include "Game.h"
#include "bindings/auto/jsb_my_module_auto.h"
#include "bindings/auto/jsb_another_module_auto.h" // Add this line
//......

int Game::init() {
//......
    se::ScriptEngine::getInstance()->addRegisterCallback(register_all_my_module);
    se::ScriptEngine::getInstance()->addRegisterCallback(register_all_another_module); // Add this line
//
  BaseGame::init();
  return 0;
}

生成和编译,但将报告以下错误:

另一个模块编译错误

由于MyObject类依赖于在另一个模块上定义的MyOtherObject。我们需要更新和添加.my-module.i#include "bindings/auto/jsb_another_module_auto.h"

// my-module.i
%module(target_namespace="my_ns") my_module

// Insert code at the beginning of generated header file (.h)
%insert(header_file) %{
#pragma once
#include "bindings/jswrapper/SeApi.h"
#include "bindings/manual/jsb_conversions.h"

#include "MyObject.h"
%}

// Insert code at the beginning of generated source file (.cpp)
%{
#include "bindings/auto/jsb_my_module_auto.h"
#include "bindings/auto/jsb_another_module_auto.h" // Add this line
%}

// ......

编译项目。它现在应该汇编成功。

接下来,我们更新 .d.ts

// my-module.d.ts
declare namespace my_ns {
class MyFeatureObject {
    foo() : void;
}

class MyCoolObject {
    constructor();
    constructor(a: number, b: number);

    publicFloatProperty : number;
    print() : void;
    coolProperty: number;
    coolMethod() : void;
    type: number;
    getFeatureObject() : MyFeatureObject;
    helloWithAnotherObject(obj: another_ns.MyAnotherObject) : void; // Add this line
}
}

// Add the following lines
declare namespace another_ns {
class MyAnotherObject {
    a: number;
    b: number;
}
}

我们添加了更多读取属性的测试代码MyAnotherObject.

// MyComponent.ts
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('MyComponent')
export class MyComponent extends Component {
    start() {
        const myObj = new my_ns.MyCoolObject();
        // ......
        const anotherObj = new another_ns.MyAnotherObject(); // Add this line 
        myObj.helloWithAnotherObject(anotherObj); // Add this line
    }
}

生成并运行项目,应看到以下输出。

3D建模学习工作室 翻译整理,转载请注明出处

上一篇:Cocos Creator:介绍 (mvrlink.com)

下一篇:Cocos Creator:使用 JavaScript 调用 Java 方法的更简单方法 (mvrlink.com)

NSDT场景编辑器 | NSDT 数字孪生 | GLTF在线编辑器 | 3D模型在线转换 | UnrealSynth虚幻合成数据生成器 | 3D模型自动纹理化工具
2023 power by nsdt©鄂ICP备2023000829号