Skip to main content

<pc-node>

The <pc-node> tag binds to a node inside the hierarchy that a <pc-model> instantiated, and declares overrides against it. It is how you adjust what a GLB was authored with, without editing the GLB: hide a node, move it, give it a component, or parent new content under it.

Where <pc-entity> creates an entity, <pc-node> references one that the model already created. Its name is a lookup, never a rename.

Usage
  • It must be a descendant of a <pc-model>, either directly or nested inside another <pc-node>.
  • It can have 0..n nested <pc-node> children, which resolve their own name within the bound node's subtree.
  • It can have 0..n <pc-entity> children, which are created and parented under the bound node — attachment points for new content.
  • It can have the same component tags as a <pc-entity><pc-collision>, <pc-light>, <pc-scripts> and the rest — which add that component to the bound node.

Attributes

AttributeTypeDefaultDescription
enabledBooleanauthoredOverrides the node's enabled state
indexNumber-Which match to bind when name matches more than one node, 0-based in depth-first order. Required when the name is ambiguous, optional otherwise
nameString-Name of the node to bind, looked up within the enclosing <pc-model> (or <pc-node>)
positionVector3authoredOverrides the node's local position as "X Y Z" values
rotationVector3authoredOverrides the node's local rotation as "X Y Z" Euler angles in degrees
scaleVector3authoredOverrides the node's local scale as "X Y Z" values
tagsStringauthoredOverrides the node's tags, separated by spaces or commas
Overrides, not defaults

Everything except name and index is an override, so <pc-node> reads its absent attributes differently from every other tag. An attribute that is present replaces the authored value; an attribute that is absent leaves it alone. Removing one at runtime — or assigning null to the matching JavaScript property — restores the value the model was authored with, rather than the engine default. That is why the table above has no concrete defaults: the default is whatever the GLB says.

An override replaces the authored value; it does not compose with it. position="0 1 0" puts the node at a local Y of 1, whatever it was exported at.

Finding the Node

name matches on the node names in the loaded hierarchy, taking the first match in depth-first order. Nesting one <pc-node> inside another scopes the inner search to the outer node's subtree, which is the simplest way to reach a node whose name is only unique locally.

When a name is not unique within the search scope, the element binds nothing and warns with the paths of every candidate, so you can pick one with index:

pc-node 'Wheel' is ambiguous in model 'car' - specify index: [0] Body/Wheel_FL/Wheel, [1] Body/Wheel_FR/Wheel

Binding nothing is deliberate: guessing would silently decorate the wrong node, and a re-export that introduced a duplicate name would break a document that used to work.

The other resolution failures warn in the same way — a name that matches nothing (with the closest name it did find, as a typo hint), an index beyond the number of matches, and a node that another <pc-node> has already bound. In each case the element binds nothing and never becomes ready.

An element only becomes ready once it is bound, and its descendants wait with it. If the model reloads, or the element retargets because you changed name, it re-resolves and re-applies its overrides, components and attached content against the new node.

Events

<pc-node> dispatches the same pointer events as <pc-entity>, fired when the pointer intersects the bound node's geometry. Binding a node is what makes it a pick target, so a <pc-node> is also how you make one part of a model interactive.

EventDescription
pointerdownFired when a pointer is pressed down on the node.
pointerenterFired when a pointer enters the node.
pointerleaveFired when a pointer leaves the node.
pointermoveFired when a pointer is moved over the node.
pointerupFired when a pointer is released from the node.

The inline onpointer* attributes work here exactly as they do on <pc-entity>.

Example

<pc-app>
<pc-asset src="assets/car.glb" id="car"></pc-asset>
<pc-scene>
<pc-entity name="camera" position="0 1 4">
<pc-camera></pc-camera>
</pc-entity>
<pc-model asset="car">
<!-- Hide the ground plane the GLB was exported with -->
<pc-node name="Plane" enabled="false"></pc-node>

<!-- Nudge the roof up and tag it, leaving its authored rotation and scale alone -->
<pc-node name="Roof" position="0 0.05 0" tags="openable"></pc-node>

<!-- Attach a spot light under the headlight node -->
<pc-node name="Headlight_L">
<pc-entity>
<pc-light type="spot" intensity="4" range="20"></pc-light>
</pc-entity>
</pc-node>
</pc-model>
</pc-scene>
</pc-app>

JavaScript Interface

You can programmatically create and manipulate <pc-node> elements using the NodeElement API.

Alongside entity, which is the node it bound, the element reports how resolution went. state is "pending" while it has nothing to bind (no name yet, or the model has not instantiated), "bound" once it has, and "missing", "ambiguous" or "duplicate" when resolution failed. path is the /-separated path of the bound node below the search scope, or null while unbound. Together they let you assert a document's bindings rather than reading the console:

import { whenReady } from '@playcanvas/web-components';

const node = await whenReady('pc-node[name="Roof"]');
console.log(node.state, node.path); // 'bound' 'Body/Roof'