react-three-game 0.0.84 → 0.0.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +110 -36
  2. package/dist/index.d.ts +3 -7
  3. package/dist/index.js +1 -4
  4. package/dist/tools/prefabeditor/InstanceProvider.d.ts +0 -4
  5. package/dist/tools/prefabeditor/InstanceProvider.js +13 -44
  6. package/dist/tools/prefabeditor/PrefabEditor.d.ts +7 -2
  7. package/dist/tools/prefabeditor/PrefabEditor.js +13 -24
  8. package/dist/tools/prefabeditor/PrefabRoot.js +99 -48
  9. package/dist/tools/prefabeditor/{runtime.d.ts → assetRuntime.d.ts} +0 -25
  10. package/dist/tools/prefabeditor/assetRuntime.js +37 -0
  11. package/dist/tools/prefabeditor/components/BufferGeometryComponent.d.ts +3 -0
  12. package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +98 -0
  13. package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
  14. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +0 -3
  15. package/dist/tools/prefabeditor/components/DataComponent.d.ts +3 -0
  16. package/dist/tools/prefabeditor/components/DataComponent.js +87 -0
  17. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +1 -1
  18. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -1
  19. package/dist/tools/prefabeditor/components/GeometryComponent.js +4 -2
  20. package/dist/tools/prefabeditor/components/Input.d.ts +2 -13
  21. package/dist/tools/prefabeditor/components/Input.js +0 -55
  22. package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +3 -0
  23. package/dist/tools/prefabeditor/components/MaterialComponent.js +49 -18
  24. package/dist/tools/prefabeditor/components/ModelComponent.js +3 -3
  25. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +4 -0
  26. package/dist/tools/prefabeditor/components/PhysicsComponent.js +69 -132
  27. package/dist/tools/prefabeditor/components/PointLightComponent.js +1 -1
  28. package/dist/tools/prefabeditor/components/SoundComponent.js +17 -17
  29. package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
  30. package/dist/tools/prefabeditor/components/index.js +10 -8
  31. package/dist/tools/prefabeditor/types.d.ts +1 -0
  32. package/dist/tools/prefabeditor/types.js +18 -0
  33. package/dist/tools/prefabeditor/usePointerEvents.d.ts +27 -0
  34. package/dist/tools/prefabeditor/usePointerEvents.js +52 -0
  35. package/package.json +1 -1
  36. package/dist/tools/prefabeditor/GameEvents.d.ts +0 -128
  37. package/dist/tools/prefabeditor/GameEvents.js +0 -118
  38. package/dist/tools/prefabeditor/components/ClickComponent.d.ts +0 -3
  39. package/dist/tools/prefabeditor/components/ClickComponent.js +0 -52
  40. package/dist/tools/prefabeditor/runtime.js +0 -184
@@ -3,9 +3,8 @@ import { useEffect, useRef } from 'react';
3
3
  import { useThree } from '@react-three/fiber';
4
4
  import { SoundPicker } from '../../assetviewer/page';
5
5
  import { sound as soundManager } from '../../../helpers/SoundManager';
6
- import { gameEvents } from '../GameEvents';
7
- import { useAssetRuntime, useEntityRuntime } from '../runtime';
8
- import { BooleanField, EventField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField } from './Input';
6
+ import { useAssetRuntime, useEntityRuntime } from '../assetRuntime';
7
+ import { BooleanField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField, StringField } from './Input';
9
8
  import { colors } from '../styles';
10
9
  import { AudioListener } from 'three';
