react-three-game 0.0.93 → 0.0.94

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/README.md CHANGED
@@ -152,7 +152,7 @@ interface GameObject {
152
152
 
153
153
  ## Runtime Mutation
154
154
 
155
- Use editor or root refs for scene-native object access, and use the editor mutation methods for authored data changes.
155
+ Use the editor or root ref for scene-native object access, and the `Scene` mutation methods for authored data changes.
156
156
 
157
157
  ```tsx
158
158
  import { useEffect, useRef } from "react";
@@ -162,7 +162,7 @@ function RaiseBall() {
162
162
  const editorRef = useRef<PrefabEditorRef>(null);
163
163
 
164
164
  useEffect(() => {
165
- editorRef.current?.updateNode("ball", (node) => ({
165
+ editorRef.current?.update("ball", (node) => ({
166
166
  ...node,
167
167
  components: {
168
168
  ...node.components,
@@ -184,29 +184,33 @@ function RaiseBall() {
184
184
  For live Three.js access, use mounted objects directly:
185
185
 
186
186
  ```tsx
187
- const ball = editorRef.current?.getNodeObject("ball");
187
+ const ball = editorRef.current?.getObject("ball");
188
188
  ball?.rotateY(0.5);
189
189
  ```
190
190
 
191
- For runtime integrations that need edit-time re-sync, subscribe to authored scene changes:
191
+ For runtime integrations that need to react to authored scene changes, subscribe through the prefab store:
192
192
 
193
193
  ```tsx
194
- const stop = editorRef.current?.onSceneChange((revision) => {
195
- console.log("scene changed", revision);
196
- });
194
+ import { usePrefabStoreApi } from "react-three-game";
197
195
 
198
- stop?.();
196
+ const store = usePrefabStoreApi();
197
+ const stop = store.subscribe(
198
+ (s) => s.nodesById,
199
+ (next, prev) => console.log("scene changed", next, prev),
200
+ );
201
+
202
+ stop();
199
203
  ```
200
204
 
201
- For runtime-owned imperative state, use node-local handles instead of reaching for ad hoc globals:
205
+ For runtime-owned imperative state, register node-local handles instead of reaching for ad hoc globals:
202
206
 
203
207
  ```tsx
204
208
  import { useEffect } from "react";
205
- import { useAssetRuntime, useCurrentNode, useCurrentNodeHandle } from "react-three-game";
209
+ import { useAssetRuntime, useNode, useNodeHandle } from "react-three-game";
206
210
 
207
211
  function SpinnerView({ children }: { children?: React.ReactNode }) {
208
- const { nodeId } = useCurrentNode();
209
- const { registerNodeHandle } = useAssetRuntime();
212
+ const { nodeId } = useNode();
213
+ const { registerHandle } = useAssetRuntime();
210
214
 
211
215
  useEffect(() => {
212
216
  const handle = {
@@ -215,15 +219,15 @@ function SpinnerView({ children }: { children?: React.ReactNode }) {
215
219
  },
216
220
  };
217
221
 
218
- registerNodeHandle(nodeId, "spinner", handle);
219
- return () => registerNodeHandle(nodeId, "spinner", null);
220
- }, [nodeId, registerNodeHandle]);
222
+ registerHandle(nodeId, "spinner", handle);
223
+ return () => registerHandle(nodeId, "spinner", null);
224
+ }, [nodeId, registerHandle]);
221
225
 
222
226
  return <>{children}</>;
223
227
  }
224
228
 
