react-three-game 0.0.46 → 0.0.47

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/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "react-three-game-skill"]
2
+ path = react-three-game-skill
3
+ url = https://github.com/prnthh/react-three-game-skill
package/README.md CHANGED
@@ -9,6 +9,11 @@ npm i react-three-game @react-three/fiber @react-three/rapier three
9
9
  ![Prefab Editor](assets/editor.gif)
10
10
  ![Architecture](assets/architecture.png)
11
11
 
12
+ ## Agent Skill
13
+ ```bash
14
+ npx skills add https://github.com/prnthh/react-three-game-skill
15
+ ```
16
+
12
17
  ## Usage
13
18
 
14
19
  ```jsx
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
6
6
  export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
7
7
  export { FieldRenderer, Input, Label, Vector3Input, ColorInput, StringInput, BooleanInput, SelectInput, } from './tools/prefabeditor/components/Input';
8
8
  export * from './tools/prefabeditor/utils';
9
+ export type { ExportGLBOptions } from './tools/prefabeditor/utils';
9
10
  export type { PrefabEditorRef } from './tools/prefabeditor/PrefabEditor';
10
11
  export type { PrefabRootRef } from './tools/prefabeditor/PrefabRoot';
11
12
  export type { Component } from './tools/prefabeditor/components/ComponentRegistry';
@@ -6,7 +6,7 @@ import { Physics } from "@react-three/rapier";
6
6
  import EditorUI from "./EditorUI";
7
7
  import { base, toolbar } from "./styles";
8
8
  import { EditorContext } from "./EditorContext";
9
- import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
9
+ import { exportGLB } from "./utils";
10
10
  const DEFAULT_PREFAB = {
11
11
  id: "prefab-default",
12
12
  name: "New Prefab",
@@ -102,18 +102,9 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, onPrefabChange, chil
102
102
  const sceneRoot = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root;
103
103
  if (!sceneRoot)
104
104
  return;
105
- const exporter = new GLTFExporter();
106
- exporter.parse(sceneRoot, (result) => {
107
- const blob = new Blob([result], { type: 'application/octet-stream' });
108
- const url = URL.createObjectURL(blob);
109
- const a = document.createElement('a');
110
- a.href = url;
111
- a.download = `${loadedPrefab.name || 'scene'}.glb`;
112
- a.click();
113
- URL.revokeObjectURL(url);
114
- }, (error) => {
115
- console.error('Error exporting GLB:', error);
116
- }, { binary: true });
105
+ exportGLB(sceneRoot, {
106
+ filename: `${loadedPrefab.name || 'scene'}.glb`
107
+ });
117
108
  };
118
109
  useEffect(() => {
119
110
  const canvas = document.querySelector('canvas');
@@ -1,8 +1,28 @@
1
1
  import { GameObject, Prefab } from "./types";
2
+ import { Object3D } from 'three';
3
+ export interface ExportGLBOptions {
4
+ filename?: string;
5
+ binary?: boolean;
6
+ onComplete?: (result: ArrayBuffer | object) => void;
7
+ onError?: (error: any) => void;
8
+ }
2
9
  /** Save a prefab as JSON file */
3
10
  export declare function saveJson(data: Prefab, filename: string): void;
4
11
  /** Load a prefab from JSON file */
5
12
  export declare function loadJson(): Promise<Prefab | undefined>;
13
+ /**
14
+ * Export a Three.js scene or object to GLB format
15
+ * @param sceneRoot - The Three.js Object3D to export
16
+ * @param options - Export options
17
+ * @returns Promise that resolves when export is complete
18
+ */
19
+ export declare function exportGLB(sceneRoot: Object3D, options?: ExportGLBOptions): Promise<ArrayBuffer | object>;
20
+ /**
21
+ * Export a Three.js scene to GLB and return the ArrayBuffer without downloading
22
+ * @param sceneRoot - The Three.js Object3D to export
23
+ * @returns Promise that resolves with the GLB data as ArrayBuffer
24
+ */
25
+ export declare function exportGLBData(sceneRoot: Object3D): Promise<ArrayBuffer>;
6
26
  /** Find a node by ID in the tree */
7
27
  export declare function findNode(root: GameObject, id: string): GameObject | null;
8
28
  /** Find the parent of a node by ID */
@@ -1,3 +1,13 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
1
11
  /** Save a prefab as JSON file */
2
12
  export function saveJson(data, filename) {
3
13
  const a = document.createElement('a');
@@ -34,6 +44,47 @@ export function loadJson() {
34
44
  input.click();
35
45
  });
36
46
  }
47
+ /**
48
+ * Export a Three.js scene or object to GLB format
49
+ * @param sceneRoot - The Three.js Object3D to export
50
+ * @param options - Export options
51
+ * @returns Promise that resolves when export is complete
52
+ */
53
+ export function exportGLB(sceneRoot, options = {}) {
54
+ const { filename = 'scene.glb', binary = true, onComplete, onError } = options;
55
+ return new Promise((resolve, reject) => {
56
+ const exporter = new GLTFExporter();
57
+ exporter.parse(sceneRoot, (result) => {
58
+ onComplete === null || onComplete === void 0 ? void 0 : onComplete(result);
59
+ resolve(result);
60
+ // Trigger download if filename is provided
61
+ if (filename) {
62
+ const blob = new Blob([result], { type: binary ? 'application/octet-stream' : 'application/json' });
63
+ const url = URL.createObjectURL(blob);
64
+ const a = document.createElement('a');
65
+ a.href = url;
66
+ a.download = filename;
67
+ a.click();
68
+ URL.revokeObjectURL(url);
69
+ }
70
+ }, (error) => {
71
+ console.error('Error exporting GLB:', error);
72
+ onError === null || onError === void 0 ? void 0 : onError(error);
73
+ reject(error);
74
+ }, { binary });
75
+ });
76
+ }
77
+ /**
78
+ * Export a Three.js scene to GLB and return the ArrayBuffer without downloading
79
+ * @param sceneRoot - The Three.js Object3D to export
80
+ * @returns Promise that resolves with the GLB data as ArrayBuffer
81
+ */
82
+ export function exportGLBData(sceneRoot) {
83
+ return __awaiter(this, void 0, void 0, function* () {
84
+ const result = yield exportGLB(sceneRoot, { filename: '', binary: true });
85
+ return result;
86
+ });
87
+ }
37
88
  /** Find a node by ID in the tree */
38
89
  export function findNode(root, id) {
39
90
  var _a;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.46",
3
+ "version": "0.0.47",
4
4
  "description": "Batteries included React Three Fiber game engine",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -1,14 +1,63 @@
1
+ ---
2
+ name: react-three-game
3
+ description: react-three-game, a JSON-first 3D game engine built on React Three Fiber, WebGPU, and Rapier Physics.
4
+ ---
5
+
1
6
  # react-three-game
2
7
 
3
- A Claude Code skill for working with react-three-game, a JSON-first 3D game engine built on React Three Fiber, WebGPU, and Rapier Physics.
8
+ Instructions for the agent to follow when this skill is activated.
9
+
10
+ ## When to use
11
+
12
+ generate 3D scenes, games and physics simulations in React.
4
13
 
5
- ## When to Use This Skill
14
+ ## Agent Workflow: JSON GLB
6
15
 
7
- Use this skill when:
8
- - Creating or modifying 3D game scenes with react-three-game
9
- - Working with prefab JSON structures
10
- - Setting up physics-enabled game objects
11
- - Creating custom components with editor UI (see Editor Mode section)
16
+ Agents can programmatically generate 3D assets:
17
+
18
+ 1. Create a JSON prefab following the GameObject schema
19
+ 2. Load it in `PrefabEditor` to render the Three.js scene
20
+ 3. Export the scene to GLB format using `exportGLB` or `exportGLBData`
21
+
22
+ ```tsx
23
+ import { useRef, useEffect } from 'react';
24
+ import { PrefabEditor, exportGLBData } from 'react-three-game';
25
+ import type { PrefabEditorRef } from 'react-three-game';
26
+
27
+ const jsonPrefab = {
28
+ root: {
29
+ id: "scene",
30
+ children: [
31
+ {
32
+ id: "cube",
33
+ components: {
34
+ transform: { type: "Transform", properties: { position: [0, 0, 0] } },
35
+ geometry: { type: "Geometry", properties: { geometryType: "box", args: [1, 1, 1] } },
36
+ material: { type: "Material", properties: { color: "#ff0000" } }
37
+ }
38
+ }
39
+ ]
40
+ }
41
+ };
42
+
43
+ function AgentExporter() {
44
+ const editorRef = useRef<PrefabEditorRef>(null);
45
+
46
+ useEffect(() => {
47
+ const timer = setTimeout(async () => {
48
+ const sceneRoot = editorRef.current?.rootRef.current?.root;
49
+ if (!sceneRoot) return;
50
+
51
+ const glbData = await exportGLBData(sceneRoot);
52
+ // glbData is an ArrayBuffer ready for upload/storage
53
+ }, 1000); // Wait for scene to render
54
+
55
+ return () => clearTimeout(timer);
56
+ }, []);
57
+
58
+ return <PrefabEditor ref={editorRef} initialPrefab={jsonPrefab} />;
59
+ }
60
+ ```
12
61
 
13
62
  ## Core Concepts
14
63
 
@@ -120,7 +169,7 @@ import { GameCanvas, PrefabRoot } from 'react-three-game';
120
169
  ### Tree Manipulation Utilities
121
170
 
122
171
  ```typescript
