react-three-game 0.0.85 → 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 (38) hide show
  1. package/README.md +87 -35
  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 +94 -44
  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.js +4 -2
  12. package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
  13. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +0 -3
  14. package/dist/tools/prefabeditor/components/DataComponent.d.ts +3 -0
  15. package/dist/tools/prefabeditor/components/DataComponent.js +87 -0
  16. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +1 -1
  17. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -1
  18. package/dist/tools/prefabeditor/components/GeometryComponent.js +4 -2
  19. package/dist/tools/prefabeditor/components/Input.d.ts +2 -13
  20. package/dist/tools/prefabeditor/components/Input.js +0 -55
  21. package/dist/tools/prefabeditor/components/MaterialComponent.js +1 -1
  22. package/dist/tools/prefabeditor/components/ModelComponent.js +3 -3
  23. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +4 -0
  24. package/dist/tools/prefabeditor/components/PhysicsComponent.js +69 -132
  25. package/dist/tools/prefabeditor/components/PointLightComponent.js +1 -1
  26. package/dist/tools/prefabeditor/components/SoundComponent.js +17 -17
  27. package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
  28. package/dist/tools/prefabeditor/components/index.js +2 -2
  29. package/dist/tools/prefabeditor/types.d.ts +1 -0
  30. package/dist/tools/prefabeditor/types.js +18 -0
  31. package/dist/tools/prefabeditor/usePointerEvents.d.ts +27 -0
  32. package/dist/tools/prefabeditor/usePointerEvents.js +52 -0
  33. package/package.json +1 -1
  34. package/dist/tools/prefabeditor/GameEvents.d.ts +0 -128
  35. package/dist/tools/prefabeditor/GameEvents.js +0 -118
  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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.85",
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;
@@ -1,184 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useMemo } from "react";
3
- import { getComponentDef } from "./components/ComponentRegistry";
4
- import { createScene } from "./scene";
5
- export const AssetRuntimeContext = createContext(null);
6
- const EntityRuntimeContext = createContext(null);
7
- export function useAssetRuntime() {
8
- const ctx = useContext(AssetRuntimeContext);
9
- if (!ctx)
10
- throw new Error("useAssetRuntime must be used inside <PrefabRoot>");
11
- return ctx;
12
- }
13
- export function useEntityRuntime() {
14
- const ctx = useContext(EntityRuntimeContext);
15
- if (!ctx)
16
- throw new Error("useEntityRuntime must be used inside a component View rendered by <PrefabRoot>");
17
- return ctx;
18
- }
19
- export function useEntityObjectRef() {
20
- const { getObject } = useEntityRuntime();
21
- return useMemo(() => ({ get current() { return getObject(); } }), [getObject]);
22
- }
23
- export function useEntityRigidBodyRef() {
24
- const { getRigidBody } = useEntityRuntime();
25
- return useMemo(() => ({ get current() { return getRigidBody(); } }), [getRigidBody]);
26
- }
27
- export function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }) {
28
- const asset = useContext(AssetRuntimeContext);
29
- if (!asset)
30
- throw new Error("EntityRuntimeScope must be used inside <PrefabRoot>");
31
- const value = useMemo(() => ({
32
- nodeId,
33
- editMode,
34
- isSelected,
35
- getObject: () => asset.getObject(nodeId),
36
- getRigidBody: () => asset.getRigidBody(nodeId),
37
- }), [asset, editMode, isSelected, nodeId]);
38
- return _jsx(EntityRuntimeContext.Provider, { value: value, children: children });
39
- }
40
- export function createRuntimeEngine({ store, getObject, getRigidBody, }) {
41
- const scene = createScene({
42
- getRootId: () => store.getState().rootId,
43
- getNode: (id) => { var _a; return (_a = store.getState().nodesById[id]) !== null && _a !== void 0 ? _a : null; },
44
- getChildIds: (id) => { var _a; return (_a = store.getState().childIdsById[id]) !== null && _a !== void 0 ? _a : []; },
45
- getParentId: (id) => { var _a; return (_a = store.getState().parentIdById[id]) !== null && _a !== void 0 ? _a : null; },
46
- updateNode: (id, update) => store.getState().updateNode(id, update),
47
- updateNodes: (updates) => {
48
- store.getState().updateNodes(Object.entries(updates).map(([id, update]) => ({ id, update })));
49
- },
50
- addNode: (node, options) => {
51
- var _a;
52
- const parentId = (_a = options === null || options === void 0 ? void 0 : options.parentId) !== null && _a !== void 0 ? _a : store.getState().rootId;
53
- store.getState().addChild(parentId, node);
54
- return node.id;
55
- },
56
- removeNode: (id) => store.getState().deleteNode(id),
57
- getObject,
58
- getRigidBody,
59
- });
60
- // key = `${nodeId}:${componentKey}:${componentType}`
61
- const instances = new Map();
62
- let active = false;
63
- let dirty = true;
64
- let lastRevision = store.getState().revision;
65
- const unsubscribe = store.subscribe((state) => {
66
- if (state.revision === lastRevision)
67
- return;
68
- lastRevision = state.revision;
69
- dirty = true;
70
- });
71
- function destroy(key) {
72
- var _a, _b;
73
- const record = instances.get(key);
74
- if (!record)
75
- return;
76
- try {
77
- (_b = (_a = record.instance).destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
78
- }
79
- catch (error) {
80
- console.error(`[runtime] destroy ${key}`, error);
81
- }
82
- instances.delete(key);
83
- }
84
- function sync() {
85
- const state = store.getState();
86
- const live = new Set();
87
- let hasPendingMount = false;
88
- const visit = (nodeId) => {
89
- var _a, _b, _c;
90
- const node = state.nodesById[nodeId];
91
- if (!node)
92
- return;
93
- if (!node.disabled && node.components) {
94
- for (const componentKey in node.components) {
95
- const data = node.components[componentKey];
96
- if (!(data === null || data === void 0 ? void 0 : data.type))
97
- continue;
98
- const def = getComponentDef(data.type);
99
- if (!(def === null || def === void 0 ? void 0 : def.create))
100
- continue;
101
- const key = `${nodeId}:${componentKey}:${data.type}`;
102
- live.add(key);
103
- const object = getObject(nodeId);
104
- if (!object) {
105
- hasPendingMount = true;
106
- continue;
107
- }
108
- const rigidBody = getRigidBody(nodeId);
109
- const existing = instances.get(key);
110
- if (existing && existing.object === object && existing.rigidBody === rigidBody) {
111
- continue;
112
- }
113
- if (existing) {
114
- destroy(key);
115
- }
116
- const entity = scene.find(nodeId);
117
- const component = entity === null || entity === void 0 ? void 0 : entity.getComponent(componentKey);
118
- if (!entity || !component)
119
- continue;
120
- let instance;
121
- try {
122
- instance = (_a = def.create({ scene, component, object, rigidBody })) !== null && _a !== void 0 ? _a : {};
123
- }
124
- catch (error) {
125
- console.error(`[runtime] create ${key}`, error);
126
- continue;
127
- }
128
- instances.set(key, { instance, object, rigidBody });
129
- try {
130
- (_b = instance.start) === null || _b === void 0 ? void 0 : _b.call(instance);
131
- }
132
- catch (error) {
133
- console.error(`[runtime] start ${key}`, error);
134
- }
135
- }
136
- }
137
- for (const childId of (_c = state.childIdsById[nodeId]) !== null && _c !== void 0 ? _c : [])
138
- visit(childId);
139
- };
140
- visit(state.rootId);
141
- for (const key of Array.from(instances.keys())) {
142
- if (!live.has(key))
143
- destroy(key);
144
- }
145
- dirty = hasPendingMount;
146
- }
147
- return {
148
- tick(dt) {
149
- if (!active)
150
- return;
151
- if (dirty)
152
- sync();
153
- for (const [key, record] of instances) {
154
- if (!record.instance.update)
155
- continue;
156
- try {
157
- record.instance.update(dt);
158
- }
159
- catch (error) {
160
- console.error(`[runtime] update ${key}`, error);
161
- }
162
- }
163
- },
164
- setActive(nextActive) {
165
- if (active === nextActive)
166
- return;
167
- active = nextActive;
168
- dirty = true;
169
- if (!active) {
170
- for (const key of Array.from(instances.keys()))
171
- destroy(key);
172
- }
173
- },
174
- invalidate() {
175
- dirty = true;
176
- },
177
- dispose() {
178
- active = false;
179
- for (const key of Array.from(instances.keys()))
180
- destroy(key);
181
- unsubscribe();
182
- },
183
- };
184
- }