通过脚本添加行为
脚本为 Web Components 应用中的实体添加自定义行为。
先看一个随时间旋转实体的简单脚本:
import { Script } from 'playcanvas';
export class RotateScript extends Script {
static scriptName = 'rotateScript';
update(dt) {
// Rotate the entity 90 degrees per second around the world-space Y axis
this.entity.rotate(0, dt * 90, 0);
}
}
加载脚本
通过 <pc-asset> 加载脚本,像其他资源一样直接放在 pc-app 内:
<pc-asset src="path/to/rotate-script.mjs"></pc-asset>
文件使用 .mjs 扩展名,让 pc-asset 识别脚本,并让引擎按 ES 模块导入。其他脚本文件会按经典脚本执行,遇到 import 会失败。
然后通过 <pc-script> 和 <pc-script-instance> 挂到实体:
<pc-entity name="spinning cube">
<pc-render type="box"></pc-render>
<pc-script>
<pc-script-instance name="rotateScript"></pc-script-instance>
</pc-script>
</pc-entity>
name 必须与脚本类的注册名一致,即示例的 scriptName,或 registerScript() 指定的名称。
不必提前注册。pc-script-instance 会等待类从资源或自己的 registerScript() 到达,随后立即实例化。所有脚本资源加载结束后仍没找到类,会输出警告,通常是缺少资源声明或名称不匹配。
registerScript() 面向运行中的应用,因此先 await whenReady('pc-app'),或将应用作为第三参数传入。
通过属性传入脚本数据
当前旋转速度写死为每秒 90 度。如果多个实体需要不同速度,就可以使用脚本属性。
先修改脚本,将速度声明为属性:
import { Script } from 'playcanvas';
export class RotateScript extends Script {
static scriptName = 'rotateScript';
/**
* The speed of the rotation in degrees per second
* @attribute
*/
speed = 90;
update(dt) {
// Rotate the entity `speed` degrees per second around the world-space Y axis
this.entity.rotate(0, dt * this.speed, 0);
}
}
现在只需为 pc-script-instance 添加 speed:
<pc-entity name="fast spinning cube">
<pc-render type="box"></pc-render>
<pc-script>
<pc-script-instance name="rotateScript" speed="180"></pc-script-instance>
</pc-script>
</pc-entity>
以下脚本作用于两个立方体,一个使用声明的默认速度,一个每秒 180 度,脚本由本站提供:
<pc-app>
<pc-asset src="https://developer.playcanvas.com/assets/scripts/rotate-script.mjs"></pc-asset>
<pc-scene>
<pc-entity name="camera" position="0 1 4" rotation="-10 0 0">
<pc-camera clear-color="#1d1f2b"></pc-camera>
</pc-entity>
<pc-entity name="light" rotation="45 30 0">
<pc-light></pc-light>
</pc-entity>
<pc-entity name="spinning cube" position="-1 0 0">
<pc-render type="box"></pc-render>
<pc-script>
<pc-script-instance name="rotateScript"></pc-script-instance>
</pc-script>
</pc-entity>
<pc-entity name="fast spinning cube" position="1 0 0">
<pc-render type="box"></pc-render>
<pc-script>
<pc-script-instance name="rotateScript" speed="180"></pc-script-instance>
</pc-script>
</pc-entity>
</pc-scene>
</pc-app>
可以尝试:
- 为第一个也设置 speed,或使用负值反向旋转。
- 添加
enabled="false",停止对应立方体。 - 拼错 name,查看控制台对未到达类的警告。
非保留属性会映射到同名脚本属性。保留项包括元素自身 name、enabled、attributes,HTML 全局属性如 id、class、style,data-、aria-、以 _ 开头的框架内部属性,以及 onclick 等真实内联事件名;one-shot 这类仅以 on 开头的脚本名仍可映射。kebab-case 对应脚本 camelCase,例如 focus-point → focusPoint。与 app、entity、destroy、initialize、postInitialize、postUpdate、swap、update 或其他脚本方法冲突时,不会写入并会警告。
按脚本默认值的类型解析,遵循通用属性规则:
| 脚本属性类型 | 标记示例 |
|---|---|
| Number(数字) | speed="180" |
| Boolean(布尔) | enable-fly="false" |
| String(字符串) | label="你好" |
| Vec2 / Vec3 / Vec4 | focus-point="0 1.75 0" |
| Color(颜色) | tint="#ff0000" 或 tint="1 0 0" |
| Quat(四元数) | orientation="0 90 0",使用角度制欧拉角 |
脚本属性没有引擎默认值。无效或移除时保留当前值,除非 attributes JSON 也定义了该项。默认值为 null 等无法推断类型时,按原字符串赋值并警告,除非使用类型前缀。
例如,引擎内置现成脚本 cameraControls 可完全通过逐属性方式配置,此处从 CDN 加载:
<pc-asset src="https://cdn.jsdelivr.net/npm/playcanvas@latest/scripts/esm/camera-controls.mjs"></pc-asset>
<!-- ... -->
<pc-entity name="camera">
<pc-camera></pc-camera>
<pc-script>
<pc-script-instance name="cameraControls"
enable-fly="false"
focus-point="0 1.75 0"
zoom-range="2 15"></pc-script-instance>
</pc-script>
</pc-entity>
属性名拼写错误会警告,还会在误用 focusPoint 等 camelCase 时建议 focus-point。编写时应保持控制台打开。
类型前缀
数字、布尔、向量和颜色从默认值推断。引用资源、实体或默认为 null 等情况,可以显式添加类型前缀:
| 前缀 | 示例 | 说明 |
|---|---|---|
asset: | asset:arial-font | 通过 id 引用 pc-asset |
entity: | entity:player | 通过实体 name 引用 pc-entity、pc-model 或 pc-node,或使用文档级 # 选择器,例如 entity:#player |
vec2: | vec2:10 20 | 两个空格分隔数字构成 Vec2 |
vec3: | vec3:10 20 30 | 三个空格分隔数字构成 Vec3 |
vec4: | vec4:10 20 30 40 | 四个空格分隔数字构成 Vec4 |
color: | color:1 0.5 0.5 | 三个 RGB 或四个 RGBA 数字构成 Color,范围为 0 到 1 |
<pc-script-instance name="myScript" font="asset:arial-font" target="entity:#player"></pc-script-instance>
数字和布尔没有前缀,默认 null 时应通过 attributes JSON 的原生类型传入。当前值是字符串时不解析前缀,因此 color:red 这样的标签仍为文本。
entity: 遵循实体引用规则:裸值是名称,从最近实体向外查找,最后查文档,不是 id;# 开头才是全局选择器。entity:body 指名称,entity:#body 指 id。模板克隆中名称先在自身实例内解析,使同一模板可创建独立实例。
attributes JSON 属性
逐属性适合平坦且名称简单的配置,其他情况可通过 attributes JSON 对象:
- 嵌套结构:逐属性无法表达的数组和对象。
- 保留名称:与元素 API 或 HTML 属性冲突的脚本名,例如 title、name、id、style。但 enabled 始终归元素,用于开关脚本。
<pc-script-instance name="annotation" attributes='{
"label": "1",
"title": "Cockpit Canopy",
"text": "Transparent canopy offering visibility and housing the pilot controls."
}'></pc-script-instance>
attributes 接受 JSON 字符串。JSON 内部键使用双引号,因此 HTML 属性外层应使用单引号。
JSON 数字数组自动转成声明的 Vec2、Vec3、Vec4 或 Color;Quat 则将三个数字视为角度制欧拉角。长度错误时警告并保留当前值:
<pc-script-instance name="cameraControls" attributes='{
"focusPoint": [0, 1.75, 0],
"pitchRange": [-90, 0]
}'></pc-script-instance>
对象会与声明默认值合并,而非整体替换。例如默认 {a: 1, b: 2},设置 {"a": 5} 后为 {a: 5, b: 2},只需指定要改的项。
类型前缀也适用于 JSON 内任意嵌套字符串。因此必须保持文本的字符串不能以 asset:、entity:、vec2:、vec3:、vec4: 或 color: 开头:
<pc-script-instance name="xrMenu" attributes='{
"menuItems": [{"label": "Exit XR", "eventName": "xr:end"}],
"fontAsset": "asset:arial-font"
}'></pc-script-instance>
JSON 的键不会像逐属性一样对照脚本检查,拼错会直接成为新属性。
优先级
同一属性同时出现在逐属性和 JSON 中时,始终以逐属性为准,包括创建和运行时更新。移除逐属性后,若 JSON 有对应值则恢复它,否则保留当前值。
如何在脚本本身声明属性,详见引擎脚本属性。
从 JavaScript 访问脚本
实例创建后,pc-script-instance 才就绪。通过 whenReady() 或 ready() 等待,再从 script 获取活动 Script 对象,完整 API 详见代码访问:
import { whenReady } from '@playcanvas/web-components';
const scriptElement = await whenReady('pc-script-instance');
scriptElement.script.speed = 360;
也可将对象赋给 scriptAttributes,无需 JSON 字符串。它与 attributes 属于相同通道,逐属性仍优先:
scriptElement.scriptAttributes = { speed: 180 };
转换规则与 attributes 相同,解析前缀并将数字数组转为声明数学类型。读取 scriptAttributes 返回最近赋入或从 JSON 解析的对象,不是当前运行值;运行值应从 script 读取。
使用引擎现成脚本
动手编写前,可以先检查引擎是否已有对应功能。内置脚本库位于 GitHub,并广泛用于 Web Components 示例。