123
- import { findNode, updateNode, updateNodeById, deleteNode, cloneNode } from 'react-three-game';
172
+ import { findNode, updateNode, updateNodeById, deleteNode, cloneNode, saveJson, loadJson, exportGLB, exportGLBData } from 'react-three-game';
124
173
 
125
174
  // Update a node by ID (optimized - avoids unnecessary object creation)
126
175
  const updated = updateNodeById(root, nodeId, node => ({ ...node, disabled: true }));
@@ -133,6 +182,14 @@ const afterDelete = deleteNode(root, nodeId);
133
182
 
134
183
  // Clone a node
135
184
  const cloned = cloneNode(node);
185
+
186
+ // Save/load JSON
187
+ saveJson(prefab, 'my-scene');
188
+ const loaded = await loadJson();
189
+
190
+ // Export to GLB
191
+ await exportGLB(sceneRoot, { filename: 'my-scene.glb' });
192
+ const glbData = await exportGLBData(sceneRoot); // Returns ArrayBuffer
136
193
  ```
137
194
 
138
195
  ## Building Game Levels
@@ -360,6 +417,66 @@ The `PrefabEditorRef` provides:
360
417
  - `setPrefab(prefab)` - update the prefab
361
418
  - `screenshot()` - save canvas as PNG
362
419
  - `exportGLB()` - export scene as GLB
420
+ - `rootRef` - reference to the Three.js scene root for programmatic GLB export
421
+
422
+ ### Programmatic GLB Export
423
+
424
+ Export Three.js scenes to GLB format from JSON prefabs:
425
+
426
+ ```tsx
427
+ import { useRef, useEffect } from 'react';
428
+ import { PrefabEditor, exportGLB, exportGLBData } from 'react-three-game';
429
+ import type { PrefabEditorRef } from 'react-three-game';
430
+
431
+ function ExportScene() {
432
+ const editorRef = useRef<PrefabEditorRef>(null);
433
+
434
+ const handleExport = async () => {
435
+ const sceneRoot = editorRef.current?.rootRef.current?.root;
436
+ if (!sceneRoot) return;
437
+
438
+ // Option 1: Export and trigger browser download
439
+ await exportGLB(sceneRoot, {
440
+ filename: 'my-scene.glb',
441
+ binary: true,
442
+ onComplete: (result) => console.log('Export complete'),
443
+ onError: (error) => console.error('Export failed', error)
444
+ });
445
+
446
+ // Option 2: Get raw ArrayBuffer without downloading
447
+ const glbData = await exportGLBData(sceneRoot);
448
+ // Upload to server, save to file system, etc.
449
+ };
450
+
451
+ return (
452
+ <>
453
+ <PrefabEditor ref={editorRef} initialPrefab={jsonPrefab} />
454
+ <button onClick={handleExport}>Export GLB</button>
455
+ </>
456
+ );
457
+ }
458
+ ```
459
+
460
+ **Export Options:**
461
+ - `filename` - Output filename (triggers download if provided)
462
+ - `binary` - Export as binary GLB (true) or JSON glTF (false)
463
+ - `onComplete` - Callback when export succeeds
464
+ - `onError` - Callback when export fails
465
+
466
+ **Common Agent Pattern:**
467
+ ```tsx
468
+ useEffect(() => {
469
+ // Wait for scene to fully render
470
+ const timer = setTimeout(async () => {
471
+ const sceneRoot = editorRef.current?.rootRef.current?.root;
472
+ if (sceneRoot) {
473
+ const glbData = await exportGLBData(sceneRoot);
474
+ // Process the ArrayBuffer
475
+ }
476
+ }, 1000);
477
+ return () => clearTimeout(timer);
478
+ }, []);
479
+ ```
363
480
 
364
481
  ### Live Node Updates with useFrame
365
482
 
@@ -489,3 +606,4 @@ npm run dev # tsc --watch + docs site
489
606
  npm run build # build to /dist
490
607
  npm run release # build + publish
491
608
  ```
