查找实体
代码经常需要访问并非由它创建的实体,例如玩家、钥匙对应的门或关卡中的所有敌人。每个实体都可以搜索自身及后代,因此既可从 app.root 搜索整个场景,也可从任意下级实体搜索更小范围。
查找方法
| 查找条件 | 首个匹配项 | 所有匹配项 |
|---|---|---|
| 名称 | findByName('door') | find('name', 'door') |
| 路径 | findByPath('house/door') | - |
| 标签 | - | findByTag('enemy') |
| 包含指定组件 | findComponent('light') | findComponents('light') |
| 包含指定脚本 | findScript('health') | findScripts('health') |
| 满足自定义条件 | findOne(entity => ...) | find(entity => ...) |
查找采用深度优先顺序:先访问第一个子实体及其全部后代,再访问第二个子实体。除 findByTag() 只搜索后代外,其余方法都会包含调用方法的实体自身。查找首个匹配项的方法在未找到时返回 null,但 findScript() 返回 undefined;查找全部匹配项的方法则返回空数组。
findComponent() 和 findComponents() 返回组件而非实体,findScript() 和 findScripts() 返回脚本实例。它们都具有 entity 属性,指向所属实体。
按名称查找
const door = app.root.findByName('door');
名称不必唯一,因此 findByName() 返回首先遇到的匹配实体。要获得全部同名实体,使用 find('name', 'door')。若使用唯一名称,findByName() 就是最简单的查找方式。
按路径查找
路径从被搜索实体的下级开始,按从上到下的实体名称排列,以斜杠分隔。即使场景其他位置存在同名实体,也能定位到具体对象:
const door = house.findByPath('ground-floor/door');
实体的 path 属性表示从层级根开始的完整路径。编辑器创建的场景中,所有实体都位于名为 Root 的场景根实体下,因此从 app.root 开始的路径也要以它开头:
const door = app.root.findByPath('Root/house/ground-floor/door');
console.log(door.path); // 'Root/house/ground-floor/door'
按标签查找
标签用于标记实体分组,findByTag() 返回带有指定标签的所有后代。传入多个标签,匹配具有其中任意标签的实体;传入标签数组,则匹配同时具有数组中所有标签的实体:
const enemies = app.root.findByTag('enemy');
// entities tagged 'enemy' OR 'boss'
const hostile = app.root.findByTag('enemy', 'boss');
// entities tagged both 'enemy' AND 'flying'
const flyers = app.root.findByTag(['enemy', 'flying']);
检查单个实体时,entity.tags.has() 使用相同的参数规则,因此哥布林只要具有任一标签,goblin.tags.has('enemy', 'boss') 就为 true。
按组件或脚本查找
// every light in the scene
const lights = app.root.findComponents('light');
lights.forEach(light => light.enabled = false);
// the first instance of the Health script below the player, by class or by name
const health = player.findScript(Health);
使用自定义条件
find() 和 findOne() 接受一个函数,该函数对每个实体执行,对符合要求的实体返回 true:
const coins = level.find(entity => entity.name.startsWith('coin') && entity.enabled);
要遍历子树中的所有实体而不收集结果,使用 forEach():
level.forEach(entity => console.log(entity.path));
各种开发方式中的用法
- 引擎
- 编辑器
- React
- Web Components
对 app.root 调用方法可搜索整个场景,对任意实体调用则搜索其下方内容。在脚本中,this.app.root 是根节点,this.entity 是脚本自身附加的实体:
const wheels = this.entity.findByTag('wheel');
编辑过程中,可在层级面板顶部的搜索框查找实体。点击放大镜选择匹配内容:Name、Component Type、Script Name 或 Tags,与代码查找方法对应。Smart Search 还会匹配与输入接近的名称。

脚本中通常不必搜索。为脚本添加实体属性,然后在 Inspector 中将实体拖入字段。这样即使重命名或移动实体,引用仍然有效,而按名称或路径搜索则可能失效:
import { Entity, Script } from 'playcanvas';
export class Key extends Script {
static scriptName = 'key';
/**
* The door that this key opens.
*
* @attribute
* @type {Entity}
*/
door;
open() {
this.door.enabled = false;
}
}
通常不必搜索,因为实体由你自己渲染。为代码需要访问的实体保留 ref:
const door = useRef(null);
<Entity ref={door} name="door" />
要搜索场景,从应用根节点开始,并在 effect 中执行,确保已渲染的实体存在:
import { useEffect } from 'react';
import { useApp } from '@playcanvas/react/hooks';
const app = useApp();
useEffect(() => {
const enemies = app.root.findByTag('enemy');
}, [app]);
文档结构与实体层级对应,因此可以用 CSS 选择器找到实体元素。元素就绪前,其 entity 为 null,应使用接受同一选择器的 whenReady() 等待:
import { whenReady } from '@playcanvas/web-components';
const door = await whenReady('pc-entity[name="door"]');
console.log(door.entity.getPosition().toString());
DOM 反映的是标记,而非引擎状态。对于通过代码创建的实体,以及运行时添加的标签,应改为搜索引擎:
import { whenReady } from '@playcanvas/web-components';
const { app } = await whenReady('pc-app');
const enemies = app.root.findByTag('enemy');
高效查找
一次查找会访问覆盖范围内的所有实体,因此开销随下方场景规模增加。
- **只查找一次。**在脚本的
initialize中找到所需实体并保留引用,避免每帧查找。 - **缩小范围。**从已知的最近实体开始,比从
app.root开始访问的实体更少。 - **优先使用引用。**实体属性、ref 或保留的元素引用都比搜索更快,也不会因名称变化而失效。
要浏览运行中应用的层级,并查看实体及组件的当前设置,使用 Inspector 调试面板。