<pc-entity>
<pc-entity>タグはエンティティを定義します。シーン内の名前付きの点で、位置・回転・スケールを持ち、その中のコンポーネントタグが描画・ライティング・物理などの能力を与えます。
- それは
<pc-scene>、別の<pc-entity>、<pc-model>、または<pc-node>の直接の子でなければなりません。モデルの下に置くとそのモデルのホストエンティティに、ノードの下に置くと読み込まれたモデル内のそのノードに親子付けされます。 - それは0からn個の
<pc-entity>または<pc-model>の子を持つことができます。 - それはオプションで、各コンポーネントタイプの子を1つ持つことができます:
<pc-anim>、<pc-audio-listener>、<pc-button>、<pc-camera>、<pc-collision>、<pc-element>、<pc-gsplat>、<pc-joint>、<pc-layout-child>、<pc-layout-group>、<pc-light>、<pc-particle-system>、<pc-render>、<pc-rigid-body>、<pc-screen>、<pc-script>、<pc-scrollbar>、<pc-scroll-view>、<pc-sound>。
属性
| 属性 | タイプ | デフォルト | 説明 |
|---|---|---|---|
enabled | Boolean | "true" | エンティティの有効状態 |
name | String | - | エンティティの名前識別子。名前のないエンティティはUntitledという名前になります |
position | Vector3 | "0 0 0" | 「X Y Z」値としてのローカル空間位置 |
rotation | Vector3 | "0 0 0" | 度単位の「X Y Z」オイラー角としてのローカル空間回転 |
scale | Vector3 | "1 1 1" | 「X Y Z」値としてのローカル空間スケール |
tags | String | - | コンマ区切りのタグのリスト |
イベント
これらのイベントは、addEventListener()を使用するか、このインターフェースのoneventnameプロパティにイベントリスナーを割り当てることでリッスンできます。
| イベント | 説明 |
|---|---|
click | エンティティ上でプライマリボタンが押され、そして離されたときに発生します。クリックを参照してください。 |
pointercancel | ブラウザが押下を取り消したときに、その押下が始まったエンティティで発生します。たとえばオペレーティングシステムがタッチを引き継いだ場合です。その後にclickは発生しません。 |
pointerdown | エンティティ上でポインターのボタンが押されたときに発生します。 |
pointerenter | ポインターが、エンティティとその下のエンティティのどれの上にもない状態から、それらのいずれかの上に移動したときに発生します。バブリングしません。 |
pointerleave | ポインターが、エンティティとその下のすべてのエンティティの上から離れたときに発生します。バブリングしません。 |
pointermove | ポインターがエンティティ上で移動したときに発生します。 |
pointerout | ポインターがエンティティの上から離れたときに発生します。relatedTargetは移動先の要素です。 |
pointerover | ポインターがエンティティの上に移動したときに発生します。relatedTargetは移動元の要素です。 |
pointerup | エンティティ上でポインターのボタンが離されたときに発生します。 |
これらのイベントは、それを含む<pc-app>がポインターの下のシーンをピッキングしてディスパッチするもので、ブラウザ自身のポインターイベントと同じように振る舞います。9つすべてがPointerEventオブジェクトです。各イベントはエンティティ要素をターゲットとし(ほとんどのイベントでは、ポインターの下にあるジオメトリを担う要素です)、そこから要素ツリーをバブリングします。event.targetはその要素です。そのため、祖先のエンティティや<pc-scene>に置いたリスナーは、その下にあるすべてのエンティティのイベントを受け取ります。
例外はpointerenterとpointerleaveです。これらはバブリングせず、ポインターが出入りした要素がそれぞれ自分用のイベントを受け取ります。背景からエンティティの上に移動すると、そのエンティティとその各祖先で、外側から順にpointerenterが発生します。親から子の上に移動したときは、ポインターは親から離れていないので、子でだけ発生します。したがって祖先はサブツリー全体に対してpointerenterとpointerleaveを1回ずつだけ受け取ります。ホバー効果に必要なのはたいていこの動作です。4つの境界イベント(pointerover・pointerout・pointerenter・pointerleave)のすべてで、relatedTargetはポインターの移動元または移動先の要素です。それが背景だった場合や、ポインターがキャンバスの外に出た場合、relatedTargetは<pc-app>になります。
ポインターはそれぞれ個別に追跡されるため、2本のタッチがそれぞれ別のエンティティの上にあることもあります。ポインターがキャンバスの外に出るとホバーが終了し、pointeroutとpointerleaveが発生しますが、押下は終了しません。戻ってきて、押下したエンティティの上で離したポインターは、そのエンティティをクリックします。一方、キャンバスの外で離すと、クリックなしで押下が終了します。
これらのイベントは、インラインの onclick および onpointer* 属性を使って宣言的に処理することもできます。これらは標準のインラインイベントハンドラーであり、ブラウザ自身によってコンパイル・実行されるため、任意のHTML要素の onclick とまったく同じように動作します。属性を(実行時であっても)設定すると以前のハンドラーが置き換えられ、削除するとハンドラーが削除されます。ハンドラー内では、this はその属性を持つ要素で、event はディスパッチされたイベントです。イベントの target は、実際にヒットしたエンティティです。
<pc-entity name="cube"
onpointerenter="this.setAttribute('scale', '1.2 1.2 1.2')"
onpointerleave="this.removeAttribute('scale')"
onclick="this.setAttribute('rotation', '0 45 0')">
<pc-render type="box"></pc-render>
</pc-entity>
クリック
クリックによる選択を実装したいときに使うのがclickです。pointerdownとpointerupから自分で組み立てるのではなく、これを使う理由を知っておく価値があります。
- プライマリボタンを必要とするため、右クリックでは発生しません。
pointerupだけではこれを区別できません。 - 押下と解放の両方を必要とするため、
pointerdownのようにカメラのドラッグ開始ごとに発生することはありません。 - 押下と解放が別のジオメトリ上で起きた場合、クリックは両者の最も近い共通の祖先で発生します。あるオブジェクトから兄弟オブジェクトへドラッグすると共通の親(トップレベルの2つのエンティティなら
<pc-scene>)でクリックが発生し、背景へドラッグして離すとどこでもクリックは発生しません。これは、ネストしたHTML上のネイティブなクリックにブラウザが適用するのと同じルールです。 detailにはネイティブなクリックと同様にクリック回数が入ります。同じ要素を0.5秒以内に再度クリックするとdetailが2のclickとして届くため、ダブルクリックは別のイベントではなくdetailから読み取ります。
ブラウザが取り消した押下 — たとえばオペレーティングシステムが引き継いだタッチ — はクリックとして成立せず、代わりに押下が始まったエンティティでpointercancelが発生します(キャンバスにはtouch-action: noneのスタイルが設定されているため、キャンバス上のタッチがスクロールになることはありません)。
イベントがディスパッチされるタイミング
ポインターの下にあるエンティティを見つけるにはシーンをもう一度レンダリングする必要があるため、<pc-app>は何かがリッスンしている間だけピッキングします。デフォルトのpicking="auto"では、あるイベントの種類のリスナーがエンティティ要素(<pc-entity>、<pc-model>、<pc-node>)または<pc-scene>に登録されている間だけ、その種類についてピッキングします。リスナーはaddEventListener()で追加しても、インライン属性として設定しても、ハンドラープロパティに代入してもかまいません。これらのイベントをどれもリッスンしていないページには、そのためのコストは一切かかりません。
次の2種類のリスナーは認識されません。
- ページ上の他の場所にあるリスナー。 ドキュメントや
<pc-app>自体に置いたリスナー、ReactのonPointerMoveのようなフレームワークの委譲ハンドラーなどです。これらは、<pc-app>が認識するリスナーがエンティティの経路上にある場合にだけイベントを受け取り、自分からイベントのディスパッチを引き起こすことはありません。すべてのポインターイベントでピッキングするには<pc-app>にpicking="always"を、イベントを無効にするにはpicking="none"を設定します。ReactのonClickは例外です。ReactはonClickを持つ要素のonclickプロパティも設定するため、エンティティ要素や<pc-scene>に置いたonClickは認識され、autoのままでクリックが動作します。そのためにalwaysに切り替えても、ポインターが動くたびにピッキングが1回増えるだけです。 - ライブラリが要素を定義する前に
addEventListener()で追加されたリスナー。 ライブラリのモジュールより先に実行されるクラシックな<script>や、テンプレートのクローンを追加する前に設定するコードがこれに当たります。このようなリスナーはライブラリをインポートするモジュールから追加し、クローンのリスナーはクローンを追加した後で登録してください。インライン属性とハンドラープロパティは、いつ設定したものでも認識されます。
キャンバスはその間もずっと自身のネイティブなポインターイベントを受け取り続けるため、<pc-app>やそれより上に置いたリスナーは両方の種類を受け取ります。両者はevent.isTrustedで区別できます。ブラウザのネイティブなイベントではtrue、ディスパッチされたイベントではfalseで、後者のtargetはエンティティ要素、<pc-scene>、あるいはpicking="always"での一部の境界イベントでは<pc-app>自身です。ディスパッチされたイベントは、ピッキングの結果がGPUから読み戻されてから届くため、それを引き起こしたネイティブなイベントより少し遅れて到着します。
例
入れ子のエンティティで組み立てたデスクランプです。関節である arm、forearm、head はそれぞれ親を基準に配置・回転されているため、1つを回すと、その先にあるものがライトも含めてすべて一緒に動きます。ハンドラーはすべてルートの lamp にあります。ポインターがパーツの間をどう移動しても、pointerenter と pointerleave はランプ全体に対して1回ずつ発生します。また、どのパーツのクリックも lamp までバブリングし、電球の enabled 属性を切り替えます。エンティティを無効にするとそのコンポーネントと子もまとめて無効になるため、ライトと発光が一緒に消えます。関節の rotation の値を変えてランプにポーズを付けたり、ルートに scale を付けて大きさを変えたりしてみましょう:
<pc-app>
<pc-material id="paint" diffuse="#e8702a" gloss="0.7"></pc-material>
<pc-material id="steel" diffuse="#b7bec7" gloss="0.8"></pc-material>
<pc-material id="glow" emissive="#fff0d0"></pc-material>
<pc-material id="desk" diffuse="#3d4250"></pc-material>
<pc-scene>
<pc-entity name="camera" position="0.45 1.55 2.3" rotation="-27 0 0">
<pc-camera clear-color="#1d1f2b"></pc-camera>
</pc-entity>
<pc-entity name="room-light" rotation="40 50 0">
<pc-light intensity="0.8" cast-shadows normal-offset-bias="0.05" shadow-bias="0.2"></pc-light>
</pc-entity>
<pc-entity name="desk" scale="100 1 100">
<pc-render type="plane" material="desk"></pc-render>
</pc-entity>
<pc-entity name="lamp" rotation="0 -25 0"
onpointerenter="document.body.style.cursor = 'pointer'"
onpointerleave="document.body.style.cursor = ''"
onclick="const bulb = this.querySelector('[name=bulb]'); bulb.setAttribute('enabled', !bulb.enabled)">
<pc-entity name="base" position="0 0.025 0" scale="0.45 0.05 0.45">
<pc-render type="cylinder" material="paint"></pc-render>
</pc-entity>
<pc-entity name="arm" position="0 0.05 0" rotation="0 0 25">
<!-- スケールは arm ではなくロッドに付けているので、その先の関節は引き伸ばされません -->
<pc-entity name="arm-rod" position="0 0.35 0" scale="0.04 0.7 0.04">
<pc-render type="cylinder" material="steel"></pc-render>
</pc-entity>
<pc-entity name="forearm" position="0 0.7 0" rotation="0 0 -80">
<pc-entity name="forearm-rod" position="0 0.35 0" scale="0.04 0.7 0.04">
<pc-render type="cylinder" material="steel"></pc-render>
</pc-entity>
<pc-entity name="head" position="0 0.7 0" rotation="0 0 70">
<pc-entity name="shade" position="0 -0.12 0" scale="0.3 0.24 0.3">
<pc-render type="cone" material="paint"></pc-render>
</pc-entity>
<pc-entity name="bulb" position="0 -0.24 0" scale="0.1 0.1 0.1">
<pc-render type="sphere" material="glow"></pc-render>
<pc-light type="spot" color="#ffc978" intensity="4" inner-cone-angle="25" outer-cone-angle="35"></pc-light>
</pc-entity>
</pc-entity>
</pc-entity>
</pc-entity>
</pc-entity>
</pc-scene>
</pc-app>
JavaScriptインターフェース
EntityElement APIを使用して、<pc-entity>要素をプログラムで作成および操作できます。
entityプロパティは、この要素が作成するエンジンのEntityです。要素の準備が完了するまではnullで、lookAt()から子タグが追加したコンポーネントまで、属性がカバーしないものはすべてここから利用できます。
エンティティのサブツリーのコピーを大量に作るには、ネイティブの <template> 要素の中に一度だけ宣言してクローンしてください — テンプレートによる再利用可能なシーンを参照してください。
関連項目
<pc-model>— GLBをインスタンス化するエンティティ<pc-node>— 名前で指定する、読み込まれたモデル内のエンティティ<pc-script>— エンティティに付ける振る舞い- テンプレートによる再利用可能なシーン —
<template>からエンティティのサブツリーをクローンする方法
サンプル: Basic Shapes、Falling Blocks