react-three-game 0.0.86 → 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.
package/dist/index.d.ts CHANGED
@@ -11,13 +11,15 @@ 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
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';
14
16
  export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
15
17
  export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from './tools/prefabeditor/components/Input';
16
18
  export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
17
19
  export type { ExportGLBOptions } from './tools/prefabeditor/utils';
18
20
  export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
19
21
  export type { PrefabEditorProps, PrefabEditorRef, } from './tools/prefabeditor/PrefabEditor';
20
- export type { SpawnOptions, } from './tools/prefabeditor/scene';
22
+ export type { Entity, EntityComponent, EntityData, EntityUpdate, PropertyPath, Scene, SceneUpdates, SpawnOptions, } from './tools/prefabeditor/scene';
21
23
  export type { PrefabRootProps } from './tools/prefabeditor/PrefabRoot';
22
24
  export type { AssetRuntime, EntityRuntime, LiveObjectRef, LiveRigidBodyRef } from './tools/prefabeditor/assetRuntime';
23
25
  export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/assetRuntime';
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ export { useEditorContext } from './tools/prefabeditor/PrefabEditor';
11
11
  // Prefab Editor - Store & Scene API
12
12
  export { createPrefabStore, prefabStoreToPrefab, usePrefabStoreApi } from './tools/prefabeditor/prefabStore';
13
13
  export { denormalizePrefab } from './tools/prefabeditor/prefab';
14
+ export { gameEvents, getEntityIdFromRigidBody, useClickEvent, useGameEvent, usePhysicsEvent } from './tools/prefabeditor/GameEvents';
14
15
  // Prefab Editor - Component Registry
15
16
  export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
16
17
  // Prefab Editor - Input Components
@@ -0,0 +1,47 @@
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;
16
+ instanceEntityId?: string;
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
+ };
28
+ export interface GameEventMap {
29
+ 'sensor:enter': PhysicsEventPayload;
30
+ 'sensor:exit': PhysicsEventPayload;
31
+ 'collision:enter': PhysicsEventPayload;
32
+ 'collision:exit': PhysicsEventPayload;
33
+ click: ClickEventPayload;
34
+ [eventType: string]: unknown;
35
+ }
36
+ export declare const gameEvents: {
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;
39
+ clear(): void;
40
+ hasListeners(type: string): boolean;
41
+ };
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;
@@ -0,0 +1,66 @@
1
+ import { useCallback, useEffect } from 'react';
2
+ const subscribers = new Map();
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;
11
+ handlers.forEach(handler => {
12
+ try {
13
+ handler(payload);
14
+ }
15
+ catch (error) {
16
+ console.error(`Error in gameEvents handler for ${trimmedType}:`, error);
17
+ }
18
+ });
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
+ },
41
+ clear() {
42
+ subscribers.clear();
43
+ },
44
+ hasListeners(type) {
45
+ var _a, _b;
46
+ return ((_b = (_a = subscribers.get(type.trim())) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0) > 0;
47
+ },
48
+ };
49
+ export function useGameEvent(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]);
55
+ }
56
+ export function usePhysicsEvent(type, handler, deps = []) {
57
+ useGameEvent(type, handler, deps);
58
+ }
59
+ export function useClickEvent(type, handler, deps = []) {
60
+ useGameEvent(type, handler, deps);
61
+ }
62
+ export function getEntityIdFromRigidBody(rigidBody) {
63
+ var _a;
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;
66
+ }
@@ -24,6 +24,7 @@ import { composeTransform, decompose } from "./utils";
24
24
  import { isPhysicsProps } from "./components/PhysicsComponent";
