请教如何在开发微信小游戏的时候使用c++

最近在使用creator3.8.2进行学习,目的是为了搭建一个制作微信小游戏的框架平台。通过官方文档,了解了在原生平台可以使用swig进行c++层到ts层的导出,可以方便的时候ts调用c++的代码,但是这个方法,对开发小游戏貌似不能使用。
我了解到WebAssembly技术,可以把c++编译成wasm格式的文件提供给js使用,这个方式能否应用到小游戏的开发中?

可以的,小游戏wasm比较正常的,可行性是早就验证过了。不过编译和使用要看一大堆文档,具体就要你自己去看了。

留个链接吧
https://emscripten.org/docs/introducing_emscripten/about_emscripten.html
https://developers.weixin.qq.com/minigame/dev/guide/performance/perf-webassembly.html

wasm是可行的,需要注意wasm对应的js文件不能有动态生成代码

可以的,wasm可以用c++ 或者rust

能不能提供个示例地址,具体应该怎么结合在一起,我现在没有头绪

随便找一个wasm库(包含js和wasm文件),然后参考引擎里面的Spine的加载流程

wasm是支持的,但是微信小游戏端屏蔽了动态生成代码的api如new Function等

先说一下具体流程

  1. cpp 的工程 ------> emscripten编译 --------> js + wasm 文件。
  2. 微信小游戏调用 WXWebAssembly 的接口,加载其中的wasm文件。

找示例应该从这2个步骤分开来找。
第一个步骤,建议用cmake把cpp的工程组织好,利用emcmake命令行来编译成wasm文件。你可以参考basis_universal 里面的webgl transcoder的emcmake编译方式。
第二个步骤, WXWebAssembly 和 WebAssembly的接口很像,你可以参考WebAssembly的接口,以及微信官方的用 WXWebAssembly 加载wasm并使用的例子。或者刚才楼上说的spine的加载例子。

需要注意的是,步骤1会产出一个js,这个js是包装wasm的loader,在web端可以使用这个js直接来初始化wasm,但是小游戏不能直接使用,要换api以及禁止该js中的动态执行代码。可以改好loader来使用。
禁止生成的js里面的动态执行代码,使用这个emscripten链接标志 -s NO_DYNAMIC_EXECUTION=1。
换API,就使用WXWebAssembly 代替WebAssembly 就可以了。

不过这个loader不太好改,适配的地方比较多,建议也可以不用管这个loader js,直接加载wasm使用也可以。
加一个改loader的示例,要不少地方改。
https://gsj987.github.io/posts/webassembly-in-wechat/

另外还有一种方式,性能会稍微差点,但是兼容性会好点,就是不编译成wasm,而是编译成js的bytecode,直接就编译成一个js文件,我用这种方式嵌入了几个库,性能不高但是兼容性还可以,也方便直接放到js的工程中。
这种编译js的bytecode的方式, emscripten 用的compile_flags是 -sWASM=0 ,link_flags 是–memory-init-file 0,直接编出一个js文件引用。也要加 -s NO_DYNAMIC_EXECUTION=1 禁止动态代码。

5赞