225
229
  function SpinnerStatus() {
226
- const spinnerRef = useCurrentNodeHandle<{ setSpeed: (next: number) => void }>("spinner");
230
+ const spinnerRef = useNodeHandle<{ setSpeed: (next: number) => void }>("spinner");
227
231
 
228
232
  useEffect(() => {
229
233
  spinnerRef.current?.setSpeed(2);
@@ -246,11 +250,11 @@ const playerByName = editorRef.current?.root?.getObjectByName("Player");
246
250
  const playerById = editorRef.current?.root?.getObjectByProperty("userData.prefabNodeId", "player");
247
251
  ```
248
252
 
249
- Treat names as a convenience surface, not the primary lookup key:
253
+ Treat names as a convenience surface, with stable ids as the primary lookup key:
250
254
 
255
+ * `editorRef.current?.getObject(id)` is the clearest stable authored-node lookup
251
256
  * names are not guaranteed unique
252
- * `getNodeObject(id)` is the clearest stable authored-node lookup
253
- * traversal metadata is applied to the prefab node transform object, not necessarily the inner mesh or model child
257
+ * traversal metadata is applied to the prefab node transform object — the inner mesh or model child is one level deeper
254
258
 
255
259
  You can author extra `userData` from the editor with a `Data` component:
256
260
 
@@ -264,67 +268,21 @@ You can author extra `userData` from the editor with a `Data` component:
264
268
  }
265
269
  ```
266
270
 
267
- For batched authored updates, write through the store once:
268
-
269
- ```tsx
270
- editorRef.current?.updateNodes([
271
- {
272
- id: "orb1",
273
- update: (node) => ({
274
- ...node,
275
- components: {
276
- ...node.components,
277
- transform: {
278
- ...node.components?.transform,
279
- type: "Transform",
280
- properties: {
281
- ...node.components?.transform?.properties,
282
- position: [1, 0, 0],
283
- },
284
- },
285
- },
286
- }),
287
- },
288
- {
289
- id: "orb2",
290
- update: (node) => ({
291
- ...node,
292
- components: {
293
- ...node.components,
294
- transform: {
295
- ...node.components?.transform,
296
- type: "Transform",
297
- properties: {
298
- ...node.components?.transform?.properties,
299
- position: [-1, 0, 0],
300
- },
301
- },
302
- },
303
- }),
304
- },
305
- ]);
306
- ```
307
-
308
- Custom component `View`s should use normal React and R3F behavior, such as `useFrame`, refs, and native Three.js APIs.
271
+ Custom component `View`s use normal React and R3F behavior — `useFrame`, refs, and native Three.js APIs.
309
272
 
310
273
  ## Useful Exports
311
274
 
312
- * `GameCanvas`
313
- * `PrefabRoot`
314
- * `PrefabEditor`
315
- * `PrefabEditorMode`
316
- * `Prefab`
317
- * `GameObject`
318
- * `registerComponent`
319
- * `useAssetRuntime()` / `useCurrentNode()`
320
- * `useCurrentNodeObject()` / `useCurrentNodeHandle()`
321
- * `ground(...)`
322
- * `gameEvents` / `useGameEvent()` / `useClickEvent()`
323
- * `loadJson()` / `saveJson()`
324
- * `loadModel()` / `loadTexture()`
325
- * `loadSound()` / `loadFiles()`
326
- * `exportGLB()`
327
- * `computeParentWorldMatrix()`
275
+ * `GameCanvas`, `PrefabRoot`, `PrefabEditor`, `PrefabEditorMode`
276
+ * `Prefab`, `GameObject`, `ComponentData`, `PrefabNode`, `PrefabEditorRef`, `Scene`
277
+ * `registerComponent`, `Component`, `ComponentViewProps`, `FieldDefinition`
278
+ * `useScene`, `useEditorRef`, `useEditorContext`
279
+ * `useNode`, `useNodeObject`, `useNodeHandle`, `useAssetRuntime`
280
+ * `usePrefabStore`, `usePrefabStoreApi`
281
+ * `gameEvents`, `useGameEvent`, `useClickEvent`
282
+ * `loadJson`, `saveJson`, `loadFiles`, `loadModel`, `loadTexture`, `loadSound`
283
+ * `exportGLB`, `exportGLBData`, `regenerateIds`, `computeParentWorldMatrix`
284
+ * `ground`, `soundManager`
285
+ * `FieldRenderer`, `Vector3Field`, `NumberField`, `StringField`, `BooleanField`, `SelectField`, `ColorField`
328
286
 
329
287
  ## Development
330
288
 
package/dist/index.d.ts CHANGED
@@ -4,10 +4,12 @@ export { ground } from './helpers';
4
4
  export type { GroundOptions, Vec3 } from './helpers';
5
5
  export { sound as soundManager } from './helpers/SoundManager';
6
6
  export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
7
- export { PrefabEditorMode } from './tools/prefabeditor/PrefabEditor';
7
+ export { PrefabEditorMode } from './tools/prefabeditor/PrefabRoot';
8
8
  export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
9
- export { useEditorContext } from './tools/prefabeditor/PrefabEditor';
9
+ export { useEditorContext, useEditorRef } from './tools/prefabeditor/PrefabEditor';
10
10
  export type { EditorContextType } from './tools/prefabeditor/PrefabEditor';
11
+ export { usePrefabStore, usePrefabStoreApi } from './tools/prefabeditor/prefabStore';
12
+ export type { PrefabStoreApi, PrefabStoreState } from './tools/prefabeditor/prefabStore';
11
13
  export { denormalizePrefab } from './tools/prefabeditor/prefab';
12
14
  export { gameEvents, useClickEvent, useGameEvent } from './tools/prefabeditor/GameEvents';
13
15
  export type { ClickEventPayload, ContactEventPayload, GameEventHandler, GameEventMap } from './tools/prefabeditor/GameEvents';
@@ -16,10 +18,12 @@ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Fiel
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
- export type { PrefabEditorProps, PrefabNode, PrefabEditorRef, SpawnOptions, } from './tools/prefabeditor/PrefabEditor';
21
+ export type { PrefabEditorProps, PrefabNode, PrefabEditorRef, } from './tools/prefabeditor/PrefabEditor';
20
22
  export type { PrefabRootProps } from './tools/prefabeditor/PrefabRoot';
21
- export type { AssetRuntime, CurrentNodeRuntime, LiveHandleRef, LiveObjectRef, CurrentNodeHandleRef, CurrentNodeObjectRef } from './tools/prefabeditor/assetRuntime';
22
- export { useAssetRuntime, useCurrentNode, useCurrentNodeHandle, useCurrentNodeObject } from './tools/prefabeditor/assetRuntime';
23
+ export type { AssetRuntime, NodeApi, LiveRef } from './tools/prefabeditor/assetRuntime';
24
+ export { useAssetRuntime, useNode, useNodeHandle, useNodeObject } from './tools/prefabeditor/assetRuntime';
25
+ export type { Scene } from './tools/prefabeditor/PrefabRoot';
26
+ export { useScene } from './tools/prefabeditor/PrefabRoot';
23
27
  export type { Component, ComponentViewProps } from './tools/prefabeditor/components/ComponentRegistry';
24
28
  export type { FieldDefinition, FieldType } from './tools/prefabeditor/components/Input';
25
29
  export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
@@ -32,4 +36,3 @@ export type { AssetLoadOptions } from './tools/dragdrop/DragDropLoader';
32
36
  export { loadModel, loadSound, loadTexture } from './tools/dragdrop/modelLoader';
33
37
  export type { LoadedModel, LoadedModels, ModelLoadResult, LoadedSound, LoadedSounds, SoundLoadResult, LoadedTexture, LoadedTextures, TextureLoadResult, ProgressCallback, } from './tools/dragdrop/modelLoader';
34
38
  export { ModelListViewer, SoundListViewer, ModelPicker, SoundPicker, TextureListViewer, TexturePicker, SingleModelViewer, SingleSoundViewer, SingleTextureViewer, SharedCanvas, } from './tools/assetviewer/page';
35
- export type { PrefabRootRef } from './tools/prefabeditor/PrefabRoot';
package/dist/index.js CHANGED
@@ -5,9 +5,10 @@ export { ground } from './helpers';
5
5
  export { sound as soundManager } from './helpers/SoundManager';
6
6
  // Prefab Editor
7
7
  export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
8
- export { PrefabEditorMode } from './tools/prefabeditor/PrefabEditor';
8
+ export { PrefabEditorMode } from './tools/prefabeditor/PrefabRoot';
9
9
  export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
10
- export { useEditorContext } from './tools/prefabeditor/PrefabEditor';
10
+ export { useEditorContext, useEditorRef } from './tools/prefabeditor/PrefabEditor';
11
+ export { usePrefabStore, usePrefabStoreApi } from './tools/prefabeditor/prefabStore';
11
12
  // Prefab Editor - Data API
12
13
  export { denormalizePrefab } from './tools/prefabeditor/prefab';
13
14
  export { gameEvents, useClickEvent, useGameEvent } from './tools/prefabeditor/GameEvents';
@@ -18,7 +19,8 @@ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Fiel
18
19
  // Prefab Editor - Utils
19
20
  export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
20
21
  export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
21
- export { useAssetRuntime, useCurrentNode, useCurrentNodeHandle, useCurrentNodeObject } from './tools/prefabeditor/assetRuntime';
22
+ export { useAssetRuntime, useNode, useNodeHandle, useNodeObject } from './tools/prefabeditor/assetRuntime';
23
+ export { useScene } from './tools/prefabeditor/PrefabRoot';
22
24
  export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
23
25
  export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
24
26
  export { float, positionLocal, sin, time, uniform, vec3, } from 'three/tsl';
@@ -1,20 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { memo, useCallback, useState } from 'react';
3
3
  import { base, colors, tree } from './styles';
4
- import { useEditorContext } from './PrefabEditor';
4
+ import { useEditorContext, useEditorRef } from './PrefabEditor';
5
5
  import { Dropdown } from './Dropdown';
6
6
  import { FileMenu, TreeContextMenu, TreeNodeMenu } from './EditorTreeMenus';
7
7
  import { createEmptyNode } from './prefab';
8
8
  import { usePrefabChildIds, usePrefabNode, usePrefabRootId, usePrefabStore, usePrefabStoreApi } from './prefabStore';
9
9
  export default function EditorTree({ selectedId, setSelectedId, getPrefab, onReplacePrefab, onImportPrefab, onUndo, onRedo, canUndo, canRedo }) {
10
10
  const { onFocusNode } = useEditorContext();
11
+ const editor = useEditorRef();
11
12
  const rootId = usePrefabRootId();
12
13
  const store = usePrefabStoreApi();
13
- const addChild = usePrefabStore(state => state.addChild);
14
- const duplicateNode = usePrefabStore(state => state.duplicateNode);
15
- const deleteNode = usePrefabStore(state => state.deleteNode);
16
- const toggleNodeFlag = usePrefabStore(state => state.toggleNodeFlag);
17
- const moveNode = usePrefabStore(state => state.moveNode);
18
14
  const [draggedId, setDraggedId] = useState(null);
19
15
  const [dropTarget, setDropTarget] = useState(null);
20
16
  const [collapsedIds, setCollapsedIds] = useState(new Set());
@@ -31,30 +27,30 @@ export default function EditorTree({ selectedId, setSelectedId, getPrefab, onRep
31
27
  };
32
28
  const handleAddChild = (parentId) => {
33
29
  const newNode = createEmptyNode();
34
- addChild(parentId, newNode);
30
+ editor.add(newNode, parentId);
35
31
  setSelectedId(newNode.id);
36
32
  };
37
33
  const handleDuplicate = (nodeId) => {
38
34
  if (nodeId === rootId)
39
35
  return;
40
- const duplicatedId = duplicateNode(nodeId);
36
+ const duplicatedId = editor.duplicate(nodeId);
41
37
  if (duplicatedId)
42
38
  setSelectedId(duplicatedId);
43
39
  };
44
40
  const handleDelete = (nodeId) => {
45
41
  if (nodeId === rootId)
46
42
  return;
47
- deleteNode(nodeId);
43
+ editor.remove(nodeId);
48
44
  if (selectedId === nodeId)
49
45
  setSelectedId(null);
50
46
  };
51
47
  const handleToggleDisabled = (nodeId) => {
52
- toggleNodeFlag(nodeId, 'disabled');
48
+ editor.update(nodeId, n => (Object.assign(Object.assign({}, n), { disabled: !n.disabled })));
53
49
  };
54
50
  const handleToggleLocked = (nodeId) => {
55
51
  var _a;
56
52
  const willLock = !((_a = store.getState().nodesById[nodeId]) === null || _a === void 0 ? void 0 : _a.locked);
57
- toggleNodeFlag(nodeId, 'locked');
53
+ editor.update(nodeId, n => (Object.assign(Object.assign({}, n), { locked: !n.locked })));
58
54
  if (willLock && selectedId === nodeId)
59
55
  setSelectedId(null);
60
56
  };
@@ -99,7 +95,7 @@ export default function EditorTree({ selectedId, setSelectedId, getPrefab, onRep
99
95
  if (!draggedId || draggedId === targetId)
100
96
  return;
101
97
  e.preventDefault();
102
- moveNode(draggedId, targetId, getDropPosition(e, isRoot));
98
+ editor.move(draggedId, targetId, getDropPosition(e, isRoot));
103
99
  setDraggedId(null);
104
100
  setDropTarget(null);
105
101
  };
@@ -15,23 +15,23 @@ import { hasComponent } from "./types";
15
15
  import EditorTree from './EditorTree';
16
16
  import { getAllComponentDefs } from './components/ComponentRegistry';
17
17
  import { createComponentData } from './prefab';
18
+ import { useEditorRef } from './PrefabEditor';
18
19
  import { base, colors, inspector, componentCard } from './styles';
19
20
  import { usePrefabStore } from './prefabStore';
20
21
  function EditorUI({ selectedId, setSelectedId, getPrefab, onReplacePrefab, onImportPrefab, basePath, onUndo, onRedo, canUndo, canRedo }) {
21
22
  const [collapsed, setCollapsed] = useState(false);
22
23
  const rootId = usePrefabStore(state => state.rootId);
23
24
  const selectedNode = usePrefabStore(state => { var _a; return selectedId ? (_a = state.nodesById[selectedId]) !== null && _a !== void 0 ? _a : null : null; });
24
- const updateNode = usePrefabStore(state => state.updateNode);
25
- const deleteNode = usePrefabStore(state => state.deleteNode);
25
+ const editor = useEditorRef();
26
26
  const updateNodeHandler = (update) => {
27
27
  if (!selectedId)
28
28
  return;
29
- updateNode(selectedId, update);
29
+ editor.update(selectedId, update);
30
30
  };
31
31
  const deleteNodeHandler = () => {
32
32
  if (!selectedId || selectedId === rootId)
33
33
  return;
34
- deleteNode(selectedId);
34
+ editor.remove(selectedId);
35
35
  setSelectedId(null);
36
36
  };
37
37
  return _jsxs(_Fragment, { children: [_jsxs("div", { style: inspector.panel, children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { children: collapsed ? '◀' : '▼' })] }), !collapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNodeHandler, deleteNode: deleteNodeHandler, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: 8, left: 8, zIndex: 20 }, children: _jsx(EditorTree, { selectedId: selectedId, setSelectedId: setSelectedId, getPrefab: getPrefab, onReplacePrefab: onReplacePrefab, onImportPrefab: onImportPrefab, onUndo: onUndo, onRedo: onRedo, canUndo: canUndo, canRedo: canRedo }) })] });
@@ -1,44 +1,21 @@
1
1
  import GameCanvas from "../../shared/GameCanvas";
2
- import { Object3D, Texture } from "three";
3
- import { GameObject, Prefab } from "./types";
2
+ import { Prefab } from "./types";
3
+ import { PrefabEditorMode, type Scene } from "./PrefabRoot";
4
4
  import type { ExportGLBOptions } from "./utils";
5
- export interface PrefabEditorRef {
6
- root: Object3D | null;
7
- getNode: (nodeId: string) => PrefabNode | null;
8
- getNodeObject: (nodeId: string) => Object3D | null;
9
- getNodeHandle: <T = unknown>(nodeId: string, kind: string) => T | null;
10
- onSceneChange: (listener: (revision: number) => void) => () => void;
11
- screenshot: () => void;
12
- exportGLB: (options?: ExportGLBOptions) => Promise<ArrayBuffer | undefined>;
13
- exportGLBData: () => Promise<ArrayBuffer | undefined>;
14
- clearSelection: () => Promise<void>;
5
+ export interface PrefabEditorRef extends Scene {
15
6
  save: () => Prefab;
16
7
  load: (prefab: Prefab, options?: {
17
8
  resetHistory?: boolean;
18
9
  notifyChange?: boolean;
19
10
  }) => void;
20
- updateNode: (nodeId: string, update: (node: PrefabNode) => PrefabNode) => void;
21
- updateNodes: (updates: Array<{
22
- id: string;
23
- update: (node: PrefabNode) => PrefabNode;
24
- }>) => void;
25
- deleteNode: (nodeId: string) => void;
26
- duplicateNode: (nodeId: string) => string | null;
27
- moveNode: (draggedId: string, targetId: string, position: "before" | "inside") => void;
28
- addNode: (node: GameObject, options?: SpawnOptions) => GameObject;
29
- addModel: (path: string, model: Object3D, options?: SpawnOptions) => GameObject;
30
- addTexture: (path: string, texture: Texture, options?: SpawnOptions) => GameObject;
31
- }
32
- export interface SpawnOptions {
33
- name?: string;
34
- parentId?: string;
35
- select?: boolean;
36
- }
37
- export type PrefabNode = Omit<GameObject, "children">;
38
- export declare enum PrefabEditorMode {
39
- Edit = "edit",
40
- Play = "play"
11
+ undo: () => void;
12
+ redo: () => void;
13
+ screenshot: () => void;
14
+ exportGLB: (options?: ExportGLBOptions) => Promise<ArrayBuffer | undefined>;
15
+ exportGLBData: () => Promise<ArrayBuffer | undefined>;
16
+ clearSelection: () => Promise<void>;
41
17
  }
18
+ export type { PrefabNode } from "./PrefabRoot";
42
19
  export interface EditorContextType {
43
20
  mode: PrefabEditorMode;
44
21
  setMode: (mode: PrefabEditorMode) => void;
@@ -55,7 +32,9 @@ export interface EditorContextType {
55
32
  onExportGLB?: () => void;
56
33
  }
57
34
  export declare const EditorContext: import("react").Context<EditorContextType | null>;
35
+ export declare const EditorRefContext: import("react").Context<PrefabEditorRef | null>;
58
36
  export declare function useEditorContext(): EditorContextType;
37
+ export declare function useEditorRef(): PrefabEditorRef;
59
38
  export interface PrefabEditorProps {
60
39
  basePath?: string;
61
40
  initialPrefab?: Prefab;