通过代码访问
Web Components 允许只用 HTML 构建丰富场景,但也可能需要 JavaScript 访问运行中的应用,例如调整设置、发送事件或响应画布外界面。本页介绍如何在正确时机访问。
元素就绪
<pc-app> 等元素异步初始化,需要加载 WebAssembly、创建图形设备与应用、构建实体层级并预加载资源。页面刚加载就查询时,AppBase 等对象可能尚不存在或未准备好。
最便捷的等待方式是 whenReady。按包名从 @playcanvas/web-components 导入,入门中的 import map 已配置该映射:
<script type="importmap">
{
"imports": {
"playcanvas": "https://cdn.jsdelivr.net/npm/playcanvas@latest/build/playcanvas.mjs",
"@playcanvas/web-components": "https://cdn.jsdelivr.net/npm/@playcanvas/web-components@latest/dist/pwc.min.mjs"
}
}
</script>
该 URL 必须与库的 <script> 完全一致,否则导入会加载第二份库。使用打包器时按普通包导入即可。随后等待所需元素:
<script type="module">
import { whenReady } from '@playcanvas/web-components';
const { app } = await whenReady('pc-app'); // app is the engine's AppBase
app.scene.exposure = 0.5;
</script>
初始化结束后,whenReady 返回元素。多数标签在引擎对象存在时就绪,但 <pc-app> 要等待预加载和应用启动,<pc-model> 等待内容,<pc-asset> 等待加载结果。<pc-sky> 则在加入文档时立即就绪,天空盒可能尚未存在。已经就绪时会立即返回,无需等待 DOMContentLoaded 或监听事件。
三种情况会拒绝 Promise:CSS 选择器无效、找不到匹配元素,或目标不异步初始化。最后一种包括唯一同步标签 <pc-material>、非 pc-* 元素,以及尚未加入文档的模板克隆。选择器只计算一次;文档还在加载时,会等解析完成再计算,因此只能发现当时存在的元素。动态创建的元素应直接传引用,见下文。
无法完成初始化时,Promise 不会结束,例如 <pc-script-instance> 不直接位于 <pc-script> 下、<pc-node> 无法匹配名称、<pc-model> 引用未声明资源、<pc-wasm> 缺少名称或大小写错误,或 <pc-app> 无法创建图形设备,详见其 error 事件。组件标签上方没有实体宿主时是例外:仍会就绪,但 component 为 null。这些情况都会输出控制台说明。
初始化完成前被移除的元素也不会就绪,且不报告错误;除非重新插入,否则 ready() 一直等待,重新插入会延续到下一次初始化。销毁可能与初始化竞争时,例如模板克隆,应让等待与自己的销毁信号竞争,详见移除实例。
获取引擎对象
whenReady 接受标签名、任意 CSS 选择器或直接元素引用,适用于所有异步元素,而非仅 <pc-app>:
const { scene } = await whenReady('pc-scene');
const camera = (await whenReady('pc-camera')).component;
const { entity } = await whenReady('pc-entity[name="player"]');
就绪以元素为单位:实体的 Entity 存在时就绪,但子组件可能尚未初始化。要访问组件,应像上例 pc-camera 一样等待组件本身,而不是等待实体后立即读取。
各元素通过以下属性暴露引擎对象:
| 元素 | 属性 | 引擎类型 |
|---|---|---|
<pc-app> | app | AppBase |
<pc-entity> | entity | Entity |
<pc-model> | entity | 元素创建并代表的宿主 Entity |
<pc-model> | contentEntity | 宿主下实例化层级的根 Entity |
<pc-node> | entity | 在模型层级中绑定的 Entity |
<pc-scene> | scene | Scene |
<pc-script-instance> | script | Script |
<pc-sound-slot> | soundSlot | SoundSlot |
<pc-sprite-clip> | spriteAnimationClip | SpriteAnimationClip |
组件标签(<pc-camera>、<pc-light> 等) | component | 相应的 Component |
这些访问器类型均允许 null:就绪前和销毁后可能返回 null。先等待通常可保证非空,但有两个例外:实体已有同类型组件时,新组件元素会警告且 component 始终为 null;<pc-model> 的 contentEntity 也可能为空,因为就绪包含加载失败或未选择资源。文件可能失败时应检查它,或监听 error;宿主 entity 则在启动后始终非空。
所有异步元素还提供 closestApp 和 closestEntity,分别返回最近的 <pc-app> 祖先,及最近的实体宿主祖先,即 <pc-entity>、<pc-model> 或 <pc-node>;不存在时为 null。持有组件并需获取所属实体或应用时很有用。
模型 contentEntity 是实例化树的根。hierarchy() 返回纯数据快照,包括节点名称、路径、匹配索引及材质分配,供 <pc-node> 和 material-overrides 使用。对结果调用 String() 可打印树,详见检查层级。
选择指定应用
多数页面只有一个 <pc-app>,whenReady('pc-app') 返回文档中的第一个。多个应用时,可用选择器指定:
const left = await whenReady('#left');
const right = await whenReady('#right');
等待已持有的元素
whenReady 也直接接受元素引用,适合刚创建等已持有元素的情况:
const appElement = document.createElement('pc-app');
document.body.appendChild(appElement);
const { app } = await whenReady(appElement);
所有异步元素内部都有返回 Promise 的 ready(),解析值为元素自身;whenReady 只是便捷封装。
模板克隆也采用这一模式:保存克隆元素,只等待实际要访问的元素,详见通过模板复用场景。
ready 事件
除 Promise 外,异步元素在就绪时还会分发 ready,支持冒泡和跨 Shadow DOM 传播,因此一个 document 监听器可观察所有元素:
document.addEventListener('ready', (event) => {
console.log(`${event.target.tagName.toLowerCase()} is ready`);
});
按需求选择:whenReady 等待指定元素,即使它早已就绪也会返回;ready 事件则响应初始化过程,也能观察页面加载后加入的元素,而选择器形式的 whenReady 不能。每轮就绪仅触发一次,应在目标初始化前注册监听。移除再插入、节点随模型重载重新绑定等会开始新周期并再次触发;<pc-wasm> 例外,其就绪状态不会重置。
资源、材质与模块
声明式资源也有 JavaScript 访问方式。<pc-asset> 和 <pc-material> 提供静态查询,将 id 转为引擎对象。两者在应用启动时创建,应先等待 <pc-app>:
import { AssetElement, MaterialElement, whenReady } from '@playcanvas/web-components';
await whenReady('pc-app');
const asset = AssetElement.get('car'); // the engine Asset declared by <pc-asset id="car">
const material = MaterialElement.get('gold'); // the material declared by <pc-material id="gold">
AssetElement.get 返回已注册的 Asset,但可能尚未加载。使用 asset.resource 前检查 asset.loaded 或监听 load。失败也会设置 loaded,但没有 resource。查询 lazy 资源不会启动加载,如无其他加载方,应调用 app.assets.load(asset)。
<pc-wasm> 在模块加载成功或失败后就绪,whenReady('pc-wasm') 可等待;Basis 例外,转码器开始加载时即就绪。就绪不代表成功,失败会触发 error。通常只需等待应用,因为 <pc-app> 在创建图形设备前会等待全部直接子模块完成。应用就绪时,模块已加载或报告失败,因此等待 Ammo 应这样做:
const { app } = await whenReady('pc-app'); // every <pc-wasm> has loaded or failed by now
<pc-wasm> 的就绪状态会保留。WebAssembly 配置引擎全局状态且不会卸载,因此移除再插入既不重置就绪,也不重新加载,详见就绪。
TypeScript
包将所有标签注册到 TypeScript 的 HTMLElementTagNameMap,因此查询有完整类型:document.querySelector('pc-app') 为 AppElement | null,whenReady('pc-camera') 返回 CameraComponentElement。向 whenReady 传入唯一同步标签 pc-material 会产生编译错误。
何时改用脚本
whenReady 适合页面层连接逻辑,例如 DOM 界面交互、设置和事件。需要更新循环、逐实体逻辑等较大或可复用行为时,应编写脚本并通过 <pc-script-instance> 挂载。脚本收到完整初始化实体,无需自行检查就绪,详见通过脚本添加行为。