脚本迁移指南
ESM 脚本已取代经典脚本,成为推荐的 PlayCanvas 应用开发方式。经典脚本仍可在现有项目中工作,并将在可预见的未来继续受支持,但新开发建议采用 ESM 格式。
逐步迁移
在项目中使用 ESM 脚本完全可选。可以按自己的进度逐步迁移到新的 ESM 格式,不影响现有项目。
同一项目可以同时包含 ESM 脚本和经典脚本。
不需要一次更新全部脚本,建议逐步迁移,并在每次修改后测试。
Codemod
我们提供了将经典脚本转换为 ESM 的 codemod,自动更新代码。
可在 GitHub 仓库中找到转换工具,通过以下命令运行:
npx codemod playcanvas-esm-scripts
对于编辑器项目,可让编程助手协调代码转换,以及源码转换无法更新的元数据。审查每批 **Pull/Push 模式同步结果,然后通过编辑器 MCP 服务器**重新解析脚本、标记受影响的关联,并在 Launcher 中测试。
已知差异
ESM 脚本提供更强的表达能力和灵活性。虽然我们尽量让迁移过程平滑,仍有一些差异需要注意。
模块作用域
**ESM 脚本使用模块作用域,经典脚本使用全局作用域。**模块不能隐式访问其他文件定义的变量。经典脚本常用全局变量保存设置;例如先加载配置,再加载脚本,使 SPEED 可以全局访问。
See code example
// config.js
var SPEED = 10;
// script.js
// ❌ This will not work. `SPEED` is scoped to config.js
console.log(SPEED)
这是一种隐藏依赖,加载顺序改变后就会失效。应改用 import/export 显式定义依赖。
// config.mjs
export const SPEED = 10
// script.mjs
import { SPEED } from './config.mjs';
// ✅ Works!
console.log(SPEED);
有关 ES 模块与普通脚本的其他区别,请查阅相关文档。
加载顺序
ESM 脚本没有脚本加载顺序。
经典脚本通过加载顺序组织依赖,保证某些代码先执行。ES 模块使用 import/export 显式表达依赖,因此不应依赖某种隐含加载顺序,而应通过导入和导出建立依赖关系。
新的 Script 类
ESM 脚本使用新的 Script 基类,代替原来的 ScriptType。Script 包含必要的最小功能,但省略了 ScriptType 中的少数功能。
虽然默认基类已变为 Script,仍可继承 ScriptType,因为后者内部继承 Script;但由于下述原因,不建议在 ESM 脚本中这样做。
属性事件
ESM 脚本不会发出属性事件。
Script 不支持 attr:[name] 格式的属性事件。旧机制会在引擎内部覆盖类成员,与 ES6 类语法不完全兼容,容易引入难以排查的问题,因此被移除。
可以围绕类成员定义自己的事件,例如:
See code example
const watch = (target, prop) => {
const privateProp = `#{prop}`;
target[privateProp] = target[prop];
Object.defineProperty(target, prop, {
set(value) {
if (target[privateProp] !== value) {
target.fire(`changed:${prop}`, value);
target[privateProp] = value;
}
},
get() {
return this[privateProp];
}
});
}
import { Script } from 'playcanvas'
export class Rotate extends Script {
static scriptName = 'rotate';
/** attribute */
speed = 10;
initialize() {
watch(this, 'speed');
this.on('changed:speed', console.log)
}
}
这样也能为任意类成员设置事件,而不仅限于脚本属性。
属性复制
ESM 脚本属性不再复制,而是按引用传递。
**属性不再复制,而是按引用传递。**这个变化同样源于 ScriptType 与 ES6 类不兼容的问题。
如果确实需要复制值,建议通过 getter 和 setter 手动、显式完成。虽然代码稍多,但行为清晰明确。
See code example
import { Script, Vec3 } from 'playcanvas';
export class Rotate extends Script {
static scriptName = 'rotate';
_speed = new Vec3();
set speed(value) {
this._speed.copy(value)
}
get speed() {
return this._speed;
}
}