babylonjs 实现软体的方式是通过其内置的 ammojs 插件调用 ammo 物理引擎
scene.enablePhysics(null, new BABYLON.AmmoJSPlugin());
这里的 AmmoJSPlugin 其实是一个提供给 babylonjs 的接口,并不包含 ammo 自身,所以还需要自行准备 ammojs
import Ammo from 'ammojs-typed'
这里使用带有类型定义的 ammojs-typed
然后使用其推荐的初始化方法:
Ammo()
.then(() => {
console.log('Ammo.js initialized successfully.');
})
.catch((err) => {
console.error('Failed to initialize Ammo.js:', err);
});
结果得到了错误Uncaught TypeError: Cannot set properties of undefined (setting 'Ammo')
需要注意的是这个错误并没有被 catch 正常捕获,错误直接发生在Ammo()
中
它貌似是在设置全局变量时出错的
在 babylonjs 的官方示例代码中,并没有提到关于 ammo 的导入或者初始化
我猜测 ammo 是直接通过 script 导入的,直接就提供了一个可用的全局 Ammo
在 babylonjs 的启用物理代码中
scene.enablePhysics(null, new BABYLON.AmmoJSPlugin());
第一个参数是重力,不指定时使用默认的地球重力,第二个参数指定插件
而不传入参数给插件调用函数时使用默认的全局 Ammo
根据 devtool 的提示可以直接查看到错误来源于 ammojs-typed 的一行
this.Ammo = b;
其实已经可以猜到 Ammo 在初始化时尝试将导出的 api 赋值给全局对象,而当前的 ES 模块的顶层 this 是 undefined
ammojs 是一个 emscripten 编译的 bullet 物理引擎,emscripten 生成的胶水 js 代码在传统上会将导出赋值给全局对象,类似this.Ammo = b;
或者window.Ammo = b;
但是在 es 模块中因为封装性和防污染,其顶层作用域是 undefined
所以最后的解决方法来自
https://forum.babylonjs.com/t/using-ammojs-with-babylon/26413/18?u=lz_k
const ammo = await Ammo.call({});
scene.enablePhysics(null, new AmmoJSPlugin(true, ammo));
使用.call
手动指定函数内部的 this 为新传入的参数,这里指定为一个空对象
使用一个临时的空对象并不会影响什么,我们只需要让初始化过程不出错就可以了,因为Ammo()
实际返回的 Promise 中也会包含可用的 Ammo api
然后将初始化得到的 ammo 实例传给 babylon 的 ammojs 插件就可以正常启用 babylon 的 ammojs 插件了
这个小技巧成功绕过了 ammojs 尝试对全局变量的赋值问题
接下来可以正常进行创建软体等操作了👻