react-three-game 0.0.85 → 0.0.87

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 (38) hide show
  1. package/README.md +87 -35
  2. package/dist/index.d.ts +5 -7
  3. package/dist/index.js +2 -4
  4. package/dist/tools/prefabeditor/GameEvents.d.ts +36 -117
  5. package/dist/tools/prefabeditor/GameEvents.js +44 -96
  6. package/dist/tools/prefabeditor/InstanceProvider.d.ts +0 -4
  7. package/dist/tools/prefabeditor/InstanceProvider.js +13 -44
  8. package/dist/tools/prefabeditor/PrefabEditor.d.ts +7 -2
  9. package/dist/tools/prefabeditor/PrefabEditor.js +13 -24
  10. package/dist/tools/prefabeditor/PrefabRoot.js +93 -44
  11. package/dist/tools/prefabeditor/{runtime.d.ts → assetRuntime.d.ts} +0 -25
  12. package/dist/tools/prefabeditor/assetRuntime.js +37 -0
  13. package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +4 -2
  14. package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
  15. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +0 -3
  16. package/dist/tools/prefabeditor/components/DataComponent.d.ts +3 -0
  17. package/dist/tools/prefabeditor/components/DataComponent.js +87 -0
  18. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +1 -1
  19. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -1
  20. package/dist/tools/prefabeditor/components/GeometryComponent.js +4 -2
  21. package/dist/tools/prefabeditor/components/Input.d.ts +2 -13
  22. package/dist/tools/prefabeditor/components/Input.js +0 -55
  23. package/dist/tools/prefabeditor/components/MaterialComponent.js +1 -1
  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 +64 -130
  27. package/dist/tools/prefabeditor/components/PointLightComponent.js +1 -1
  28. package/dist/tools/prefabeditor/components/SoundComponent.js +18 -11
  29. package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
  30. package/dist/tools/prefabeditor/components/index.js +2 -2
  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/components/ClickComponent.d.ts +0 -3
  37. package/dist/tools/prefabeditor/components/ClickComponent.js +0 -52
  38. package/dist/tools/prefabeditor/runtime.js +0 -184
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Scene Editor](assets/editor.gif)
4
4
 
5
- JSON-first 3D game engine for React Three Fiber.
5
+ JSON-first scene mounting and authoring for React Three Fiber.
6
6
 