11
10
  const CLIP_MODE_OPTIONS = [
@@ -84,7 +83,7 @@ function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
84
83
  const removeClip = (index) => {
85
84
  setClips(clips.filter((_, clipIndex) => clipIndex !== index));
86
85
  };
87
- return (_jsxs(FieldGroup, { children: [_jsx(EventField, { name: "eventName", label: "Listen Event", values: component.properties, onChange: onUpdate, placeholder: "click" }), _jsx(FieldRenderer, { fields: [
86
+ return (_jsxs(FieldGroup, { children: [_jsx(StringField, { name: "eventName", label: "Listen Event", values: component.properties, onChange: onUpdate, placeholder: "player:footstep" }), _jsx(FieldRenderer, { fields: [
88
87
  {
89
88
  name: 'clipMode',
90
89
  label: 'Clip Mode',
@@ -115,14 +114,6 @@ function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
115
114
  { value: 'exponential', label: 'Exponential' },
116
115
  ] })] })) : null, _jsx(BooleanField, { name: "randomizePitch", label: "Random Pitch", values: component.properties, onChange: onUpdate, fallback: false }), randomizePitch ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "minPitch", label: "Min Pitch", values: component.properties, onChange: onUpdate, fallback: 0.96, step: 0.01, min: 0.1 }), _jsx(NumberField, { name: "maxPitch", label: "Max Pitch", values: component.properties, onChange: onUpdate, fallback: 1.04, step: 0.01, min: 0.1 })] })) : (_jsx(NumberField, { name: "pitch", label: "Pitch", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0.1 })), _jsx(BooleanField, { name: "randomizeVolume", label: "Random Volume", values: component.properties, onChange: onUpdate, fallback: false }), randomizeVolume ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "minVolume", label: "Min Volume", values: component.properties, onChange: onUpdate, fallback: 0.9, step: 0.01, min: 0 }), _jsx(NumberField, { name: "maxVolume", label: "Max Volume", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0 })] })) : (_jsx(NumberField, { name: "volume", label: "Volume", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0 }))] }));
117
116
  }
118
- function payloadMatchesNode(nodeId, payload) {
119
- if (!nodeId || !payload || typeof payload !== 'object')
120
- return true;
121
- const eventPayload = payload;
122
- const ids = [eventPayload.sourceEntityId, eventPayload.targetEntityId, eventPayload.instanceEntityId];
123
- const hasEntityIds = ids.some(id => typeof id === 'string');
124
- return hasEntityIds ? ids.includes(nodeId) : true;
125
- }
126
117
  function SoundComponentView({ properties, children }) {
127
118
  const { getSound } = useAssetRuntime();
128
119
  const { editMode, nodeId } = useEntityRuntime();
@@ -165,12 +156,17 @@ function SoundComponentView({ properties, children }) {
165
156
  audio.setDistanceModel(distanceModel);
166
157
  }, [distanceModel, maxDistance, refDistance, rolloffFactor]);