步骤概述

  1. 编译 RVO2 库为 WASM
    使用 Emscripten 编译 RVO2 库的 C++ 代码为 WASM,生成 rvo2.jsrvo2.wasm 文件。

    emcc Agent.cpp KdTree.cpp Obstacle.cpp RVOSimulator.cpp binds.cpp -I. -o rvo2.js -s WASM=1 -s EXPORTED_RUNTIME_METHODS="['ccall', 'cwrap']" --bind -s MODULARIZE=1 -s EXPORT_ES6=1
    
  2. 拷贝生成的文件到项目目录
    rvo2.jsrvo2.wasm 文件拷贝到 Cocos Creator 项目的 assets/scripts/3rd/rvo2/ 目录下。

  3. 修改 rvo2.js 中的 scriptDirectory
    rvo2.js 文件中修改 scriptDirectory 变量和 locateFile 函数,以确保 WASM 文件能够被正确加载。

    var scriptDirectory = '../../scripts/3rd/rvo2/';
    function locateFile(path) {
      if (Module['locateFile']) {
        return Module['locateFile'](path, scriptDirectory);
      }
      return scriptDirectory + path;
    }
    
  4. 创建 TypeScript 声明文件
    assets/scripts/3rd/rvo2/ 目录下创建 rvo2.d.ts 文件,提供模块的 TypeScript 声明。

    declare module 'assets/scripts/3rd/rvo2/rvo2' {
        const RVO2Module: () => Promise<any>;
        export default RVO2Module;
    }
    
  5. Splash.ts 中使用 RVO2 模块
    Splash.ts 文件中导入并使用 RVO2 模块。

    import RVO2Module from 'assets/scripts/3rd/rvo2/rvo2';
    
    RVO2Module().then((instance: any) => {
        console.log("=== rvo2 =========== ", instance);
    });
    

遇到的问题

当尝试在 Cocos Creator 中运行项目时,遇到了一个错误,指出附加到 “Splash” 的脚本 “8bcfek46dtC07oHYPIB8vbb” 缺失或无效。错误信息如下:

Node path: "Splash"
Script UUID: "8bcfe938-e9db-42d3-ba07-60f201f2f6db"
Class ID: "8bcfek46dtC07oHYPIB8vbb"
Error: [Scene] Script "8bcfek46dtC07oHYPIB8vbb" attached to "Splash" is missing or invalid.

我是新手,应该有什么基础问题没搞对。
cocos3.8.1, Splash.ts去掉新增的这部分代码,编译运行是没问题的。

1赞

搞定了吗?翻遍论坛,都是文档写不全,每一步具体的命令参数不提供,瀑布汗。

你贴出来的错误,看上去像是语法错误导致认不出这个脚本,而不是加载wasm出现的问题。
建议你先用一个假的 rvo2.js (原来的js删除掉有效内容只保留导出框架)试一下你的导入有没有问题。
在导入没问题之后,就用分治法(注释掉一半代码,没错再加1/4,有错再减1/4)定位一下具体是哪里出错。

1. 生成.js和.wasm文件

首先,使用以下命令来生成rvo2.jsrvo2.wasm文件。这个命令会将几个C++源文件编译成WebAssembly模块和一个JavaScript加载器。

emcc Agent.cpp KdTree.cpp Obstacle.cpp RVOSimulator.cpp binds.cpp -I. -o rvo2.js -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s DYNAMIC_EXECUTION=0 --bind -s EXPORT_ES6=1 -s MODULARIZE=1 -s EXPORT_NAME='createRvo2Module'

2. 文件准备和重命名

  • 将生成的rvo2.jsrvo2.wasm文件拷贝到assets/scripts/3rd/rvo2目录下。
  • rvo2.js文件更名为rvo2.mjs,以便与引擎的TypeScript配置(es2015)兼容,避免出现commonJS错误。

3. 创建TypeScript声明文件

创建一个rvo2.d.ts文件来声明TypeScript的类型,以便更好地与TypeScript项目集成。

// rvo2.d.ts
export interface ModuleConfigOptions {
    wasmBinary?: Uint8Array;
}

export declare function createRvo2Module(options?: ModuleConfigOptions): Promise<RVO2Module>;

4. 更名.wasm并处理导入

  • rvo2.wasm更名为rvo2.bin,这样Cocos引擎会将其视为buffer类型进行导入,确保在接下来的步骤中asset能被识别为BufferAsset类型。

5. 加载并初始化WebAssembly模块

assets/scripts/3rd/rvo2目录下创建rvo2wasm.ts来加载和初始化WebAssembly模块。

// assets/scripts/3rd/rvo2/rvo2wasm.ts
import { createRvo2Module, RVO2Module } from './rvo2.mjs';
import { assetManager, BufferAsset } from 'cc';

