跳到主要内容

脚本迁移指南

ESM 脚本已取代经典脚本,成为推荐的 PlayCanvas 应用开发方式。经典脚本仍可在现有项目中工作,并将在可预见的未来继续受支持,但新开发建议采用 ESM 格式。

逐步迁移​

在项目中使用 ESM 脚本完全可选。可以按自己的进度逐步迁移到新的 ESM 格式,不影响现有项目。

提示

同一项目可以同时包含 ESM 脚本和经典脚本。

不需要一次更新全部脚本,建议逐步迁移,并在每次修改后测试。

Codemod​

我们提供了将经典脚本转换为 ESM 的 codemod,自动更新代码。

可在 GitHub 仓库中找到转换工具,通过以下命令运行:

npx codemod playcanvas-esm-scripts
AI

对于编辑器项目,可让编程助手协调代码转换,以及源码转换无法更新的元数据。审查每批 **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;
}
}