エンティティの検索
コードは、自分で作成していないエンティティを必要とすることがよくあります。プレイヤー、鍵で開くドア、レベル内のすべての敵などです。どのエンティティも、自分自身とその子孫からほかのエンティティを検索できるので、app.rootからシーン全体を検索することも、その下の任意のエンティティからシーンの一部だけを検索することもできます。
検索メソッド
| 検索するエンティティ | 最初に一致したもの | 一致したものすべて |
|---|---|---|
| 名前で | findByName('door') | find('name', 'door') |
| パスで | findByPath('house/door') | - |
| タグで | - | findByTag('enemy') |
| コンポーネントを持つもの | findComponent('light') | findComponents('light') |
| スクリプトを持つもの | findScript('health') | findScripts('health') |
| 独自の条件を満たすもの | findOne(entity => ...) | find(entity => ...) |
検索は深さ優先です。エンティティの最初の子とそのすべての子孫を調べてから、2番目の子に進みます。検索の対象は、メソッドを呼び出したエンティティ自身とその子孫です。ただしfindByTag()は子孫だけを検索します。最初に一致したものを探すメソッドは、見つからないとnullを返します。例外はfindScript()で、undefinedを返します。一致したものすべてを探すメソッドは、空の配列を返します。
findComponent()とfindComponents()はエンティティではなくコンポーネントを、findScript()とfindScripts()はスクリプトのインスタンスを返します。いずれも、所属するエンティティを指すentityプロパティを持っています。
名前で検索
const door = app.root.findByName('door');
名前は一意である必要がないので、findByName()は一致するエンティティのうち最初に到達したものを返します。その名前を持つすべてのエンティティを取得するには、find('name', 'door')を使います。エンティティに一意の名前を付けているなら、findByName()が最も簡単な見つけ方です。
パスで検索
パスは、目的のエンティティに至るまでのエンティティの名前をスラッシュで区切ったもので、検索を始めるエンティティの下から始まります。シーンのほかの場所で名前が重複していても、エンティティを1つに特定できます。
const door = house.findByPath('ground-floor/door');
エンティティのpathプロパティは、階層のルートからのパスです。エディターで作ったシーンでは、すべてのエンティティがRootという名前のシーンのルートエンティティの下にあるので、app.rootからのパスは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');
// 'enemy'または'boss'のタグを持つエンティティ
const hostile = app.root.findByTag('enemy', 'boss');
// 'enemy'と'flying'の両方のタグを持つエンティティ
const flyers = app.root.findByTag(['enemy', 'flying']);
1つのエンティティを調べるには、同じ方法で引数を受け取るentity.tags.has()を使います。goblin.tags.has('enemy', 'boss')は、goblinがどちらかのタグを持っていればtrueです。
コンポーネントやスクリプトで検索
// シーン内のすべてのライト
const lights = app.root.findComponents('light');
lights.forEach(light => light.enabled = false);
// playerの下にある最初のHealthスクリプトのインスタンス。クラスでも名前でも検索できます
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));
各環境での検索
- Engine
- Editor
- React
- Web Components
シーン全体を検索するにはapp.rootのメソッドを、あるエンティティの下を検索するにはそのエンティティのメソッドを呼び出します。スクリプトの中では、this.app.rootがルートで、this.entityがスクリプト自身のエンティティです。
const wheels = this.entity.findByTag('wheel');
編集中にエンティティを見つけるには、ヒエラルキー上部の検索ボックスに入力します。虫眼鏡をクリックすると、何に一致させるかをName、Component Type、Script Name、Tagsから選べます。これらは検索メソッドが調べるものと同じです。Smart Searchを使うと、入力した文字列に近い名前にも一致します。

スクリプトの中で検索が必要になることはほとんどありません。スクリプトにエンティティ属性を持たせ、インスペクターでその属性にエンティティをドラッグします。こうして得た参照は、エンティティの名前を変えたり移動したりしても有効なままです。名前やパスによる検索ではそうはいきません。
import { Entity, Script } from 'playcanvas';
export class Key extends Script {
static scriptName = 'key';
/**
* この鍵で開くドア。
*
* @attribute
* @type {Entity}
*/
door;
open() {
this.door.enabled = false;
}
}
エンティティは自分でレンダリングしたものなので、検索が必要になることはほとんどありません。コードが必要とするエンティティへのrefを保持します。
const door = useRef(null);
<Entity ref={door} name="door" />
シーンを検索するには、アプリケーションのルートから始めます。レンダリングしたエンティティが存在するように、エフェクトの中で検索します。
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デバッグパネルを使います。