export async function loadWasm(): Promise<RVO2Module> {
    try {
        // 以 Uint8Array 的形式加载 .wasm 文件
        const wasmBinary = await new Promise<Uint8Array>((resolve, reject) => {
            assetManager.loadAny<BufferAsset>('65533cf4-cba5-4e5c-9e46-648df007179a', (error, asset) => {
                if (error) {
                    reject(error);
                } else {
                    console.log('.wasm 文件加载成功', asset.constructor.name);
                    resolve(new Uint8Array(asset.buffer()));
                }
            });
        });

        // 调用 Emscripten 模块函数初始化模块
        const module = await createRvo2Module({
            wasmBinary,
        });

        return module;
    } catch (error) {
        console.error('Failed to load or initialize the wasm module:', error);
        throw error;
    }
}

遇到以下错误,应该是emcc导出的rvo2.mjs有问题,可以帮我检查下命令是否有不正确的参数设置:

[PreviewInEditor] Failed to load or initialize the wasm module: TypeError: (intermediate value)(intermediate value)(intermediate value) is not a function

试了你说的办法,把mjs文件删的仅剩下createRvo2Module导出,也是同样的报错。

那可能是js文件导出的问题了。看看文档是不是有解释的,我记得以前弄这个导入也弄了挺久,后面按文档手工改了生成的js里面的导出形式,具体的有点忘记了。

https://docs.cocos.com/creator/3.8/manual/zh/scripting/modules/spec.html
https://docs.cocos.com/creator/3.8/manual/zh/scripting/modules/example.html

确实如此,重新整理了下流程:

1. 编译 WASM 模块

使用以下命令将 C++ 代码编译为 .js.wasm 文件:

emcc Agent.cpp KdTree.cpp Obstacle.cpp RVOSimulator.cpp binds.cpp -I. -o rvo2.js -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s DYNAMIC_EXECUTION=0 --bind -s MODULARIZE=1 -s EXPORT_NAME='createRvo2Module'

2. 文件部署

将生成的 rvo2.jsrvo2.wasm 文件拷贝到项目的 assets/scripts/3rd/rvo2 目录下。

3. 代码修改

为了兼容旧版本的 Node.js,修改 rvo2.js 文件中的以下代码:

if (numericVersion < 160000)

改为:

if (numericVersion < 140000)

这样可以避免在 Node.js v14.16.0 环境下运行时出现版本兼容性错误。

4. 更改文件扩展名

rvo2.wasm 文件重命名为 rvo2.bin,以便 Cocos 引擎将其作为 buffer 类型导入,确保按照 BufferAsset 类型正确加载。

5. 类型定义文件

创建 rvo2.d.ts 文件,以提供 TypeScript 支持:

export interface ModuleConfigOptions {
    wasmBinary?: Uint8Array;
}

export default function createRvo2Module(options?: ModuleConfigOptions): Promise<RVO2Module>;

export interface RVO2Module {
}

6. 加载 WASM 模块

编写加载代码,确保遵循 Cocos Creator 文档中关于 ESM 与 CJS 交互规则的指导:

// assets/scripts/3rd/rvo2/rvo2wasm.ts
import { default as createRvo2Module, RVO2Module } from './rvo2.js';
import { assetManager, BufferAsset } from 'cc';

export async function loadWasm(): Promise<RVO2Module> {
    try {
        const wasmBinaryData = await new Promise<Uint8Array>((resolve, reject) => {
            assetManager.loadAny<BufferAsset>('788394bd-56d9-41e4-8d30-cbbd992b13a9', (error, asset) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(new Uint8Array(asset.buffer()));
                }
            });
        });

        try {
            const module = await createRvo2Module({ wasmBinary: wasmBinaryData });
            return module;
        } catch (error) {
            console.error('模块加载失败', error);
            throw error;
        }
    } catch (error) {
        console.error('Failed to load or initialize the wasm module:', error);
        throw error;
    }
}

单独弄了一贴,感谢帮助!