最近在使用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等
先说一下具体流程
- cpp 的工程 ------> emscripten编译 --------> js + wasm 文件。
- 微信小游戏调用 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 禁止动态代码。
步骤概述
-
编译 RVO2 库为 WASM:
使用 Emscripten 编译 RVO2 库的 C++ 代码为 WASM,生成rvo2.js和rvo2.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 -
拷贝生成的文件到项目目录:
将rvo2.js和rvo2.wasm文件拷贝到 Cocos Creator 项目的assets/scripts/3rd/rvo2/目录下。 -
修改
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; } -
创建 TypeScript 声明文件:
在assets/scripts/3rd/rvo2/目录下创建rvo2.d.ts文件,提供模块的 TypeScript 声明。declare module 'assets/scripts/3rd/rvo2/rvo2' { const RVO2Module: () => Promise<any>; export default RVO2Module; } -
在
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去掉新增的这部分代码,编译运行是没问题的。
搞定了吗?翻遍论坛,都是文档写不全,每一步具体的命令参数不提供,瀑布汗。
你贴出来的错误,看上去像是语法错误导致认不出这个脚本,而不是加载wasm出现的问题。
建议你先用一个假的 rvo2.js (原来的js删除掉有效内容只保留导出框架)试一下你的导入有没有问题。
在导入没问题之后,就用分治法(注释掉一半代码,没错再加1/4,有错再减1/4)定位一下具体是哪里出错。
1. 生成.js和.wasm文件
首先,使用以下命令来生成rvo2.js和rvo2.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.js和rvo2.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.js 和 rvo2.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;
}
}
单独弄了一贴,感谢帮助!