7
7
  Built on top of [three.js](https://github.com/mrdoob/three.js), [@react-three/fiber](https://github.com/pmndrs/react-three-fiber), and [@react-three/rapier](https://github.com/pmndrs/react-three-rapier).
8
8
 
@@ -10,7 +10,7 @@ Built on top of [three.js](https://github.com/mrdoob/three.js), [@react-three/fi
10
10
  * **🎬 Scene Editor** - Edit prefabs visually with hierarchy, inspector, transform gizmos, and play mode.
11
11
  * **⚛️ Physics** - Author rigid bodies directly in prefab data and run them through Rapier.
12
12
  * **🧩 Components** - Build scenes from reusable `GameObject` + component composition.
13
- * **🔧 Runtime Scene API** - Mutate the live world through `Scene`, `Entity`, and `EntityComponent` handles.
13
+ * **🔧 Direct Runtime Access** - Get native `Object3D`, Rapier rigid body, and prefab store access without a parallel engine API.
14
14
  * **⚡ R3F Native** - Use normal React Three Fiber components whenever runtime behavior is clearer in code.
15
15
 
16
16
  ## Documentation
@@ -98,12 +98,10 @@ Open the hosted editor here:
98
98
 
99
99
  * https://prnth.com/react-three-game/editor
100
100
 
101
- ## Prefabs And Scenes
101
+ ## Prefabs And Mounted Scenes
102
102
 
103
103
  `Prefab` is the serializable pure data format.
104
104
 
105
- `Scene` is the live runtime/editor world handle.
106
-
107
105
  That means a saved scene is just a prefab, and the same prefab can be:
108
106
 
109
107
  * edited directly in `PrefabEditor`
@@ -163,7 +161,7 @@ interface GameObject {
163
161
 
164
162
  ## Runtime Mutation
165
163
 
166
- Use the `Scene` API from `PrefabEditorRef` for authored state changes that should stay in prefab data.
164
+ Use native object access for Three.js behavior and the prefab store for authored data changes.
167
165
 
168
166
  ```tsx
169
167
  import { useEffect, useRef } from "react";
@@ -173,48 +171,105 @@ function RaiseBall() {
173
171
  const editorRef = useRef<PrefabEditorRef>(null);
174
172
 
175
173
  useEffect(() => {
176
- const transform = editorRef.current
177
- ?.scene
178
- .find("ball")
179
- ?.getComponent<{ position: [number, number, number] }>("Transform");
180
-
181
- transform?.set("position", [0, 8, 0]);
174
+ editorRef.current?.store.getState().updateNode("ball", (node) => ({
175
+ ...node,
176
+ components: {
177
+ ...node.components,
178
+ transform: {
179
+ type: "Transform",
180
+ properties: {
181
+ ...node.components?.transform?.properties,
182
+ position: [0, 8, 0],
183
+ },
184
+ },
185
+ },
186
+ }));
182
187
  }, []);
183
188
 
184
189
  return <PrefabEditor ref={editorRef} initialPrefab={prefab} />;
185
190
  }
186
191
  ```
187
192
 
188
- Batch related entity changes so they flush as one store revision:
193
+ For live Three.js access, use mounted objects directly:
189
194
 
190
195
  ```tsx
191
- scene.batch(() => {
192
- scene.find("orb1")?.getComponent("Transform")?.set("position", [1, 0, 0]);
193
- scene.find("orb2")?.getComponent("Transform")?.set("position", [-1, 0, 0]);
194
- });
196
+ const ball = editorRef.current?.getObject("ball");
197
+ ball?.rotateY(0.5);
195
198
  ```
196
199
 
197
- Define a component runtime factory with `create(ctx)` for hot runtime behavior that mutates the live Three.js object directly:
200
+ Mounted node metadata is mirrored onto the canonical Three.js wrapper object:
201
+
202
+ * `GameObject.id` -> `object.userData.prefabNodeId`
203
+ * `GameObject.name` -> `object.name` and `object.userData.prefabNodeName`
204
+ * `Data.properties.data` -> merged into `object.userData`
205
+
206
+ That gives you a stable authored id for traversal-based integrations, while still making Three.js name lookup convenient:
198
207
 
199
208
  ```tsx
200
- import type { Component } from "react-three-game";
201
-
202
- const Rotator: Component = {
203
- name: "Rotator",
204
- Editor: () => null,
205
- defaultProperties: { speed: 1 },
206
- create(ctx) {
207
- return {
208
- update(dt) {
209
- const speed = ctx.component.get<number>("speed") ?? 1;
210
- ctx.object.rotation.y += dt * speed;
209
+ const playerByName = editorRef.current?.root?.getObjectByName("Player");
210
+ const playerById = editorRef.current?.root?.getObjectByProperty("userData.prefabNodeId", "player");
211
+ ```
212
+
213
+ Treat names as a convenience surface, not the primary lookup key:
214
+
215
+ * names are not guaranteed unique
216
+ * `getObject(id)` is still the stable authored-node lookup
217
+ * traversal metadata is applied to the prefab node transform object, not necessarily the inner mesh or model child
218
+
219
+ You can author extra `userData` from the editor with a `Data` component:
220
+
221
+ ```json
222
+ {
223
+ "data": {
224
+ "faction": "enemy",
225
+ "health": 100,
226
+ "loot": { "table": "crate" }
227
+ }
228
+ }
229
+ ```
230
+
231
+ For batched authored updates, write through the store once:
232
+
233
+ ```tsx
234
+ editorRef.current?.store.getState().updateNodes([
235
+ {
236
+ id: "orb1",
237
+ update: (node) => ({
238
+ ...node,
239
+ components: {
240
+ ...node.components,
241
+ transform: {
242
+ ...node.components?.transform,
243
+ type: "Transform",
244
+ properties: {
245
+ ...node.components?.transform?.properties,
246
+ position: [1, 0, 0],
247
+ },
248
+ },
211
249
  },
212
- };
250
+ }),
213
251
  },
214
- };
252
+ {
253
+ id: "orb2",
254
+ update: (node) => ({
255
+ ...node,
256
+ components: {
257
+ ...node.components,
258
+ transform: {
259
+ ...node.components?.transform,
260
+ type: "Transform",
261
+ properties: {
262
+ ...node.components?.transform?.properties,
263
+ position: [-1, 0, 0],
264
+ },
265
+ },
266
+ },
267
+ }),
268
+ },
269
+ ]);
215
270
  ```
216
271
 
217
- `View` handles composition and rendering. `create(ctx)` handles imperative runtime behavior.
272
+ Custom component `View`s should use normal React and R3F behavior, such as `useFrame`, refs, and native Three.js APIs.
218
273
 
219
274
  ## Useful Exports
220
275
 
@@ -224,9 +279,6 @@ const Rotator: Component = {
224
279
  * `PrefabEditorMode`
225
280
  * `Prefab`
226
281
  * `GameObject`
227
- * `Scene`
228
- * `Entity`
229
- * `EntityComponent`
230
282
  * `registerComponent`
231
283
  * `createPrefabStore`
232
284
  * `usePrefabStoreApi`
package/dist/index.d.ts CHANGED
@@ -10,19 +10,19 @@ export { useEditorContext } from './tools/prefabeditor/PrefabEditor';
10
10
  export type { EditorContextType } from './tools/prefabeditor/PrefabEditor';
11
11
  export { createPrefabStore, prefabStoreToPrefab, usePrefabStoreApi } from './tools/prefabeditor/prefabStore';
12
12
  export type { PrefabStoreApi, PrefabStoreState } from './tools/prefabeditor/prefabStore';
13
- export { createScene } from './tools/prefabeditor/scene';
14
13
  export { denormalizePrefab } from './tools/prefabeditor/prefab';
14
+ export { gameEvents, getEntityIdFromRigidBody, useClickEvent, useGameEvent, usePhysicsEvent } from './tools/prefabeditor/GameEvents';
15
+ export type { ClickEventPayload, GameEventHandler, GameEventMap, PhysicsEventPayload } from './tools/prefabeditor/GameEvents';
15
16
  export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
16
17
  export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from './tools/prefabeditor/components/Input';
17
18
  export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
18
19
  export type { ExportGLBOptions } from './tools/prefabeditor/utils';
19
20
  export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
20
21
  export type { PrefabEditorProps, PrefabEditorRef, } from './tools/prefabeditor/PrefabEditor';
21
- export type { SpawnOptions, Scene, Entity, EntityComponent, EntityData, EntityUpdate, PropertyPath, SceneUpdates, } from './tools/prefabeditor/scene';
22
+ export type { Entity, EntityComponent, EntityData, EntityUpdate, PropertyPath, Scene, SceneUpdates, SpawnOptions, } from './tools/prefabeditor/scene';
22
23
  export type { PrefabRootProps } from './tools/prefabeditor/PrefabRoot';
23
- export type { AssetRuntime, EntityRuntime, LiveObjectRef, LiveRigidBodyRef } from './tools/prefabeditor/runtime';
24
- export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/runtime';
25
- export type { ComponentInstance, ComponentRuntimeContext } from './tools/prefabeditor/runtime';
24
+ export type { AssetRuntime, EntityRuntime, LiveObjectRef, LiveRigidBodyRef } from './tools/prefabeditor/assetRuntime';
25
+ export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/assetRuntime';
26
26
  export type { Component, ComponentViewProps } from './tools/prefabeditor/components/ComponentRegistry';
27
27
  export type { FieldDefinition, FieldType } from './tools/prefabeditor/components/Input';
28
28
  export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
@@ -30,8 +30,6 @@ export type { MaterialOverrides } from './tools/prefabeditor/components/Material
30
30
  export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
31
31
  export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
32
32
  export { float, positionLocal, sin, time, uniform, vec3, } from 'three/tsl';
33
- export { gameEvents, useGameEvent, usePhysicsEvent, useClickEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
34
- export type { GameEventType, GameEventMap, GameEventPayload, PhysicsEventType, InteractionEventType, PhysicsEventPayload, ClickEventPayload } from './tools/prefabeditor/GameEvents';
35
33
  export { loadFiles } from './tools/dragdrop/DragDropLoader';
36
34
  export type { AssetLoadOptions } from './tools/dragdrop/DragDropLoader';
37
35
  export { loadModel, loadSound, loadTexture } from './tools/dragdrop/modelLoader';
package/dist/index.js CHANGED
@@ -10,8 +10,8 @@ export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
10
10
  export { useEditorContext } from './tools/prefabeditor/PrefabEditor';
11
11
  // Prefab Editor - Store & Scene API
12
12
  export { createPrefabStore, prefabStoreToPrefab, usePrefabStoreApi } from './tools/prefabeditor/prefabStore';
13
- export { createScene } from './tools/prefabeditor/scene';
14
13
  export { denormalizePrefab } from './tools/prefabeditor/prefab';
14
+ export { gameEvents, getEntityIdFromRigidBody, useClickEvent, useGameEvent, usePhysicsEvent } from './tools/prefabeditor/GameEvents';
15
15
  // Prefab Editor - Component Registry
16
16
  export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
17
17
  // Prefab Editor - Input Components
@@ -19,12 +19,10 @@ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Fiel
19
19
  // Prefab Editor - Utils
20
20
  export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
21
21
  export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
22
- export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/runtime';
22
+ export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/assetRuntime';
23
23
  export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
24
24
  export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
25
25
  export { float, positionLocal, sin, time, uniform, vec3, } from 'three/tsl';
26
- // Game Events (physics + custom events)
27
- export { gameEvents, useGameEvent, usePhysicsEvent, useClickEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
28
26
  // Asset Loading
29
27
  export { loadFiles } from './tools/dragdrop/DragDropLoader';
30
28
  export { loadModel, loadSound, loadTexture } from './tools/dragdrop/modelLoader';
@@ -1,128 +1,47 @@
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;
1
+ export type GameEventHandler<TPayload = unknown> = (payload: TPayload) => void;
2
+ export type PhysicsEventPayload = {
3
+ sourceEntityId?: string;
4
+ sourceNodeId?: string;
5
+ sourceObject?: unknown;
6
+ sourceRigidBody?: unknown;
7
+ targetEntityId?: string | null;
8
+ targetNodeId?: string | null;
9
+ targetObject?: unknown;
10
+ targetRigidBody?: unknown;
11
+ rapierEvent?: unknown;
12
+ };
13
+ export type ClickEventPayload = {
14
+ sourceEntityId?: string;
15
+ sourceNodeId?: string;
13
16
  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
- */
17
+ nodeId?: string;
18
+ node?: unknown;
19
+ object?: unknown;
20
+ point?: [number, number, number];
21
+ button?: number;
22
+ altKey?: boolean;
23
+ ctrlKey?: boolean;
24
+ metaKey?: boolean;
25
+ shiftKey?: boolean;
26
+ r3fEvent?: unknown;
27
+ };
31
28
  export interface GameEventMap {
32
29
  'sensor:enter': PhysicsEventPayload;
33
30
  'sensor:exit': PhysicsEventPayload;
34
31
  'collision:enter': PhysicsEventPayload;
35
32
  'collision:exit': PhysicsEventPayload;
36
- 'click': ClickEventPayload;
33
+ click: ClickEventPayload;
34
+ [eventType: string]: unknown;
37
35
  }
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
36
  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
- */
37
+ emit<TType extends string>(type: TType, payload: TType extends keyof GameEventMap ? GameEventMap[TType] : unknown): void;
38
+ on<TType extends string>(type: TType, handler: GameEventHandler<TType extends keyof GameEventMap ? GameEventMap[TType] : unknown>): () => void;
89
39
  clear(): void;
90
- /**
91
- * Check if an event type has any subscribers
92
- */
93
40
  hasListeners(type: string): boolean;
94
41
  };
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 {};
42
+ export declare function useGameEvent<TType extends string>(type: TType, handler: GameEventHandler<TType extends keyof GameEventMap ? GameEventMap[TType] : unknown>, deps?: React.DependencyList): void;
43
+ export declare function usePhysicsEvent<TType extends string>(type: TType, handler: GameEventHandler<TType extends keyof GameEventMap ? GameEventMap[TType] : unknown>, deps?: React.DependencyList): void;
44
+ export declare function useClickEvent<TType extends string>(type: TType, handler: GameEventHandler<TType extends keyof GameEventMap ? GameEventMap[TType] : unknown>, deps?: React.DependencyList): void;
45
+ export declare function getEntityIdFromRigidBody(rigidBody: {
46
+ userData?: unknown;
47
+ } | null | undefined): string | null;
@@ -1,118 +1,66 @@
1
- import { useEffect, useCallback } from 'react';
2
- // Internal subscriber storage
1
+ import { useCallback, useEffect } from 'react';
3
2
  const subscribers = new Map();
4
- function emitGameEvent(type, payload) {
5
- const handlers = subscribers.get(type);
6
- if (handlers) {
3
+ export const gameEvents = {
4
+ emit(type, payload) {
5
+ const trimmedType = type.trim();
6
+ if (!trimmedType)
7
+ return;
8
+ const handlers = subscribers.get(trimmedType);
9
+ if (!handlers)
10
+ return;
7
11
  handlers.forEach(handler => {
8
12
  try {
9
13
  handler(payload);
10
14
  }
11
- catch (e) {
12
- console.error(`Error in gameEvents handler for ${type}:`, e);
15
+ catch (error) {
16
+ console.error(`Error in gameEvents handler for ${trimmedType}:`, error);
13
17
  }
14
18
  });
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
- */
19
+ },
20
+ on(type, handler) {
21
+ const trimmedType = type.trim();
22
+ if (!trimmedType) {
23
+ return () => { };
24
+ }
25
+ let handlers = subscribers.get(trimmedType);
26
+ if (!handlers) {
27
+ handlers = new Set();
28
+ subscribers.set(trimmedType, handlers);
29
+ }
30
+ handlers.add(handler);
31
+ return () => {
32
+ const currentHandlers = subscribers.get(trimmedType);
33
+ if (!currentHandlers)
34
+ return;
35
+ currentHandlers.delete(handler);
36
+ if (currentHandlers.size === 0) {
37
+ subscribers.delete(trimmedType);
38
+ }
39
+ };
40
+ },
77
41
  clear() {
78
42
  subscribers.clear();
79
43
  },
80
- /**
81
- * Check if an event type has any subscribers
82
- */
83
44
  hasListeners(type) {
84
45
  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
- }
46
+ return ((_b = (_a = subscribers.get(type.trim())) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0) > 0;
47
+ },
87
48
  };
88
49
  export function useGameEvent(type, handler, deps = []) {
89
- useTypedGameEvent(type, handler, deps);
50
+ // eslint-disable-next-line react-hooks/exhaustive-deps
51
+ const stableHandler = useCallback(handler, deps);
52
+ useEffect(() => {
53
+ return gameEvents.on(type, stableHandler);
54
+ }, [type, stableHandler]);
90
55
  }
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
56
  export function usePhysicsEvent(type, handler, deps = []) {
96
- useTypedGameEvent(type, handler, deps);
57
+ useGameEvent(type, handler, deps);
97
58
  }
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
59
  export function useClickEvent(type, handler, deps = []) {
103
- useTypedGameEvent(type, handler, deps);
60
+ useGameEvent(type, handler, deps);
104
61
  }
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
62
  export function getEntityIdFromRigidBody(rigidBody) {
113
63
  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;
64
+ const entityId = (_a = rigidBody === null || rigidBody === void 0 ? void 0 : rigidBody.userData) === null || _a === void 0 ? void 0 : _a.entityId;
65
+ return typeof entityId === 'string' ? entityId : null;
118
66
  }
@@ -12,8 +12,6 @@ export declare function getRepeatAxesFromModelProperties(properties: Record<stri
12
12
  export type InstanceData = {
13
13
  id: string;
14
14
  sourceId: string;
15
- clickable?: boolean;
16
- clickEventName?: string;
17
15
  locked?: boolean;
18
16
  position: [number, number, number];
19
17
  rotation: [number, number, number];
@@ -35,8 +33,6 @@ export declare function useInstanceCheck(id: string): boolean;
35
33
  export declare const GameInstance: React.ForwardRefExoticComponent<{
36
34
  id: string;
37
35
  sourceId?: string;
38
- clickable?: boolean;
39
- clickEventName?: string;
40
36
  modelUrl: string;
41
37
  locked?: boolean;
42
38
  position: [number, number, number];