609
+
package/src/index.ts CHANGED
@@ -26,6 +26,7 @@ export {
26
26
 
27
27
  // Prefab Editor - Styles & Utils
28
28
  export * from './tools/prefabeditor/utils';
29
+ export type { ExportGLBOptions } from './tools/prefabeditor/utils';
29
30
 
30
31
  // Prefab Editor - Types
31
32
  export type { PrefabEditorRef } from './tools/prefabeditor/PrefabEditor';
@@ -6,8 +6,7 @@ import { Physics } from "@react-three/rapier";
6
6
  import EditorUI from "./EditorUI";
7
7
  import { base, toolbar } from "./styles";
8
8
  import { EditorContext } from "./EditorContext";
9
- import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
10
- import { Group } from "three";
9
+ import { exportGLB } from "./utils";
11
10
 
12
11
  export interface PrefabEditorRef {
13
12
  screenshot: () => void;
@@ -115,23 +114,9 @@ const PrefabEditor = forwardRef<PrefabEditorRef, {
115
114
  const sceneRoot = prefabRootRef.current?.root;
116
115
  if (!sceneRoot) return;
117
116
 
118
- const exporter = new GLTFExporter();
119
- exporter.parse(
120
- sceneRoot,
121
- (result) => {
122
- const blob = new Blob([result as ArrayBuffer], { type: 'application/octet-stream' });
123
- const url = URL.createObjectURL(blob);
124
- const a = document.createElement('a');
125
- a.href = url;
126
- a.download = `${loadedPrefab.name || 'scene'}.glb`;
127
- a.click();
128
- URL.revokeObjectURL(url);
129
- },
130
- (error) => {
131
- console.error('Error exporting GLB:', error);
132
- },
133
- { binary: true }
134
- );
117
+ exportGLB(sceneRoot, {
118
+ filename: `${loadedPrefab.name || 'scene'}.glb`
119
+ });
135
120
  };
136
121
 
137
122
  useEffect(() => {
@@ -1,4 +1,13 @@
1
1
  import { GameObject, Prefab } from "./types";
2
+ import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
3
+ import { Object3D } from 'three';
4
+
5
+ export interface ExportGLBOptions {
6
+ filename?: string;
7
+ binary?: boolean;
8
+ onComplete?: (result: ArrayBuffer | object) => void;
9
+ onError?: (error: any) => void;
10
+ }
2
11
 
3
12
  /** Save a prefab as JSON file */
4
13
  export function saveJson(data: Prefab, filename: string) {
@@ -33,6 +42,66 @@ export function loadJson(): Promise<Prefab | undefined> {
33
42
  });
34
43
  }
35
44
 
45
+ /**
46
+ * Export a Three.js scene or object to GLB format
47
+ * @param sceneRoot - The Three.js Object3D to export
48
+ * @param options - Export options
49
+ * @returns Promise that resolves when export is complete
50
+ */
51
+ export function exportGLB(
52
+ sceneRoot: Object3D,
53
+ options: ExportGLBOptions = {}
54
+ ): Promise<ArrayBuffer | object> {
55
+ const {
56
+ filename = 'scene.glb',
57
+ binary = true,
58
+ onComplete,
59
+ onError
60
+ } = options;
61
+
62
+ return new Promise((resolve, reject) => {
63
+ const exporter = new GLTFExporter();
64
+
65
+ exporter.parse(
66
+ sceneRoot,
67
+ (result) => {
68
+ onComplete?.(result);
69
+ resolve(result);
70
+
71
+ // Trigger download if filename is provided
72
+ if (filename) {
73
+ const blob = new Blob(
74
+ [result as ArrayBuffer],
75
+ { type: binary ? 'application/octet-stream' : 'application/json' }
76
+ );
77
+ const url = URL.createObjectURL(blob);
78
+ const a = document.createElement('a');
79
+ a.href = url;
80
+ a.download = filename;
81
+ a.click();
82
+ URL.revokeObjectURL(url);
83
+ }
84
+ },
85
+ (error) => {
86
+ console.error('Error exporting GLB:', error);
87
+ onError?.(error);
88
+ reject(error);
89
+ },
90
+ { binary }
91
+ );
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Export a Three.js scene to GLB and return the ArrayBuffer without downloading
97
+ * @param sceneRoot - The Three.js Object3D to export
98
+ * @returns Promise that resolves with the GLB data as ArrayBuffer
99
+ */
100
+ export async function exportGLBData(sceneRoot: Object3D): Promise<ArrayBuffer> {
101
+ const result = await exportGLB(sceneRoot, { filename: '', binary: true });
102
+ return result as ArrayBuffer;
103
+ }
104
+
36
105
  /** Find a node by ID in the tree */
37
106
  export function findNode(root: GameObject, id: string): GameObject | null {
38
107
  if (root.id === id) return root;
@@ -1,17 +0,0 @@
1
- {
2
- "name": "@react-three-game/skills",
3
- "version": "0.0.1",
4
- "description": "Agent skill for working with react-three-game - a JSON-first 3D game engine",
5
- "repository": {
6
- "url": "https://github.com/prnth/react-three-game/tree/main/skill"
7
- },
8
- "keywords": [
9
- "agentic",
10
- "skill",
11
- "react-three-fiber",
12
- "game-engine",
13
- "3d",
14
- "webgpu"
15
- ],
16
- "license": "MIT"
17
- }