react-three-game 0.0.18 → 0.0.19
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/.github/copilot-instructions.md +54 -183
- package/README.md +69 -214
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -0
- package/dist/tools/prefabeditor/EditorTree.d.ts +2 -4
- package/dist/tools/prefabeditor/EditorTree.js +20 -194
- package/dist/tools/prefabeditor/EditorUI.js +43 -224
- package/dist/tools/prefabeditor/PrefabEditor.js +33 -99
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +0 -1
- package/dist/tools/prefabeditor/PrefabRoot.js +7 -23
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +31 -43
- package/dist/tools/prefabeditor/styles.d.ts +1809 -0
- package/dist/tools/prefabeditor/styles.js +168 -0
- package/dist/tools/prefabeditor/types.d.ts +3 -14
- package/dist/tools/prefabeditor/types.js +0 -1
- package/dist/tools/prefabeditor/utils.d.ts +19 -0
- package/dist/tools/prefabeditor/utils.js +72 -0
- package/package.json +1 -1
- package/src/index.ts +5 -1
- package/src/tools/prefabeditor/EditorTree.tsx +38 -270
- package/src/tools/prefabeditor/EditorUI.tsx +105 -322
- package/src/tools/prefabeditor/PrefabEditor.tsx +40 -151
- package/src/tools/prefabeditor/PrefabRoot.tsx +11 -32
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +38 -53
- package/src/tools/prefabeditor/styles.ts +195 -0
- package/src/tools/prefabeditor/types.ts +4 -12
- package/src/tools/prefabeditor/utils.ts +80 -0
|
@@ -1,207 +1,78 @@
|
|
|
1
1
|
# react-three-game - AI Coding Agent Instructions
|
|
2
2
|
|
|
3
|
-
## Project
|
|
4
|
-
|
|
3
|
+
## Project Overview
|
|
4
|
+
AI-native 3D game engine where **scenes are JSON prefabs**. Unity-like GameObject+Component architecture built on React Three Fiber + WebGPU.
|
|
5
5
|
|
|
6
|
-
##
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
- ✅ Visual prefab editor exports versionable JSON
|
|
11
|
-
- ✅ No manual scene graph manipulation required
|
|
6
|
+
## Monorepo Structure
|
|
7
|
+
- **`/src`** → Library source, builds to `/dist`, published as `react-three-game`
|
|
8
|
+
- **`/docs`** → Next.js 16 site, imports library via `"react-three-game": "file:.."`
|
|
9
|
+
- **`npm run dev`** → Runs `tsc --watch` + Next.js concurrently (hot reload works)
|
|
12
10
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
### Dual-Structure Monorepo
|
|
16
|
-
- **`/src`**: Library source (TypeScript) → builds to `/dist` → published as `react-three-game` npm package
|
|
17
|
-
- **`/docs`**: Next.js 16 documentation site that imports library via `"react-three-game": "file:.."` in package.json
|
|
18
|
-
- Development command: `npm run dev` (runs `tsc --watch` + Next.js dev server concurrently via `concurrently`)
|
|
19
|
-
- **Hot reload works**: Changes to `/src` trigger rebuild → docs site sees updates
|
|
20
|
-
|
|
21
|
-
### Component-Based Prefab System
|
|
22
|
-
The core innovation is a **GameObject + Component** architecture similar to Unity/Unreal:
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
// Prefab JSON structure (see src/tools/prefabeditor/samples/*.json)
|
|
26
|
-
{
|
|
27
|
-
"id": "prefab-1",
|
|
28
|
-
"root": {
|
|
29
|
-
"id": "root",
|
|
30
|
-
"enabled": true,
|
|
31
|
-
"visible": true,
|
|
32
|
-
"components": {
|
|
33
|
-
"transform": { type: "Transform", properties: { position: [0,0,0], rotation: [0,0,0], scale: [1,1,1] } },
|
|
34
|
-
"geometry": { type: "Geometry", properties: { geometryType: "box", args: [1,1,1] } },
|
|
35
|
-
"material": { type: "Material", properties: { color: "#ffffff" } },
|
|
36
|
-
"physics": { type: "Physics", properties: { type: "dynamic" } }
|
|
37
|
-
},
|
|
38
|
-
"children": [ /* recursive GameObject[] */ ]
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
**AI agents can generate this entire structure from natural language prompts.**
|
|
44
|
-
|
|
45
|
-
### Component Registry Pattern (`src/tools/prefabeditor/components/`)
|
|
46
|
-
Every component implements:
|
|
11
|
+
## Prefab JSON Schema
|
|
47
12
|
```typescript
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
13
|
+
// See docs/app/samples/*.json for examples
|
|
14
|
+
interface Prefab { id?: string; name?: string; root: GameObject; }
|
|
15
|
+
interface GameObject {
|
|
16
|
+
id: string; // Use crypto.randomUUID() for new nodes
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
hidden?: boolean;
|
|
19
|
+
components?: Record<string, { type: string; properties: any }>;
|
|
20
|
+
children?: GameObject[];
|
|
53
21
|
}
|
|
54
22
|
```
|
|
23
|
+
**Transforms are LOCAL** (parent-relative). Rotations in radians. Colors as CSS strings.
|
|
55
24
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
- **PhysicsComponent**: Wraps children in `<RigidBody>` from @react-three/rapier (only in play mode)
|
|
59
|
-
- **MaterialComponent**: Renders as `<meshStandardNodeMaterial>` with texture support
|
|
60
|
-
- **ModelComponent**: Loads GLB/FBX via `modelLoader.ts`, supports instancing
|
|
61
|
-
|
|
62
|
-
### World Matrix Math (CRITICAL)
|
|
63
|
-
`PrefabRoot.tsx` maintains **parent-relative transforms** but uses **world matrices** for TransformControls:
|
|
64
|
-
- Each `GameObjectRenderer` computes `worldMatrix = parentMatrix * localMatrix`
|
|
65
|
-
- On transform drag: extract world matrix → compute parent inverse → derive new local transform
|
|
66
|
-
- Helper: `computeParentWorldMatrix(root, targetId)` traverses tree to get parent's world matrix
|
|
67
|
-
- **Never directly set world transforms in prefab JSON** - always store local transforms
|
|
68
|
-
|
|
69
|
-
### Instancing System (`InstanceProvider.tsx`)
|
|
70
|
-
Optimizes rendering of repeated models:
|
|
71
|
-
1. `GameInstanceProvider` flattens all model meshes into `flatMeshes` map
|
|
72
|
-
2. `GameInstance` component registers instance data (position/rotation/scale)
|
|
73
|
-
3. Provider renders once per unique mesh using `<Merged>` from drei + `<InstancedRigidBodies>`
|
|
74
|
-
4. Toggled by `model.properties.instanced = true` in prefab JSON
|
|
75
|
-
5. Physics instances use world-space transforms (not local)
|
|
76
|
-
|
|
77
|
-
## Key Files & Patterns
|
|
78
|
-
|
|
79
|
-
### `src/index.ts` - Library Exports
|
|
80
|
-
Main entry point for published package. When adding new features, export them here:
|
|
81
|
-
```typescript
|
|
82
|
-
export { default as GameCanvas } from './shared/GameCanvas';
|
|
83
|
-
export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
84
|
-
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
85
|
-
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
86
|
-
// Add new exports as features are developed
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### `PrefabEditor.tsx` - Main Editor Wrapper
|
|
90
|
-
- Manages edit/play mode toggle (pauses Rapier physics in edit mode)
|
|
91
|
-
- Handles JSON import/export via file input
|
|
92
|
-
- Renders `<EditorUI>` (inspector + tree) and `<PrefabRoot>` (scene renderer)
|
|
93
|
-
|
|
94
|
-
### `PrefabRoot.tsx` - Recursive Scene Renderer
|
|
95
|
-
Three rendering paths:
|
|
96
|
-
1. **Instanced nodes**: Short-circuit to `<GameInstance>` (world-space, terminal node)
|
|
97
|
-
2. **Model nodes**: Render as `<primitive object={clonedModel}>` with material override
|
|
98
|
-
3. **Geometry nodes**: Render as `<mesh>` with geometry + material components
|
|
99
|
-
- Always wrap in physics if component exists (except edit mode)
|
|
100
|
-
- Children always use relative transforms in `<group>`
|
|
101
|
-
|
|
102
|
-
### `EditorUI.tsx` + `EditorTree.tsx`
|
|
103
|
-
- Tree view: Drag-to-reorder via pointer events (updates parent's children array)
|
|
104
|
-
- Inspector: Dynamically renders component editors from registry
|
|
105
|
-
- Transform modes: T/R/S keyboard shortcuts handled in PrefabEditor
|
|
106
|
-
|
|
107
|
-
### `GameCanvas.tsx` - WebGPU Renderer Wrapper
|
|
108
|
-
Uses Three.js WebGPU renderer (not WebGL):
|
|
109
|
-
```tsx
|
|
110
|
-
<Canvas gl={async ({ canvas }) => {
|
|
111
|
-
const renderer = new WebGPURenderer({ canvas, shadowMap: true });
|
|
112
|
-
await renderer.init(); // MUST await initialization
|
|
113
|
-
return renderer;
|
|
114
|
-
}}>
|
|
115
|
-
```
|
|
116
|
-
**Material nodes**: Use `MeshStandardNodeMaterial` not `MeshStandardMaterial` (extends for node materials)
|
|
117
|
-
|
|
118
|
-
## Development Workflows
|
|
119
|
-
|
|
120
|
-
### Adding New Components
|
|
121
|
-
1. Create `src/tools/prefabeditor/components/MyComponent.tsx`:
|
|
25
|
+
## Component System (`src/tools/prefabeditor/components/`)
|
|
26
|
+
Every component has `Editor` (inspector UI) + optional `View` (Three.js render):
|
|
122
27
|
```typescript
|
|
123
28
|
const MyComponent: Component = {
|
|
124
|
-
name: 'MyComponent',
|
|
125
|
-
Editor: ({ component, onUpdate }) => {
|
|
126
|
-
View: ({ properties, children }) => {
|
|
127
|
-
defaultProperties: {
|
|
29
|
+
name: 'MyComponent', // TitleCase for registry, lowercase key in JSON
|
|
30
|
+
Editor: ({ component, onUpdate }) => <input onChange={e => onUpdate({ value: e.target.value })} />,
|
|
31
|
+
View: ({ properties, children }) => <group>{children}</group>, // Wrapper components accept children
|
|
32
|
+
defaultProperties: { value: 0 }
|
|
128
33
|
};
|
|
129
|
-
export default MyComponent;
|
|
130
34
|
```
|
|
131
|
-
|
|
132
|
-
3. Auto-registers via `components.forEach(registerComponent)` in PrefabRoot
|
|
35
|
+
**To add a component:** Create file → export from `components/index.ts` → auto-registered in `PrefabRoot.tsx`.
|
|
133
36
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
37
|
+
## Key Files
|
|
38
|
+
| File | Purpose |
|
|
39
|
+
|------|---------|
|
|
40
|
+
| `src/index.ts` | All public exports - add new features here |
|
|
41
|
+
| `src/tools/prefabeditor/PrefabRoot.tsx` | Recursive scene renderer, world matrix math |
|
|
42
|
+
| `src/tools/prefabeditor/PrefabEditor.tsx` | Edit/play mode, physics pause, JSON import/export |
|
|
43
|
+
| `src/tools/prefabeditor/utils.ts` | Tree helpers: `findNode`, `updateNode`, `deleteNode`, `cloneNode` |
|
|
44
|
+
| `src/shared/GameCanvas.tsx` | WebGPU renderer setup (use `MeshStandardNodeMaterial`) |
|
|
138
45
|
|
|
139
|
-
|
|
140
|
-
- Supports GLB/GLTF (with Draco compression) and FBX
|
|
141
|
-
- Models auto-load when `model.properties.filename` detected in prefab tree
|
|
142
|
-
- Uses singleton loaders (don't recreate GLTFLoader instances)
|
|
143
|
-
- Draco decoder from CDN: `https://www.gstatic.com/draco/v1/decoders/`
|
|
46
|
+
## Critical Patterns
|
|
144
47
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
### Update Prefab Node
|
|
48
|
+
### Tree Manipulation (Immutable)
|
|
148
49
|
```typescript
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (root.children) {
|
|
152
|
-
return { ...root, children: root.children.map(child => updatePrefabNode(child, id, update)) };
|
|
153
|
-
}
|
|
154
|
-
return root;
|
|
155
|
-
}
|
|
50
|
+
import { updateNode, findNode, deleteNode } from 'react-three-game';
|
|
51
|
+
const newRoot = updateNode(root, nodeId, node => ({ ...node, components: { ...node.components, physics: {...} } }));
|
|
156
52
|
```
|
|
157
53
|
|
|
158
|
-
###
|
|
159
|
-
|
|
160
|
-
- `registerRef(id, obj)` callback passed down hierarchy
|
|
161
|
-
- Used by TransformControls to manipulate objects directly
|
|
162
|
-
|
|
163
|
-
### Edit vs Play Mode
|
|
164
|
-
- Edit mode: `<MapControls>`, `<TransformControls>`, physics paused
|
|
165
|
-
- Play mode: Physics active, no editor UI
|
|
166
|
-
- Components check `editMode` prop to conditionally wrap (e.g., PhysicsComponent only wraps in play)
|
|
54
|
+
### WebGPU Materials
|
|
55
|
+
Use node materials only: `MeshStandardNodeMaterial`, `MeshBasicNodeMaterial` (not `MeshStandardMaterial`).
|
|
167
56
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
npm run build # tsc → dist/
|
|
171
|
-
npm publish --access public
|
|
172
|
-
```
|
|
57
|
+
### Physics Wrapping
|
|
58
|
+
`PhysicsComponent.View` wraps children in `<RigidBody>` only when `editMode=false`. Edit mode pauses physics.
|
|
173
59
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
- **Transforms**: Always `[x, y, z]` number arrays, rotations in radians
|
|
177
|
-
- **Colors**: Accept CSS strings (hex codes or named colors) → convert to THREE.Color
|
|
178
|
-
- **Texture paths**: Relative to public root (e.g., `/textures/grid.png`)
|
|
179
|
-
- **Component keys**: Lowercase in prefab JSON (`"transform"`, `"physics"`) but TitleCase for registry (`"Transform"`, `"Physics"`)
|
|
60
|
+
### Model Instancing
|
|
61
|
+
Set `model.properties.instanced = true` → uses `InstanceProvider.tsx` for batched rendering with physics.
|
|
180
62
|
|
|
181
|
-
##
|
|
182
|
-
|
|
183
|
-
- **@react-three/fiber** (React renderer for Three.js)
|
|
184
|
-
- **@react-three/drei** (helpers: MapControls, TransformControls, Merged)
|
|
185
|
-
- **@react-three/rapier** (physics via Rapier WASM)
|
|
186
|
-
- **Three.js WebGPU** (cutting edge renderer, not WebGL)
|
|
187
|
-
- **Next.js 16** (docs site only)
|
|
188
|
-
- **Tailwind 4** (docs site styling)
|
|
63
|
+
## Built-in Components
|
|
64
|
+
`Transform`, `Geometry` (box/sphere/plane), `Material` (color/texture), `Physics` (dynamic/fixed), `Model` (GLB/FBX), `SpotLight`, `DirectionalLight`
|
|
189
65
|
|
|
190
|
-
##
|
|
191
|
-
|
|
192
|
-
2. **Zero boilerplate**: No manual Three.js object creation in user code
|
|
193
|
-
3. **Component composition**: Mix physics + rendering + behavior via declarative components
|
|
194
|
-
4. **Visual editing**: Prefab editor generates JSON that can be version controlled
|
|
195
|
-
5. **Instancing by default**: Optimize repeated geometry automatically
|
|
66
|
+
## Custom Components (User-space)
|
|
67
|
+
See `docs/app/demo/editor/RotatorComponent.tsx` for runtime behavior example using `useFrame`. Register with `registerComponent()` before rendering `<PrefabEditor>`.
|
|
196
68
|
|
|
197
|
-
##
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
- **Touch UI**: Virtual joystick and button components for mobile
|
|
69
|
+
## Development Workflow
|
|
70
|
+
1. **Edit library**: Modify `/src`, auto-rebuilds via `tsc --watch`
|
|
71
|
+
2. **Test in docs**: Changes reflect at `http://localhost:3000`
|
|
72
|
+
3. **Add sample prefabs**: `docs/app/samples/*.json`
|
|
73
|
+
4. **Release**: `npm run release` (builds + publishes)
|
|
203
74
|
|
|
204
|
-
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
75
|
+
## Conventions
|
|
76
|
+
- Component keys: lowercase in JSON (`"transform"`), TitleCase in registry (`"Transform"`)
|
|
77
|
+
- Asset paths: Relative to `/public` (e.g., `models/cars/taxi/model.glb`)
|
|
78
|
+
- All Three.js renders must be inside `<GameCanvas>` (WebGPU init required)
|
package/README.md
CHANGED
|
@@ -1,279 +1,134 @@
|
|
|
1
1
|
# react-three-game
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
JSON-first 3D game engine. React Three Fiber + WebGPU + Rapier Physics.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm i react-three-game @react-three/fiber three
|
|
6
|
+
npm i react-three-game @react-three/fiber @react-three/rapier three
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
[](LICENSE)
|
|
10
|
-
[](https://www.typescriptlang.org/)
|
|
11
|
-
[](https://react.dev/)
|
|
12
|
-
|
|
13
9
|

|
|
14
10
|
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
Scenes are JSON prefabs. Components are registered modules. Hierarchy is declarative.
|
|
11
|
+
## Usage
|
|
18
12
|
|
|
19
13
|
```jsx
|
|
20
|
-
|
|
14
|
+
import { Physics } from '@react-three/rapier';
|
|
15
|
+
import { GameCanvas, PrefabRoot } from 'react-three-game';
|
|
16
|
+
|
|
17
|
+
<GameCanvas>
|
|
18
|
+
<Physics>
|
|
19
|
+
<PrefabRoot data={{
|
|
21
20
|
root: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
id: "scene",
|
|
22
|
+
children: [
|
|
23
|
+
{
|
|
24
|
+
id: "ground",
|
|
25
|
+
components: {
|
|
26
|
+
transform: { type: "Transform", properties: { position: [0, 0, 0], rotation: [-1.57, 0, 0] } },
|
|
27
|
+
geometry: { type: "Geometry", properties: { geometryType: "plane", args: [50, 50] } },
|
|
28
|
+
material: { type: "Material", properties: { color: "#3a3" } },
|
|
29
|
+
physics: { type: "Physics", properties: { type: "fixed" } }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "ball",
|
|
34
|
+
components: {
|
|
35
|
+
transform: { type: "Transform", properties: { position: [0, 5, 0] } },
|
|
36
|
+
geometry: { type: "Geometry", properties: { geometryType: "sphere" } },
|
|
37
|
+
material: { type: "Material", properties: { color: "#f66" } },
|
|
27
38
|
physics: { type: "Physics", properties: { type: "dynamic" } }
|
|
39
|
+
}
|
|
28
40
|
}
|
|
41
|
+
]
|
|
29
42
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
## Quick Start
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
npm install react-three-game @react-three/fiber @react-three/rapier three
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
```jsx
|
|
40
|
-
import { Physics } from '@react-three/rapier';
|
|
41
|
-
import { GameCanvas, PrefabRoot } from 'react-three-game';
|
|
42
|
-
|
|
43
|
-
export default function App() {
|
|
44
|
-
return (
|
|
45
|
-
<GameCanvas>
|
|
46
|
-
<Physics>
|
|
47
|
-
<ambientLight intensity={0.8} />
|
|
48
|
-
<PrefabRoot
|
|
49
|
-
data={{
|
|
50
|
-
id: "scene",
|
|
51
|
-
name: "scene",
|
|
52
|
-
root: {
|
|
53
|
-
id: "root",
|
|
54
|
-
children: [
|
|
55
|
-
{
|
|
56
|
-
id: "ground",
|
|
57
|
-
components: {
|
|
58
|
-
transform: { type: "Transform", properties: { position: [0, 0, 0], rotation: [-1.57, 0, 0] } },
|
|
59
|
-
geometry: { type: "Geometry", properties: { geometryType: "plane", args: [50, 50] } },
|
|
60
|
-
material: { type: "Material", properties: { color: "green" } },
|
|
61
|
-
physics: { type: "Physics", properties: { type: "fixed" } }
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
id: "player",
|
|
66
|
-
components: {
|
|
67
|
-
transform: { type: "Transform", properties: { position: [0, 2, 0] } },
|
|
68
|
-
geometry: { type: "Geometry", properties: { geometryType: "sphere" } },
|
|
69
|
-
material: { type: "Material", properties: { color: "#ff6b6b" } },
|
|
70
|
-
physics: { type: "Physics", properties: { type: "dynamic" } }
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
]
|
|
74
|
-
}
|
|
75
|
-
}}
|
|
76
|
-
/>
|
|
77
|
-
</Physics>
|
|
78
|
-
</GameCanvas>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
43
|
+
}} />
|
|
44
|
+
</Physics>
|
|
45
|
+
</GameCanvas>
|
|
81
46
|
```
|
|
82
47
|
|
|
83
|
-
## GameObject
|
|
48
|
+
## GameObject Schema
|
|
84
49
|
|
|
85
50
|
```typescript
|
|
86
51
|
interface GameObject {
|
|
87
52
|
id: string;
|
|
88
53
|
disabled?: boolean;
|
|
89
54
|
hidden?: boolean;
|
|
90
|
-
components:
|
|
91
|
-
transform?: TransformComponent;
|
|
92
|
-
geometry?: GeometryComponent;
|
|
93
|
-
material?: MaterialComponent;
|
|
94
|
-
physics?: PhysicsComponent;
|
|
95
|
-
model?: ModelComponent;
|
|
96
|
-
};
|
|
55
|
+
components?: Record<string, { type: string; properties: any }>;
|
|
97
56
|
children?: GameObject[];
|
|
98
57
|
}
|
|
99
58
|
```
|
|
100
59
|
|
|
101
|
-
##
|
|
102
|
-
|
|
103
|
-
Extend the engine by registering your own components. Components have two parts:
|
|
104
|
-
- **Editor**: UI for inspector panel (edit mode)
|
|
105
|
-
- **View**: Three.js runtime renderer (play mode)
|
|
106
|
-
|
|
107
|
-
### Component Interface
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
import { Component } from 'react-three-game';
|
|
60
|
+
## Built-in Components
|
|
111
61
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
62
|
+
| Component | Key Properties |
|
|
63
|
+
|-----------|----------------|
|
|
64
|
+
| Transform | `position`, `rotation`, `scale` — all `[x,y,z]` arrays, rotation in radians |
|
|
65
|
+
| Geometry | `geometryType`: box/sphere/plane/cylinder, `args`: dimension array |
|
|
66
|
+
| Material | `color`, `texture?`, `metalness?`, `roughness?` |
|
|
67
|
+
| Physics | `type`: dynamic/fixed |
|
|
68
|
+
| Model | `filename` (GLB/FBX path), `instanced?` for GPU batching |
|
|
69
|
+
| SpotLight | `color`, `intensity`, `angle`, `penumbra` |
|
|
119
70
|
|
|
120
|
-
|
|
71
|
+
## Custom Components
|
|
121
72
|
|
|
122
73
|
```tsx
|
|
123
74
|
import { Component, registerComponent } from 'react-three-game';
|
|
124
75
|
import { useFrame } from '@react-three/fiber';
|
|
125
|
-
import { useRef } from 'react';
|
|
126
76
|
|
|
127
|
-
const
|
|
77
|
+
const Rotator: Component = {
|
|
128
78
|
name: 'Rotator',
|
|
129
|
-
|
|
130
79
|
Editor: ({ component, onUpdate }) => (
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
<input
|
|
134
|
-
type="number"
|
|
135
|
-
value={component.properties.speed ?? 1.0}
|
|
136
|
-
onChange={e => onUpdate({ ...component.properties, speed: parseFloat(e.target.value) })}
|
|
137
|
-
/>
|
|
138
|
-
<label>Axis</label>
|
|
139
|
-
<select
|
|
140
|
-
value={component.properties.axis ?? 'y'}
|
|
141
|
-
onChange={e => onUpdate({ ...component.properties, axis: e.target.value })}
|
|
142
|
-
>
|
|
143
|
-
<option value="x">X</option>
|
|
144
|
-
<option value="y">Y</option>
|
|
145
|
-
<option value="z">Z</option>
|
|
146
|
-
</select>
|
|
147
|
-
</div>
|
|
80
|
+
<input type="number" value={component.properties.speed}
|
|
81
|
+
onChange={e => onUpdate({ speed: +e.target.value })} />
|
|
148
82
|
),
|
|
149
|
-
|
|
150
83
|
View: ({ properties, children }) => {
|
|
151
|
-
const ref = useRef();
|
|
152
|
-
|
|
153
|
-
const axis = properties.axis ?? 'y';
|
|
154
|
-
|
|
155
|
-
useFrame((state, delta) => {
|
|
156
|
-
if (ref.current) {
|
|
157
|
-
ref.current.rotation[axis] += delta * speed;
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
|
|
84
|
+
const ref = useRef<Group>(null);
|
|
85
|
+
useFrame((_, dt) => { ref.current!.rotation.y += dt * properties.speed });
|
|
161
86
|
return <group ref={ref}>{children}</group>;
|
|
162
87
|
},
|
|
163
|
-
|
|
164
|
-
defaultProperties: { speed: 1.0, axis: 'y' }
|
|
88
|
+
defaultProperties: { speed: 1 }
|
|
165
89
|
};
|
|
166
90
|
|
|
167
|
-
//
|
|
168
|
-
registerComponent(RotatorComponent);
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Usage in Prefab JSON
|
|
172
|
-
|
|
173
|
-
```json
|
|
174
|
-
{
|
|
175
|
-
"id": "spinning-cube",
|
|
176
|
-
"components": {
|
|
177
|
-
"transform": { "type": "Transform", "properties": { "position": [0, 1, 0] } },
|
|
178
|
-
"geometry": { "type": "Geometry", "properties": { "geometryType": "box" } },
|
|
179
|
-
"material": { "type": "Material", "properties": { "color": "#ff6b6b" } },
|
|
180
|
-
"rotator": { "type": "Rotator", "properties": { "speed": 2.0, "axis": "y" } }
|
|
181
|
-
}
|
|
182
|
-
}
|
|
91
|
+
registerComponent(Rotator); // before rendering PrefabEditor
|
|
183
92
|
```
|
|
184
93
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
**Wrapper components** (accept `children`) wrap the rendered content:
|
|
188
|
-
- Use for behaviors that need to manipulate the scene graph (animations, controllers)
|
|
189
|
-
- Example: Rotator wraps mesh to apply rotation
|
|
190
|
-
|
|
191
|
-
**Leaf components** (no `children`) render as siblings:
|
|
192
|
-
- Use for standalone effects (lights, particles, audio sources)
|
|
193
|
-
- Example: SpotLight renders a `<spotLight>` element
|
|
194
|
-
|
|
195
|
-
The engine automatically detects component type by checking if `View` accepts `children` prop.
|
|
196
|
-
|
|
197
|
-
## Built-in Components
|
|
198
|
-
|
|
199
|
-
| Component | Properties |
|
|
200
|
-
|-----------|-----------|
|
|
201
|
-
| **Transform** | `position: [x,y,z]`, `rotation: [x,y,z]`, `scale: [x,y,z]` |
|
|
202
|
-
| **Geometry** | `geometryType: "box"\|"sphere"\|"plane"\|"cylinder"`, `args: number[]` |
|
|
203
|
-
| **Material** | `color: string`, `texture?: string`, `metalness?: number`, `roughness?: number` |
|
|
204
|
-
| **Physics** | `type: "dynamic"\|"fixed"` |
|
|
205
|
-
| **Model** | `filename: string`, `instanced?: boolean` |
|
|
206
|
-
| **SpotLight** | `color: string`, `intensity: number`, `angle: number`, `penumbra: number` |
|
|
94
|
+
**Wrapper** components accept `children` (animations, controllers). **Leaf** components don't (lights, particles).
|
|
207
95
|
|
|
208
|
-
##
|
|
96
|
+
## Visual Editor
|
|
209
97
|
|
|
210
98
|
```jsx
|
|
211
99
|
import { PrefabEditor } from 'react-three-game';
|
|
212
|
-
|
|
213
|
-
export default function EditorPage() {
|
|
214
|
-
return <PrefabEditor />;
|
|
215
|
-
}
|
|
100
|
+
<PrefabEditor initialPrefab={sceneData} onPrefabChange={setSceneData} />
|
|
216
101
|
```
|
|
217
102
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
## Implementation Details
|
|
103
|
+
Keys: **T**ranslate / **R**otate / **S**cale. Drag tree nodes to reparent. Import/export JSON.
|
|
221
104
|
|
|
222
|
-
|
|
223
|
-
- Local transforms stored in JSON (relative to parent)
|
|
224
|
-
- World transforms computed at runtime via matrix multiplication
|
|
225
|
-
- TransformControls extract world matrix → compute parent inverse → derive new local transform
|
|
105
|
+
## Internals
|
|
226
106
|
|
|
227
|
-
|
|
228
|
-
|
|
107
|
+
- **Transforms**: Local in JSON, world computed via matrix multiplication
|
|
108
|
+
- **Instancing**: `model.properties.instanced = true` → `<Merged>` + `<InstancedRigidBodies>`
|
|
109
|
+
- **Models**: GLB/GLTF (Draco) and FBX auto-load from `filename`
|
|
229
110
|
|
|
230
|
-
|
|
231
|
-
Supports GLB/GLTF (with Draco compression) and FBX. Models auto-load when `model.properties.filename` is detected.
|
|
111
|
+
## Tree Utilities
|
|
232
112
|
|
|
233
|
-
## Patterns
|
|
234
|
-
|
|
235
|
-
### Load External Prefabs
|
|
236
|
-
```jsx
|
|
237
|
-
import levelData from './prefabs/arena.json';
|
|
238
|
-
<PrefabRoot data={levelData} />
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Update Prefab Nodes
|
|
242
113
|
```typescript
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
return { ...root, children: root.children.map(child => updatePrefabNode(child, id, update)) };
|
|
247
|
-
}
|
|
248
|
-
return root;
|
|
249
|
-
}
|
|
114
|
+
import { findNode, updateNode, deleteNode, cloneNode } from 'react-three-game';
|
|
115
|
+
|
|
116
|
+
const updated = updateNode(root, nodeId, n => ({ ...n, disabled: true }));
|
|
250
117
|
```
|
|
251
118
|
|
|
252
119
|
## Development
|
|
253
120
|
|
|
254
121
|
```bash
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
npm
|
|
258
|
-
npm run dev # tsc --watch + Next.js docs
|
|
259
|
-
npm run build # TypeScript → /dist
|
|
260
|
-
npm publish # publish to npm
|
|
122
|
+
npm run dev # tsc --watch + docs site (localhost:3000)
|
|
123
|
+
npm run build # → /dist
|
|
124
|
+
npm run release # build + publish
|
|
261
125
|
```
|
|
262
126
|
|
|
263
|
-
Project structure:
|
|
264
127
|
```
|
|
265
|
-
/src
|
|
266
|
-
|
|
267
|
-
/tools
|
|
268
|
-
/prefabeditor → editor + PrefabRoot
|
|
269
|
-
/docs → Next.js site
|
|
270
|
-
/app → demo pages
|
|
128
|
+
/src → library (published)
|
|
129
|
+
/docs → Next.js demo site
|
|
271
130
|
```
|
|
272
131
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
React 19 • Three.js r181 • TypeScript 5 • WebGPU • Rapier Physics
|
|
276
|
-
|
|
277
|
-
## License
|
|
132
|
+
---
|
|
278
133
|
|
|
279
|
-
|
|
134
|
+
React 19 · Three.js WebGPU · TypeScript 5 · Rapier WASM · MIT License
|
package/dist/index.d.ts
CHANGED
|
@@ -5,5 +5,7 @@ export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
|
5
5
|
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
6
6
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
7
7
|
export type { Component } from './tools/prefabeditor/components/ComponentRegistry';
|
|
8
|
+
export * as editorStyles from './tools/prefabeditor/styles';
|
|
9
|
+
export * from './tools/prefabeditor/utils';
|
|
8
10
|
export * from './helpers';
|
|
9
|
-
export type { Prefab, GameObject } from './tools/prefabeditor/types';
|
|
11
|
+
export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
|
package/dist/index.js
CHANGED
|
@@ -6,5 +6,8 @@ export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
|
6
6
|
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
7
7
|
// Component Registry
|
|
8
8
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
9
|
+
// Editor Styles & Utils
|
|
10
|
+
export * as editorStyles from './tools/prefabeditor/styles';
|
|
11
|
+
export * from './tools/prefabeditor/utils';
|
|
9
12
|
// Helpers
|
|
10
13
|
export * from './helpers';
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { Dispatch, SetStateAction } from 'react';
|
|
2
2
|
import { Prefab } from "./types";
|
|
3
|
-
|
|
3
|
+
export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId }: {
|
|
4
4
|
prefabData?: Prefab;
|
|
5
5
|
setPrefabData?: Dispatch<SetStateAction<Prefab>>;
|
|
6
6
|
selectedId: string | null;
|
|
7
7
|
setSelectedId: Dispatch<SetStateAction<string | null>>;
|
|
8
|
-
}
|
|
9
|
-
export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId }: EditorTreeProps): import("react/jsx-runtime").JSX.Element | null;
|
|
10
|
-
export {};
|
|
8
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|