@playcanvas/react
Version:
A React renderer for PlayCanvas – build interactive 3D applications using React's declarative paradigm.
158 lines • 6.31 kB
JavaScript
"use client";
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
import { Entity as PcEntity } from 'playcanvas';
import { forwardRef, useImperativeHandle, useLayoutEffect, useMemo } from 'react';
import { useParent, ParentContext, useApp } from "./hooks/index.js";
import { usePointerEventsContext } from "./contexts/pointer-events-context.js";
import { validatePropsWithDefaults } from "./utils/validation.js";
/**
* The Entity component is the fundamental building block of a PlayCanvas scene.
* It represents a node in the scene graph and can have components attached to it.
*
* @example
* // Basic usage
* <Entity name="myEntity" position={[0, 1, 0]}>
* <Render type="box" />
* </Entity>
*
* @example
* // With pointer events
* <Entity
* position={[0, 1, 0]}
* onPointerDown={(e) => console.log('Clicked!')}
* onClick={(e) => console.log('Mouse clicked!')}
* >
* <Render type="sphere" />
* </Entity>
*
* @param {EntityProps} props - Component props
*/
export const Entity = forwardRef(function Entity(props, ref) {
const { children, ...propsToValidate } = props;
const safeProps = validatePropsWithDefaults(propsToValidate, componentDefinition);
const {
/** The name of the entity */
name = 'Untitled',
/** The local position of the entity */
position = [0, 0, 0],
/** The local scale of the entity */
scale = [1, 1, 1],
/** The local rotation of the entity */
rotation = [0, 0, 0],
/** The callback for the pointer down event */
onPointerDown,
/** The callback for the pointer up event */
onPointerUp,
/** The callback for the pointer over event */
onPointerOver,
/** The callback for the pointer out event */
onPointerOut,
/** The callback for the click event */
onClick, } = safeProps;
const parent = useParent();
const app = useApp();
const pointerEvents = usePointerEventsContext();
// Check if the entity has pointer events attached
const hasPointerEvents = !!(onPointerDown || onPointerUp || onPointerOver || onPointerOut || onClick);
// Create the entity only when 'app' changes
const entity = useMemo(() => new PcEntity(undefined, app), [app]);
useImperativeHandle(ref, () => entity);
// Add entity to parent when 'entity' or 'parent' changes
useLayoutEffect(() => {
parent.addChild(entity);
return () => {
parent.removeChild(entity);
entity.destroy(); // Clean up the entity
};
}, [app, parent, entity]);
// PointerEvents
useLayoutEffect(() => {
if (hasPointerEvents) {
pointerEvents.add(entity.getGuid());
}
if (onPointerDown)
entity.on('pointerdown', onPointerDown);
if (onPointerUp)
entity.on('pointerup', onPointerUp);
if (onPointerOver)
entity.on('pointerover', onPointerOver);
if (onPointerOut)
entity.on('pointerout', onPointerOut);
if (onClick)
entity.on('click', onClick);
return () => {
if (hasPointerEvents) {
pointerEvents.delete(entity.getGuid());
}
if (onPointerDown)
entity.off('pointerdown', onPointerDown);
if (onPointerUp)
entity.off('pointerup', onPointerUp);
if (onPointerOver)
entity.off('pointerover', onPointerOver);
if (onPointerOut)
entity.off('pointerout', onPointerOut);
if (onClick)
entity.off('click', onClick);
};
}, [app, parent, entity, onPointerDown, onPointerUp, onPointerOver, onPointerOut, onClick]);
useLayoutEffect(() => {
entity.name = name;
entity.setLocalPosition(...position);
entity.setLocalScale(...scale);
entity.setLocalEulerAngles(...rotation);
}, [entity, name, position, scale, rotation]);
return (_jsx(_Fragment, { children: _jsx(ParentContext.Provider, { value: entity, children: children || null }) }));
});
const componentDefinition = {
name: "Entity",
apiName: "Entity",
schema: {
name: {
validate: (val) => !val || typeof val === 'string',
errorMsg: (val) => `Invalid value for prop "name": "${val}". Expected a string or undefined.`,
default: 'Untitled'
},
position: {
validate: (val) => Array.isArray(val) && val.length === 3,
errorMsg: (val) => `Invalid value for prop "position": "${val}". Expected an array of 3 numbers.`,
default: [0, 0, 0]
},
scale: {
validate: (val) => Array.isArray(val) && val.length === 3,
errorMsg: (val) => `Invalid value for prop "scale": "${val}". Expected an array of 3 numbers.`,
default: [1, 1, 1]
},
rotation: {
validate: (val) => Array.isArray(val) && val.length === 3,
errorMsg: (val) => `Invalid value for prop "rotation": "${val}". Expected an array of 3 numbers.`,
default: [0, 0, 0]
},
onPointerDown: {
validate: (val) => typeof val === 'function',
errorMsg: (val) => `Invalid value for prop "onPointerDown": "${val}". Expected a function.`,
default: undefined
},
onPointerUp: {
validate: (val) => typeof val === 'function',
errorMsg: (val) => `Invalid value for prop "onPointerUp": "${val}". Expected a function.`,
default: undefined
},
onPointerOver: {
validate: (val) => typeof val === 'function',
errorMsg: (val) => `Invalid value for prop "onPointerOver": "${val}". Expected a function.`,
default: undefined
},
onPointerOut: {
validate: (val) => typeof val === 'function',
errorMsg: (val) => `Invalid value for prop "onPointerOut": "${val}". Expected a function.`,
default: undefined
},
onClick: {
validate: (val) => typeof val === 'function',
errorMsg: (val) => `Invalid value for prop "onClick": "${val}". Expected a function.`,
default: undefined
}
}
};
//# sourceMappingURL=Entity.js.map