167
158
  useEffect(() => {
168
- if (editMode || paths.length === 0 || !eventName) {
159
+ if (editMode || paths.length === 0 || !eventName || typeof window === 'undefined') {
169
160
  return;
170
161
  }
171
- return gameEvents.on(eventName, (payload) => {
172
- if (!payloadMatchesNode(nodeId, payload)) {
173
- return;
162
+ const handleEvent = (event) => {
163
+ const customEvent = event;
164
+ const detail = customEvent.detail;
165
+ if (nodeId && detail && typeof detail === 'object') {
166
+ const relatedNodeIds = [detail.nodeId, detail.sourceNodeId, detail.targetNodeId].filter((value) => typeof value === 'string');
167
+ if (relatedNodeIds.length > 0 && !relatedNodeIds.includes(nodeId)) {
168
+ return;
169
+ }
174
170
  }
175
171
  const clip = pickClip(paths, mode, sequenceIndexRef);
176
172
  if (!clip)
@@ -204,7 +200,11 @@ function SoundComponentView({ properties, children }) {
204
200
  audio.setPlaybackRate(pitch);
205
201
  audio.setVolume(volume);
206
202
  audio.play();
207
- });
203
+ };
204
+ window.addEventListener(eventName, handleEvent);
205
+ return () => {
206
+ window.removeEventListener(eventName, handleEvent);
207
+ };
208
208
  }, [editMode, eventName, getSound, mode, nodeId, paths, positional, properties]);
209
209
  return (_jsxs(_Fragment, { children: [positional && listenerRef.current ? _jsx("positionalAudio", { ref: positionalAudioRef, args: [listenerRef.current] }) : null, children] }));
210
210
  }
@@ -3,7 +3,7 @@ import { useHelper } from "@react-three/drei";
3
3
  import { useRef, useEffect } from "react";
4
4
  import { BooleanField, ColorField, Label, NumberField, Vector3Input } from "./Input";
5
5
  import { SpotLightHelper } from "three";
6
- import { useAssetRuntime, useEntityRuntime } from "../runtime";
6
+ import { useAssetRuntime, useEntityRuntime } from "../assetRuntime";
7
7
  import { useFrame } from "@react-three/fiber";
8
8
  import { TexturePicker } from "../../assetviewer/page";
9
9
  import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
@@ -1,30 +1,32 @@
1
- import GeometryComponent from './GeometryComponent';
2
1
  import TransformComponent from './TransformComponent';
2
+ import GeometryComponent from './GeometryComponent';
3
+ import BufferGeometryComponent from './BufferGeometryComponent';
4
+ import ModelComponent from './ModelComponent';
5
+ import TextComponent from './TextComponent';
3
6
  import MaterialComponent from './MaterialComponent';
4
7
  import PhysicsComponent from './PhysicsComponent';
5
8
  import SpotLightComponent from './SpotLightComponent';
6
9
  import PointLightComponent from './PointLightComponent';
7
10
  import DirectionalLightComponent from './DirectionalLightComponent';
8
11
  import AmbientLightComponent from './AmbientLightComponent';
9
- import ModelComponent from './ModelComponent';
10
- import TextComponent from './TextComponent';
11
12
  import EnvironmentComponent from './EnvironmentComponent';
12
13
  import CameraComponent from './CameraComponent';
13
- import ClickComponent from './ClickComponent';
14
14
  import SoundComponent from './SoundComponent';
15
+ import DataComponent from './DataComponent';
15
16
  export const builtinComponents = [
16
- GeometryComponent,
17
17
  TransformComponent,
18
+ GeometryComponent,
19
+ BufferGeometryComponent,
20
+ ModelComponent,
21
+ TextComponent,
18
22
  MaterialComponent,
19
23
  PhysicsComponent,
20
24
  SpotLightComponent,
21
25
  PointLightComponent,
22
26
  DirectionalLightComponent,
23
27
  AmbientLightComponent,
24
- ModelComponent,
25
- TextComponent,
26
28
  EnvironmentComponent,
27
29
  CameraComponent,
28
- ClickComponent,
29
30
  SoundComponent,
31
+ DataComponent,
30
32
  ];
@@ -29,4 +29,5 @@ export declare function findComponent(node: ComponentHost | null | undefined, na
29
29
  export declare function findComponentEntry(node: ComponentHost | null | undefined, name: string): [string, ComponentData] | undefined;
30
30
  /** Check if a node has a component of the given type. */
31
31
  export declare function hasComponent(node: ComponentHost | null | undefined, typeName: string): boolean;
32
+ export declare function getNodeUserData(node: GameObject | null | undefined): Record<string, unknown>;
32
33
  export {};
@@ -1,3 +1,7 @@
1
+ const RESERVED_USER_DATA_KEYS = new Set([
2
+ 'prefabNodeId',
3
+ 'prefabNodeName',
4
+ ]);
1
5
  /** Find a component on a node by type name or key (e.g. "Model", "transform"). */
2
6
  export function findComponent(node, name) {
3
7
  var _a;
@@ -26,3 +30,17 @@ export function findComponentEntry(node, name) {
26
30
  export function hasComponent(node, typeName) {
27
31
  return findComponentEntry(node, typeName) !== undefined;
28
32
  }
33
+ export function getNodeUserData(node) {
34
+ var _a, _b;
35
+ const data = (_b = (_a = findComponent(node, 'Data')) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.data;
36
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
37
+ return {};
38
+ }
39
+ return Object.entries(data).reduce((result, [key, value]) => {
40
+ if (!key.trim() || RESERVED_USER_DATA_KEYS.has(key) || value === undefined) {
41
+ return result;
42
+ }
43
+ result[key] = value;
44
+ return result;
45
+ }, {});
46
+ }
@@ -0,0 +1,27 @@
1
+ import type { ThreeEvent } from "@react-three/fiber";
2
+ export type PointerHandler<T> = (event: ThreeEvent<PointerEvent>, entity: T) => void;
3
+ export interface PointerEventHandlers<T> {
4
+ onClick?: PointerHandler<T>;
5
+ onPointerDown?: PointerHandler<T>;
6
+ onPointerUp?: PointerHandler<T>;
7
+ onPointerMove?: PointerHandler<T>;
8
+ onPointerEnter?: PointerHandler<T>;
9
+ onPointerLeave?: PointerHandler<T>;
10
+ onPointerOver?: PointerHandler<T>;
11
+ onPointerOut?: PointerHandler<T>;
12
+ }
13
+ export interface UsePointerEventsOptions<T> extends PointerEventHandlers<T> {
14
+ enabled: boolean;
15
+ entity: T | null | undefined;
16
+ }
17
+ export declare function hasPointerEventHandlers<T>(handlers: PointerEventHandlers<T>): boolean;
18
+ export declare function usePointerEvents<T>({ enabled, entity, onClick, onPointerDown, onPointerUp, onPointerMove, onPointerEnter, onPointerLeave, onPointerOver, onPointerOut, }: UsePointerEventsOptions<T>): {
19
+ onClick: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
20
+ onPointerDown: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
21
+ onPointerMove: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
22
+ onPointerUp: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
23
+ onPointerEnter: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
24
+ onPointerLeave: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
25
+ onPointerOver: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
26
+ onPointerOut: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
27
+ };
@@ -0,0 +1,52 @@
1
+ export function hasPointerEventHandlers(handlers) {
2
+ return Boolean(handlers.onClick
3
+ || handlers.onPointerDown
4
+ || handlers.onPointerUp
5
+ || handlers.onPointerMove
6
+ || handlers.onPointerEnter
7
+ || handlers.onPointerLeave
8
+ || handlers.onPointerOver
9
+ || handlers.onPointerOut);
10
+ }
11
+ export function usePointerEvents({ enabled, entity, onClick, onPointerDown, onPointerUp, onPointerMove, onPointerEnter, onPointerLeave, onPointerOver, onPointerOut, }) {
12
+ if (!enabled) {
13
+ return {
14
+ onClick: undefined,
15
+ onPointerDown: undefined,
16
+ onPointerMove: undefined,
17
+ onPointerUp: undefined,
18
+ onPointerEnter: undefined,
19
+ onPointerLeave: undefined,
20
+ onPointerOver: undefined,
21
+ onPointerOut: undefined,
22
+ };
23
+ }
24
+ const forward = (handler) => {
25
+ if (!handler)
26
+ return undefined;
27
+ return (event) => {
28
+ event.stopPropagation();
29
+ if (!entity)
30
+ return;
31
+ handler(event, entity);
32
+ };
33
+ };
34
+ const forwardMove = onPointerMove
35
+ ? (event) => {
36
+ event.stopPropagation();
37
+ if (!entity)
38
+ return;
39
+ onPointerMove(event, entity);
40
+ }
41
+ : undefined;
42
+ return {
43
+ onClick: forward(onClick),
44
+ onPointerDown: forward(onPointerDown),
45
+ onPointerMove: forwardMove,
46
+ onPointerUp: forward(onPointerUp),
47
+ onPointerEnter: forward(onPointerEnter),
48
+ onPointerLeave: forward(onPointerLeave),
49
+ onPointerOver: forward(onPointerOver),
50
+ onPointerOut: forward(onPointerOut),
51
+ };
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.84",
3
+ "version": "0.0.86",
4
4
  "description": "high performance 3D game engine built in React",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -1,128 +0,0 @@
1
- import type { RapierRigidBody } from '@react-three/rapier';
2
- /** Physics event types (built-in) */
3
- export type PhysicsEventType = 'sensor:enter' | 'sensor:exit' | 'collision:enter' | 'collision:exit';
4
- export type InteractionEventType = 'click';
5
- /** Payload for physics events */
6
- export interface PhysicsEventPayload {
7
- sourceEntityId: string;
8
- targetEntityId: string | null;
9
- targetRigidBody: RapierRigidBody | null | undefined;
10
- }
11
- export interface ClickEventPayload {
12
- sourceEntityId: string;
13
- instanceEntityId?: string;
14
- point: [number, number, number];
15
- button: number;
16
- altKey: boolean;
17
- ctrlKey: boolean;
18
- metaKey: boolean;
19
- shiftKey: boolean;
20
- }
21
- /**
22
- * Register your custom event types here by extending this interface:
23
- *
24
- * declare module 'react-three-game' {
25
- * interface GameEventMap {
26
- * 'player:death': { playerId: string; cause: string };
27
- * 'score:change': { delta: number; total: number };
28
- * }
29
- * }
30
- */
31
- export interface GameEventMap {
32
- 'sensor:enter': PhysicsEventPayload;
33
- 'sensor:exit': PhysicsEventPayload;
34
- 'collision:enter': PhysicsEventPayload;
35
- 'collision:exit': PhysicsEventPayload;
36
- 'click': ClickEventPayload;
37
- }
38
- /** All registered event types */
39
- export type GameEventType = keyof GameEventMap | (string & {});
40
- /** Get payload type for an event, or fallback to generic */
41
- export type GameEventPayload<T extends string> = T extends keyof GameEventMap ? GameEventMap[T] : Record<string, unknown>;
42
- type EventHandler<T = unknown> = (payload: T) => void;
43
- type UnknownEventPayload = Record<string, unknown>;
44
- declare function emitGameEvent<TType extends keyof GameEventMap>(type: TType, payload: GameEventMap[TType]): void;
45
- declare function emitGameEvent(type: string, payload: UnknownEventPayload): void;
46
- declare function onGameEvent<TType extends keyof GameEventMap>(type: TType, handler: EventHandler<GameEventMap[TType]>): () => void;
47
- declare function onGameEvent(type: string, handler: EventHandler<UnknownEventPayload>): () => void;
48
- declare function offGameEvent<TType extends keyof GameEventMap>(type: TType, handler: EventHandler<GameEventMap[TType]>): void;
49
- declare function offGameEvent(type: string, handler: EventHandler<UnknownEventPayload>): void;
50
- /**
51
- * Game event system for all game interactions.
52
- *
53
- * Built-in events:
54
- * - sensor:enter - Something entered a sensor collider
55
- * - sensor:exit - Something exited a sensor collider
56
- * - collision:enter - A collision started
57
- * - collision:exit - A collision ended
58
- * - click - A prefab entity with a Click component was clicked in play mode
59
- *
60
- * Custom events:
61
- * - Emit any event type with any payload
62
- * - Extend GameEventMap interface for type safety
63
- *
64
- * @example
65
- * // Physics events (typed)
66
- * gameEvents.emit('sensor:enter', { sourceEntityId: 'zone', targetEntityId: 'player', targetRigidBody: rb });
67
- *
68
- * // Custom events
69
- * gameEvents.emit('player:death', { playerId: 'p1', cause: 'lava' });
70
- * gameEvents.emit('level:complete', { levelId: 3, time: 45.2 });
71
- */
72
- export declare const gameEvents: {
73
- /**
74
- * Emit an event to all subscribers
75
- */
76
- emit: typeof emitGameEvent;
77
- /**
78
- * Subscribe to an event type
79
- * @returns Unsubscribe function
80
- */
81
- on: typeof onGameEvent;
82
- /**
83
- * Unsubscribe from an event type
84
- */
85
- off: typeof offGameEvent;
86
- /**
87
- * Remove all subscribers (useful for cleanup/reset)
88
- */
89
- clear(): void;
90
- /**
91
- * Check if an event type has any subscribers
92
- */
93
- hasListeners(type: string): boolean;
94
- };
95
- /**
96
- * React hook to subscribe to game events.
97
- * Automatically cleans up on unmount.
98
- *
99
- * @example
100
- * // Physics event
101
- * useGameEvent('sensor:enter', (payload) => {
102
- * if (payload.sourceEntityId === 'coin') collectCoin();
103
- * }, []);
104
- *
105
- * // Custom event
106
- * useGameEvent('player:death', (payload) => {
107
- * const cause = typeof payload.cause === 'string' ? payload.cause : 'unknown';
108
- * showGameOver(cause);
109
- * }, []);
110
- */
111
- export declare function useGameEvent<TType extends keyof GameEventMap>(type: TType, handler: EventHandler<GameEventMap[TType]>, deps?: unknown[]): void;
112
- export declare function useGameEvent(type: string, handler: EventHandler<UnknownEventPayload>, deps?: unknown[]): void;
113
- /**
114
- * React hook to subscribe to any physics event payload.
115
- * Use this when the event name is dynamic but the payload comes from PhysicsComponent.
116
- */
117
- export declare function usePhysicsEvent(type: string, handler: EventHandler<PhysicsEventPayload>, deps?: unknown[]): void;
118
- /**
119
- * React hook to subscribe to click event payloads.
120
- * Use this when the event name is dynamic but the payload comes from ClickComponent.
121
- */
122
- export declare function useClickEvent(type: string, handler: EventHandler<ClickEventPayload>, deps?: unknown[]): void;
123
- /**
124
- * Helper to extract entity ID from Rapier collision data.
125
- * Entity IDs are stored in RigidBody userData.
126
- */
127
- export declare function getEntityIdFromRigidBody(rigidBody: RapierRigidBody | null | undefined): string | null;
128
- export {};
@@ -1,118 +0,0 @@
1
- import { useEffect, useCallback } from 'react';
2
- // Internal subscriber storage
3
- const subscribers = new Map();
4
- function emitGameEvent(type, payload) {
5
- const handlers = subscribers.get(type);
6
- if (handlers) {
7
- handlers.forEach(handler => {
8
- try {
9
- handler(payload);
10
- }
11
- catch (e) {
12
- console.error(`Error in gameEvents handler for ${type}:`, e);
13
- }
14
- });
15
- }
16
- }
17
- function onGameEvent(type, handler) {
18
- if (!subscribers.has(type)) {
19
- subscribers.set(type, new Set());
20
- }
21
- subscribers.get(type).add(handler);
22
- return () => {
23
- var _a;
24
- (_a = subscribers.get(type)) === null || _a === void 0 ? void 0 : _a.delete(handler);
25
- };
26
- }
27
- function offGameEvent(type, handler) {
28
- var _a;
29
- (_a = subscribers.get(type)) === null || _a === void 0 ? void 0 : _a.delete(handler);
30
- }
31
- function useTypedGameEvent(type, handler, deps = []) {
32
- // eslint-disable-next-line react-hooks/exhaustive-deps
33
- const stableHandler = useCallback(handler, deps);
34
- useEffect(() => {
35
- return onGameEvent(type, stableHandler);
36
- }, [type, stableHandler]);
37
- }
38
- /**
39
- * Game event system for all game interactions.
40
- *
41
- * Built-in events:
42
- * - sensor:enter - Something entered a sensor collider
43
- * - sensor:exit - Something exited a sensor collider
44
- * - collision:enter - A collision started
45
- * - collision:exit - A collision ended
46
- * - click - A prefab entity with a Click component was clicked in play mode
47
- *
48
- * Custom events:
49
- * - Emit any event type with any payload
50
- * - Extend GameEventMap interface for type safety
51
- *
52
- * @example
53
- * // Physics events (typed)
54
- * gameEvents.emit('sensor:enter', { sourceEntityId: 'zone', targetEntityId: 'player', targetRigidBody: rb });
55
- *
56
- * // Custom events
57
- * gameEvents.emit('player:death', { playerId: 'p1', cause: 'lava' });
58
- * gameEvents.emit('level:complete', { levelId: 3, time: 45.2 });
59
- */
60
- export const gameEvents = {
61
- /**
62
- * Emit an event to all subscribers
63
- */
64
- emit: emitGameEvent,
65
- /**
66
- * Subscribe to an event type
67
- * @returns Unsubscribe function
68
- */
69
- on: onGameEvent,
70
- /**
71
- * Unsubscribe from an event type
72
- */
73
- off: offGameEvent,
74
- /**
75
- * Remove all subscribers (useful for cleanup/reset)
76
- */
77
- clear() {
78
- subscribers.clear();
79
- },
80
- /**
81
- * Check if an event type has any subscribers
82
- */
83
- hasListeners(type) {
84
- var _a, _b;
85
- return ((_b = (_a = subscribers.get(type)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0) > 0;
86
- }
87
- };
88
- export function useGameEvent(type, handler, deps = []) {
89
- useTypedGameEvent(type, handler, deps);
90
- }
91
- /**
92
- * React hook to subscribe to any physics event payload.
93
- * Use this when the event name is dynamic but the payload comes from PhysicsComponent.
94
- */
95
- export function usePhysicsEvent(type, handler, deps = []) {
96
- useTypedGameEvent(type, handler, deps);
97
- }
98
- /**
99
- * React hook to subscribe to click event payloads.
100
- * Use this when the event name is dynamic but the payload comes from ClickComponent.
101
- */
102
- export function useClickEvent(type, handler, deps = []) {
103
- useTypedGameEvent(type, handler, deps);
104
- }
105
- // ============================================================================
106
- // Helpers
107
- // ============================================================================
108
- /**
109
- * Helper to extract entity ID from Rapier collision data.
110
- * Entity IDs are stored in RigidBody userData.
111
- */
112
- export function getEntityIdFromRigidBody(rigidBody) {
113
- var _a;
114
- if (!rigidBody)
115
- return null;
116
- const userData = rigidBody.userData;
117
- return (_a = userData === null || userData === void 0 ? void 0 : userData.entityId) !== null && _a !== void 0 ? _a : null;
118
- }
@@ -1,3 +0,0 @@
1
- import { Component } from './ComponentRegistry';
2
- declare const ClickComponent: Component;
3
- export default ClickComponent;
@@ -1,52 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useRef } from 'react';
3
- import { gameEvents } from '../GameEvents';
4
- import { useEntityRuntime } from '../runtime';
5
- import { EventField, FieldGroup } from './Input';
6
- function ClickComponentEditor({ component, onUpdate }) {
7
- return (_jsxs(FieldGroup, { children: [_jsx("div", { style: { fontSize: 12, opacity: 0.8 }, children: "Emits a game event in play mode when this entity is clicked." }), _jsx(EventField, { name: "eventName", label: "Emit Event", values: component.properties, onChange: onUpdate, placeholder: "click" })] }));
8
- }
9
- function ClickComponentView({ children, properties }) {
10
- const clickValid = useRef(false);
11
- const { editMode, nodeId } = useEntityRuntime();
12
- const eventName = (properties === null || properties === void 0 ? void 0 : properties.eventName) || 'click';
13
- const emitClick = (event) => {
14
- if (!nodeId)
15
- return;
16
- gameEvents.emit(eventName, {
17
- sourceEntityId: nodeId,
18
- point: [event.point.x, event.point.y, event.point.z],
19
- button: event.button,
20
- altKey: event.nativeEvent.altKey,
21
- ctrlKey: event.nativeEvent.ctrlKey,
22
- metaKey: event.nativeEvent.metaKey,
23
- shiftKey: event.nativeEvent.shiftKey,
24
- });
25
- };
26
- if (editMode) {
27
- return _jsx(_Fragment, { children: children });
28
- }
29
- return (_jsx("group", { onPointerDown: (event) => {
30
- event.stopPropagation();
31
- clickValid.current = true;
32
- }, onClick: (event) => {
33
- event.stopPropagation();
34
- }, onPointerMove: () => {
35
- clickValid.current = false;
36
- }, onPointerUp: (event) => {
37
- if (!clickValid.current)
38
- return;
39
- event.stopPropagation();
40
- emitClick(event);
41
- clickValid.current = false;
42
- }, children: children }));
43
- }
44
- const ClickComponent = {
45
- name: 'Click',
46
- Editor: ClickComponentEditor,
47
- View: ClickComponentView,
48
- defaultProperties: {
49
- eventName: 'click',
50
- },
51
- };
52
- export default ClickComponent;