UNPKG

@playcanvas/react

Version:

A React renderer for PlayCanvas – build interactive 3D applications using React's declarative paradigm.

158 lines 6.31 kB
"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