25
25
  import { createPrefabStore, PrefabStoreProvider, useOptionalPrefabStoreApi, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
26
26
  import { AssetRuntimeContext, EntityRuntimeScope } from "./assetRuntime";
27
+ import { gameEvents } from "./GameEvents";
27
28
  import { sound as soundManager } from "../../helpers/SoundManager";
28
29
  builtinComponents.forEach(registerComponent);
29
30
  const IDENTITY = new Matrix4();
@@ -44,12 +45,6 @@ function isNodeReady(node, loadedModels) {
44
45
  return true;
45
46
  return Boolean(loadedModels[model.properties.filename]);
46
47
  }
47
- function emitNativeEvent(type, detail) {
48
- const trimmedType = type === null || type === void 0 ? void 0 : type.trim();
49
- if (!trimmedType || typeof window === 'undefined')
50
- return;
51
- window.dispatchEvent(new CustomEvent(trimmedType, { detail }));
52
- }
53
48
  function getNodeClickEventName(node) {
54
49
  var _a;
55
50
  const clickComponents = [
@@ -214,12 +209,16 @@ function StoreRootNode(props) {
214
209
  }
215
210
  function emitNodePointerEvent(eventName, event, nodeId, node, fallbackObject) {
216
211
  var _a;
217
- if (!eventName)
212
+ const trimmedEventName = eventName === null || eventName === void 0 ? void 0 : eventName.trim();
213
+ if (!trimmedEventName)
218
214
  return;
219
- emitNativeEvent(eventName, {
215
+ gameEvents.emit(trimmedEventName, {
216
+ sourceEntityId: nodeId,
217
+ sourceNodeId: nodeId,
220
218
  nodeId,
221
219
  node,
222
220
  object: (_a = event.object) !== null && _a !== void 0 ? _a : fallbackObject,
221
+ point: [event.point.x, event.point.y, event.point.z],
223
222
  button: event.button,
224
223
  altKey: event.nativeEvent.altKey,
225
224
  ctrlKey: event.nativeEvent.ctrlKey,
@@ -13,6 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
13
13
  import { CapsuleCollider, RigidBody, useRapier } from "@react-three/rapier";
14
14
  import { useCallback, useEffect, useRef } from 'react';
15
15
  import { useAssetRuntime, useEntityRuntime } from "../assetRuntime";
16
+ import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
16
17
  import { usePrefabNode } from "../prefabStore";
17
18
  import { BooleanField, FieldGroup, NumberField, SelectField, StringField, Vector3Field } from "./Input";
18
19
  import { getNodeUserData } from "../types";
@@ -80,16 +81,6 @@ function PhysicsComponentEditor({ component, onUpdate }) {
80
81
  { value: 'all', label: 'All (includes kinematic & fixed)' },
81
82
  ] })] }));
82
83
  }
83
- function emitNativeEvent(type, detail) {
84
- const trimmedType = type === null || type === void 0 ? void 0 : type.trim();
85
- if (!trimmedType || typeof window === 'undefined')
86
- return;
87
- window.dispatchEvent(new CustomEvent(trimmedType, { detail }));
88
- }
89
- function getEntityIdFromRigidBody(rigidBody) {
90
- const userData = rigidBody === null || rigidBody === void 0 ? void 0 : rigidBody.userData;
91
- return typeof (userData === null || userData === void 0 ? void 0 : userData.entityId) === 'string' ? userData.entityId : null;
92
- }
93
84
  function PhysicsComponentView({ properties, children, position, rotation, scale }) {
94
85
  var _a, _b, _c;
95
86
  const { registerRigidBodyRef } = useAssetRuntime();
@@ -157,11 +148,17 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
157
148
  var _a, _b, _c;
158
149
  if (!nodeId)
159
150
  return;
160
- emitNativeEvent(eventType, {
151
+ const trimmedEventType = eventType === null || eventType === void 0 ? void 0 : eventType.trim();
152
+ if (!trimmedEventType)
153
+ return;
154
+ const targetEntityId = getEntityIdFromRigidBody(payload.other.rigidBody);
155
+ gameEvents.emit(trimmedEventType, {
156
+ sourceEntityId: nodeId,
161
157
  sourceNodeId: nodeId,
162
158
  sourceObject: getObject(),
163
159
  sourceRigidBody: rigidBodyRef.current,
164
- targetNodeId: getEntityIdFromRigidBody(payload.other.rigidBody),
160
+ targetEntityId,
161
+ targetNodeId: targetEntityId,
165
162
  targetObject: (_b = (_a = payload.other.rigidBodyObject) !== null && _a !== void 0 ? _a : payload.other.colliderObject) !== null && _b !== void 0 ? _b : null,
166
163
  targetRigidBody: (_c = payload.other.rigidBody) !== null && _c !== void 0 ? _c : null,
167
164
  rapierEvent: payload,
@@ -4,6 +4,7 @@ import { useThree } from '@react-three/fiber';
4
4
  import { SoundPicker } from '../../assetviewer/page';
5
5
  import { sound as soundManager } from '../../../helpers/SoundManager';
6
6
  import { useAssetRuntime, useEntityRuntime } from '../assetRuntime';
7
+ import { gameEvents } from '../GameEvents';
7
8
  import { BooleanField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField, StringField } from './Input';
8
9
  import { colors } from '../styles';
9
10
  import { AudioListener } from 'three';
@@ -62,6 +63,21 @@ function pickClip(paths, mode, sequenceIndexRef) {
62
63
  }
63
64
  return paths[Math.floor(Math.random() * paths.length)];
64
65
  }
66
+ function payloadMatchesNode(nodeId, payload) {
67
+ if (!nodeId || !payload || typeof payload !== 'object') {
68
+ return true;
69
+ }
70
+ const eventPayload = payload;
71
+ const relatedNodeIds = [
72
+ eventPayload.nodeId,
73
+ eventPayload.sourceEntityId,
74
+ eventPayload.sourceNodeId,
75
+ eventPayload.targetEntityId,
76
+ eventPayload.targetNodeId,
77
+ eventPayload.instanceEntityId,
78
+ ].filter((value) => typeof value === 'string');
79
+ return relatedNodeIds.length > 0 ? relatedNodeIds.includes(nodeId) : true;
80
+ }
65
81
  function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
66
82
  const clips = Array.isArray(component.properties.clips)
67
83
  ? component.properties.clips.map((clip) => typeof clip === 'string' ? clip : '')
@@ -156,17 +172,12 @@ function SoundComponentView({ properties, children }) {
156
172
  audio.setDistanceModel(distanceModel);
157
173
  }, [distanceModel, maxDistance, refDistance, rolloffFactor]);
158
174
  useEffect(() => {
159
- if (editMode || paths.length === 0 || !eventName || typeof window === 'undefined') {
175
+ if (editMode || paths.length === 0 || !eventName) {
160
176
  return;
161
177
  }
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
- }
178
+ return gameEvents.on(eventName, (payload) => {
179
+ if (!payloadMatchesNode(nodeId, payload)) {
180
+ return;
170
181
  }
171
182
  const clip = pickClip(paths, mode, sequenceIndexRef);
172
183
  if (!clip)
@@ -200,11 +211,7 @@ function SoundComponentView({ properties, children }) {
200
211
  audio.setPlaybackRate(pitch);
201
212
  audio.setVolume(volume);
202
213
  audio.play();
203
- };
204
- window.addEventListener(eventName, handleEvent);
205
- return () => {
206
- window.removeEventListener(eventName, handleEvent);
207
- };
214
+ });
208
215
  }, [editMode, eventName, getSound, mode, nodeId, paths, positional, properties]);
209
216
  return (_jsxs(_Fragment, { children: [positional && listenerRef.current ? _jsx("positionalAudio", { ref: positionalAudioRef, args: [listenerRef.current] }) : null, children] }));
210
217
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.86",
3
+ "version": "0.0.87",
4
4
  "description": "high performance 3D game engine built in React",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",