react-three-game 0.0.60 → 0.0.61
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/package.json +9 -3
- package/.gitattributes +0 -2
- package/.github/copilot-instructions.md +0 -83
- package/.github/workflows/nextjs.yml +0 -99
- package/.gitmodules +0 -3
- package/assets/architecture.png +0 -0
- package/assets/editor.gif +0 -0
- package/assets/favicon.ico +0 -0
- package/assets/react-three-game-logo.png +0 -0
- package/dist/tools/dragdrop/page.d.ts +0 -1
- package/dist/tools/dragdrop/page.js +0 -11
- package/dist/tools/prefabeditor/EntityEvents.d.ts +0 -54
- package/dist/tools/prefabeditor/EntityEvents.js +0 -85
- package/dist/tools/prefabeditor/page.d.ts +0 -1
- package/dist/tools/prefabeditor/page.js +0 -5
- package/react-three-game-skill/.gitattributes +0 -2
- package/react-three-game-skill/README.md +0 -7
- package/react-three-game-skill/react-three-game/SKILL.md +0 -514
- package/react-three-game-skill/react-three-game/rules/ADVANCED_PHYSICS.md +0 -472
- package/react-three-game-skill/react-three-game/rules/LIGHTING.md +0 -6
- package/src/helpers/SoundManager.ts +0 -130
- package/src/helpers/index.ts +0 -91
- package/src/index.ts +0 -59
- package/src/shared/ContactShadow.tsx +0 -74
- package/src/shared/GameCanvas.tsx +0 -52
- package/src/tools/assetviewer/page.tsx +0 -425
- package/src/tools/dragdrop/DragDropLoader.tsx +0 -159
- package/src/tools/dragdrop/index.ts +0 -4
- package/src/tools/dragdrop/modelLoader.ts +0 -204
- package/src/tools/dragdrop/page.tsx +0 -45
- package/src/tools/prefabeditor/Dropdown.tsx +0 -112
- package/src/tools/prefabeditor/EditorContext.tsx +0 -25
- package/src/tools/prefabeditor/EditorTree.tsx +0 -452
- package/src/tools/prefabeditor/EditorTreeMenus.tsx +0 -307
- package/src/tools/prefabeditor/EditorUI.tsx +0 -204
- package/src/tools/prefabeditor/EventSystem.tsx +0 -36
- package/src/tools/prefabeditor/GameEvents.ts +0 -191
- package/src/tools/prefabeditor/InstanceProvider.tsx +0 -466
- package/src/tools/prefabeditor/PrefabEditor.tsx +0 -256
- package/src/tools/prefabeditor/PrefabRoot.tsx +0 -767
- package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +0 -34
- package/src/tools/prefabeditor/components/CameraComponent.tsx +0 -117
- package/src/tools/prefabeditor/components/ComponentRegistry.ts +0 -40
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +0 -210
- package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +0 -47
- package/src/tools/prefabeditor/components/GeometryComponent.tsx +0 -133
- package/src/tools/prefabeditor/components/Input.tsx +0 -820
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +0 -431
- package/src/tools/prefabeditor/components/ModelComponent.tsx +0 -176
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +0 -188
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +0 -109
- package/src/tools/prefabeditor/components/TextComponent.tsx +0 -137
- package/src/tools/prefabeditor/components/TransformComponent.tsx +0 -173
- package/src/tools/prefabeditor/components/index.ts +0 -26
- package/src/tools/prefabeditor/page.tsx +0 -10
- package/src/tools/prefabeditor/styles.ts +0 -235
- package/src/tools/prefabeditor/types.ts +0 -20
- package/src/tools/prefabeditor/utils.ts +0 -312
|
@@ -1,514 +0,0 @@
|
|
|
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
|
-
|
|
6
|
-
# react-three-game
|
|
7
|
-
|
|
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.
|
|
13
|
-
|
|
14
|
-
## Agent Workflow: JSON → GLB
|
|
15
|
-
|
|
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
|
-
```
|
|
61
|
-
|
|
62
|
-
## Core Concepts
|
|
63
|
-
|
|
64
|
-
### Asset Paths and Public Directory
|
|
65
|
-
|
|
66
|
-
**All asset paths are relative to `/public`** and omit the `/public` prefix:
|
|
67
|
-
|
|
68
|
-
```json
|
|
69
|
-
{
|
|
70
|
-
"texture": "/textures/floor.png",
|
|
71
|
-
"model": "/models/car.glb",
|
|
72
|
-
"font": "/fonts/font.ttf"
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Path `"/any/path/file.ext"` refers to `/public/any/path/file.ext`.
|
|
77
|
-
### GameObject Structure
|
|
78
|
-
|
|
79
|
-
Every game object follows this schema:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
interface GameObject {
|
|
83
|
-
id: string;
|
|
84
|
-
name?: string;
|
|
85
|
-
disabled?: boolean;
|
|
86
|
-
components?: Record<string, { type: string; properties: any }>;
|
|
87
|
-
children?: GameObject[];
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
`disabled` is the canonical visibility toggle. Transforms are local to the parent node.
|
|
92
|
-
|
|
93
|
-
### Prefab JSON Format
|
|
94
|
-
|
|
95
|
-
Scenes are defined as JSON prefabs with a root node containing children:
|
|
96
|
-
|
|
97
|
-
```json
|
|
98
|
-
{
|
|
99
|
-
"root": {
|
|
100
|
-
"id": "scene",
|
|
101
|
-
"children": [
|
|
102
|
-
{
|
|
103
|
-
"id": "my-object",
|
|
104
|
-
"components": {
|
|
105
|
-
"transform": { "type": "Transform", "properties": { "position": [0, 0, 0] } },
|
|
106
|
-
"geometry": { "type": "Geometry", "properties": { "geometryType": "box" } },
|
|
107
|
-
"material": { "type": "Material", "properties": { "color": "#ff0000" } }
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Built-in Components
|
|
116
|
-
|
|
117
|
-
| Component | Type | Key Properties |
|
|
118
|
-
|-----------|------|----------------|
|
|
119
|
-
| Transform | `Transform` | `position: [x,y,z]`, `rotation: [x,y,z]` (radians), `scale: [x,y,z]` |
|
|
120
|
-
| Geometry | `Geometry` | `geometryType`: box/sphere/plane/cylinder, `args`: dimension array |
|
|
121
|
-
| Material | `Material` | `color`, `texture?`, `metalness?`, `roughness?`, `repeat?`, `repeatCount?` |
|
|
122
|
-
| Physics | `Physics` | `type`: dynamic/fixed/kinematicPosition/kinematicVelocity, `mass?`, `restitution?`, `friction?`, `linearDamping?`, `angularDamping?`, `gravityScale?`, `sensor?`, `activeCollisionTypes?: 'all'` (enable kinematic/fixed collision detection), plus any Rapier RigidBody props - [See advanced physics guide](./rules/ADVANCED_PHYSICS.md) |
|
|
123
|
-
| Model | `Model` | `filename` (GLB/FBX path), `instanced?` for GPU batching |
|
|
124
|
-
| SpotLight | `SpotLight` | `color`, `intensity`, `angle`, `penumbra`, `distance?`, `castShadow?` |
|
|
125
|
-
| DirectionalLight | `DirectionalLight` | `color`, `intensity`, `castShadow?`, `targetOffset?: [x,y,z]` |
|
|
126
|
-
| AmbientLight | `AmbientLight` | `color`, `intensity` |
|
|
127
|
-
| Environment | `Environment` | `intensity`, `resolution` |
|
|
128
|
-
| Camera | `Camera` | `fov`, `near`, `far`, `zoom` |
|
|
129
|
-
| Text | `Text` | `text`, `font`, `size`, `depth`, `width`, `align`, `color` |
|
|
130
|
-
|
|
131
|
-
### Text Component
|
|
132
|
-
|
|
133
|
-
Requires `hb.wasm` and a font file (TTF/WOFF) in `/public/fonts/`:
|
|
134
|
-
- hb.wasm: https://github.com/prnthh/react-three-game/raw/refs/heads/main/docs/public/fonts/hb.wasm
|
|
135
|
-
- Sample font: https://github.com/prnthh/react-three-game/raw/refs/heads/main/docs/public/fonts/NotoSans-Regular.ttf
|
|
136
|
-
|
|
137
|
-
Font property: `"font": "/fonts/NotoSans-Regular.ttf"`
|
|
138
|
-
|
|
139
|
-
### Geometry Args by Type
|
|
140
|
-
|
|
141
|
-
| geometryType | args array |
|
|
142
|
-
|--------------|------------|
|
|
143
|
-
| `box` | `[width, height, depth]` |
|
|
144
|
-
| `sphere` | `[radius, widthSegments, heightSegments]` |
|
|
145
|
-
| `plane` | `[width, height]` |
|
|
146
|
-
| `cylinder` | `[radiusTop, radiusBottom, height, radialSegments]` |
|
|
147
|
-
|
|
148
|
-
### Material Textures
|
|
149
|
-
|
|
150
|
-
```json
|
|
151
|
-
{
|
|
152
|
-
"material": {
|
|
153
|
-
"type": "Material",
|
|
154
|
-
"properties": {
|
|
155
|
-
"color": "white",
|
|
156
|
-
"texture": "/textures/floor.png",
|
|
157
|
-
"repeat": true,
|
|
158
|
-
"repeatCount": [4, 4]
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### Rotations
|
|
165
|
-
|
|
166
|
-
Use radians: `1.57` = 90°, `3.14` = 180°, `-1.57` = -90°
|
|
167
|
-
|
|
168
|
-
## Common Patterns
|
|
169
|
-
|
|
170
|
-
### Usage Modes
|
|
171
|
-
|
|
172
|
-
**PrefabRoot**: Pure renderer for embedding prefab data in standard R3F applications. Render it inside a regular `@react-three/fiber` `Canvas`. `GameCanvas` provides the WebGPU canvas setup. Add a `Physics` wrapper to enable physics. Use this to integrate prefabs into larger R3F scenes.
|
|
173
|
-
|
|
174
|
-
```jsx
|
|
175
|
-
import { Canvas } from '@react-three/fiber';
|
|
176
|
-
import { Physics } from '@react-three/rapier';
|
|
177
|
-
import { PrefabRoot } from 'react-three-game';
|
|
178
|
-
|
|
179
|
-
<Canvas>
|
|
180
|
-
<Physics>
|
|
181
|
-
<PrefabRoot data={prefabData} />
|
|
182
|
-
<CustomComponent />
|
|
183
|
-
</Physics>
|
|
184
|
-
</Canvas>
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
`GameCanvas` provides the library's WebGPU canvas setup.
|
|
188
|
-
|
|
189
|
-
**PrefabEditor**: Managed scene with editor UI and play/pause controls for physics. Full authoring tool for level design and prototyping. Includes canvas, physics, transform gizmos, and inspector. Physics only runs in play mode. Can pass R3F components as children. Editor actions live under `Menu > File`, and exports under `Menu > Export`.
|
|
190
|
-
|
|
191
|
-
```jsx
|
|
192
|
-
import { PrefabEditor } from 'react-three-game';
|
|
193
|
-
|
|
194
|
-
<PrefabEditor initialPrefab={prefabData}>
|
|
195
|
-
<CustomComponent />
|
|
196
|
-
</PrefabEditor>
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### Tree Utilities
|
|
200
|
-
|
|
201
|
-
```typescript
|
|
202
|
-
import { findNode, updateNode, updateNodeById, deleteNode, cloneNode, exportGLBData } from 'react-three-game';
|
|
203
|
-
|
|
204
|
-
const node = findNode(root, nodeId);
|
|
205
|
-
const updated = updateNode(root, nodeId, n => ({ ...n, disabled: true })); // or updateNodeById (identical)
|
|
206
|
-
const afterDelete = deleteNode(root, nodeId);
|
|
207
|
-
const cloned = cloneNode(node);
|
|
208
|
-
const glbData = await exportGLBData(sceneRoot);
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
## Hybrid JSON + R3F Children Pattern
|
|
212
|
-
|
|
213
|
-
**Prefabs define static scene structure, R3F children add dynamic behavior**:
|
|
214
|
-
|
|
215
|
-
```tsx
|
|
216
|
-
import { useRef } from 'react';
|
|
217
|
-
import { useFrame } from '@react-three/fiber';
|
|
218
|
-
import { PrefabEditor, findNode } from 'react-three-game';
|
|
219
|
-
import type { PrefabEditorRef } from 'react-three-game';
|
|
220
|
-
|
|
221
|
-
function DynamicLight() {
|
|
222
|
-
const lightRef = useRef<THREE.SpotLight>(null!);
|
|
223
|
-
|
|
224
|
-
useFrame(({ clock }) => {
|
|
225
|
-
lightRef.current.intensity = 100 + Math.sin(clock.elapsedTime) * 50;
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
return <spotLight ref={lightRef} position={[10, 15, 10]} angle={0.5} />;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
<PrefabEditor initialPrefab={staticScenePrefab}>
|
|
232
|
-
<DynamicLight />
|
|
233
|
-
<CustomController />
|
|
234
|
-
</PrefabEditor>
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
**Use cases**: Player controllers, AI behaviors, procedural animation, real-time effects.
|
|
238
|
-
|
|
239
|
-
## World Scene Pattern
|
|
240
|
-
|
|
241
|
-
The current world demo combines prefab-authored level geometry with runtime React behavior:
|
|
242
|
-
|
|
243
|
-
- Static level layout, props, and collision live in prefab JSON.
|
|
244
|
-
- `Environment` can wrap sky geometry or lighting content for a full scene backdrop.
|
|
245
|
-
- `Camera` can live in the prefab so view-only scenes and editor scenes share the same authored viewpoint.
|
|
246
|
-
- Runtime logic can use `useFrame` plus `updateNodeById` to animate prefab entities without abandoning the JSON scene model.
|
|
247
|
-
|
|
248
|
-
```json
|
|
249
|
-
{
|
|
250
|
-
"id": "environment",
|
|
251
|
-
"components": {
|
|
252
|
-
"environment": {
|
|
253
|
-
"type": "Environment",
|
|
254
|
-
"properties": { "intensity": 1, "resolution": 256 }
|
|
255
|
-
}
|
|
256
|
-
},
|
|
257
|
-
"children": [
|
|
258
|
-
{
|
|
259
|
-
"id": "sky",
|
|
260
|
-
"components": {
|
|
261
|
-
"geometry": { "type": "Geometry", "properties": { "geometryType": "sphere", "args": [100, 32, 16] } },
|
|
262
|
-
"material": { "type": "Material", "properties": { "texture": "/textures/skybox/skybox1.jpg", "side": "BackSide", "materialType": "basic" } }
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
]
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
## Quick Reference Examples
|
|
270
|
-
|
|
271
|
-
```json
|
|
272
|
-
// Static geometry with physics (floor, wall, platform, ramp)
|
|
273
|
-
{ "id": "floor", "components": {
|
|
274
|
-
"transform": { "type": "Transform", "properties": { "position": [0, -0.5, 0] } },
|
|
275
|
-
"geometry": { "type": "Geometry", "properties": { "geometryType": "box", "args": [40, 1, 40] } },
|
|
276
|
-
"material": { "type": "Material", "properties": { "texture": "/textures/floor.png", "repeat": true, "repeatCount": [20, 20] } },
|
|
277
|
-
"physics": { "type": "Physics", "properties": { "type": "fixed" } }
|
|
278
|
-
}}
|
|
279
|
-
|
|
280
|
-
// Lighting
|
|
281
|
-
{ "id": "spot", "components": {
|
|
282
|
-
"transform": { "type": "Transform", "properties": { "position": [10, 15, 10] } },
|
|
283
|
-
"spotlight": { "type": "SpotLight", "properties": { "intensity": 200, "angle": 0.8, "castShadow": true } }
|
|
284
|
-
}}
|
|
285
|
-
|
|
286
|
-
// 3D Text
|
|
287
|
-
{ "id": "title", "components": {
|
|
288
|
-
"transform": { "type": "Transform", "properties": { "position": [0, 3, 0] } },
|
|
289
|
-
"text": { "type": "Text", "properties": { "text": "Welcome", "font": "/fonts/font.ttf", "size": 1, "depth": 0.1 } }
|
|
290
|
-
}}
|
|
291
|
-
|
|
292
|
-
// GLB Model
|
|
293
|
-
{ "id": "tree", "components": {
|
|
294
|
-
"transform": { "type": "Transform", "properties": { "position": [0, 0, 0], "scale": [1.5, 1.5, 1.5] } },
|
|
295
|
-
"model": { "type": "Model", "properties": { "filename": "/models/tree.glb" } }
|
|
296
|
-
}}
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
## Editor
|
|
300
|
-
|
|
301
|
-
### Basic Usage
|
|
302
|
-
|
|
303
|
-
```jsx
|
|
304
|
-
import { PrefabEditor } from 'react-three-game';
|
|
305
|
-
|
|
306
|
-
<PrefabEditor initialPrefab={sceneData} onPrefabChange={setSceneData} />
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
Keyboard shortcuts: **T** (Translate), **R** (Rotate), **S** (Scale)
|
|
310
|
-
|
|
311
|
-
### Camera Control
|
|
312
|
-
|
|
313
|
-
By default, `PrefabEditor` uses an orbit camera. **Override it by adding a custom camera with `makeDefault`**:
|
|
314
|
-
|
|
315
|
-
```tsx
|
|
316
|
-
import { PerspectiveCamera } from '@react-three/drei';
|
|
317
|
-
import { PrefabEditor } from 'react-three-game';
|
|
318
|
-
|
|
319
|
-
<PrefabEditor initialPrefab={prefab}>
|
|
320
|
-
<PerspectiveCamera makeDefault position={[0, 5, 10]} fov={75} />
|
|
321
|
-
</PrefabEditor>
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
Any R3F camera component works: `PerspectiveCamera`, `OrthographicCamera`, or custom camera controllers.
|
|
325
|
-
|
|
326
|
-
### Programmatic Updates
|
|
327
|
-
|
|
328
|
-
```jsx
|
|
329
|
-
import { useRef } from 'react';
|
|
330
|
-
import { PrefabEditor, updateNodeById } from 'react-three-game';
|
|
331
|
-
import type { PrefabEditorRef } from 'react-three-game';
|
|
332
|
-
|
|
333
|
-
function Scene() {
|
|
334
|
-
const editorRef = useRef<PrefabEditorRef>(null);
|
|
335
|
-
|
|
336
|
-
const moveBall = () => {
|
|
337
|
-
const prefab = editorRef.current!.prefab;
|
|
338
|
-
const newRoot = updateNodeById(prefab.root, "ball", node => ({
|
|
339
|
-
...node,
|
|
340
|
-
components: {
|
|
341
|
-
...node.components,
|
|
342
|
-
transform: {
|
|
343
|
-
...node.components!.transform!,
|
|
344
|
-
properties: { ...node.components!.transform!.properties, position: [5, 0, 0] }
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}));
|
|
348
|
-
editorRef.current!.setPrefab({ ...prefab, root: newRoot });
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
return <PrefabEditor ref={editorRef} initialPrefab={sceneData} />;
|
|
352
|
-
}
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
**PrefabEditorRef**: `prefab`, `setPrefab()`, `screenshot()`, `exportGLB()`, `rootRef`
|
|
356
|
-
|
|
357
|
-
### GLB Export
|
|
358
|
-
|
|
359
|
-
```tsx
|
|
360
|
-
import { exportGLBData } from 'react-three-game';
|
|
361
|
-
|
|
362
|
-
const glbData = await exportGLBData(editorRef.current!.rootRef.current!.root);
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
### Runtime Animation
|
|
366
|
-
|
|
367
|
-
```tsx
|
|
368
|
-
import { useRef } from "react";
|
|
369
|
-
import { useFrame } from "@react-three/fiber";
|
|
370
|
-
import { PrefabEditor, updateNodeById } from "react-three-game";
|
|
371
|
-
|
|
372
|
-
function Animator({ editorRef }) {
|
|
373
|
-
useFrame(() => {
|
|
374
|
-
const prefab = editorRef.current!.prefab;
|
|
375
|
-
const newRoot = updateNodeById(prefab.root, "ball", node => ({
|
|
376
|
-
...node,
|
|
377
|
-
components: {
|
|
378
|
-
...node.components,
|
|
379
|
-
transform: {
|
|
380
|
-
...node.components!.transform!,
|
|
381
|
-
properties: { ...node.components!.transform!.properties, position: [x, y, z] }
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}));
|
|
385
|
-
editorRef.current!.setPrefab({ ...prefab, root: newRoot });
|
|
386
|
-
});
|
|
387
|
-
return null;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function Scene() {
|
|
391
|
-
const editorRef = useRef(null);
|
|
392
|
-
return (
|
|
393
|
-
<PrefabEditor ref={editorRef} initialPrefab={data}>
|
|
394
|
-
<Animator editorRef={editorRef} />
|
|
395
|
-
</PrefabEditor>
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
### Custom Component
|
|
401
|
-
|
|
402
|
-
```tsx
|
|
403
|
-
import { Component, registerComponent, FieldRenderer } from 'react-three-game';
|
|
404
|
-
|
|
405
|
-
const MyComponent: Component = {
|
|
406
|
-
name: 'MyComponent',
|
|
407
|
-
Editor: ({ component, onUpdate }) => (
|
|
408
|
-
<FieldRenderer fields={[{ name: 'speed', type: 'number', step: 0.1 }]} values={component.properties} onChange={onUpdate} />
|
|
409
|
-
),
|
|
410
|
-
View: ({ properties, children }) => <group>{children}</group>,
|
|
411
|
-
defaultProperties: { speed: 1 }
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
registerComponent(MyComponent);
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
Use the component in prefab JSON by adding a component entry whose `type` matches the registered component name:
|
|
418
|
-
|
|
419
|
-
```json
|
|
420
|
-
{
|
|
421
|
-
"components": {
|
|
422
|
-
"mycomponent": {
|
|
423
|
-
"type": "MyComponent",
|
|
424
|
-
"properties": {
|
|
425
|
-
"speed": 1
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
Rules:
|
|
433
|
-
- Call `registerComponent(MyComponent)` before rendering `<PrefabEditor>` or `<PrefabRoot>` with prefab data that uses it.
|
|
434
|
-
- `type` must match the registered component name exactly (`name: 'MyComponent'` -> `"type": "MyComponent"`).
|
|
435
|
-
- Use `View` to render visible content, wrap `children`, or add runtime behavior with hooks like `useFrame`.
|
|
436
|
-
|
|
437
|
-
**Field types**: `vector3`, `number`, `string`, `color`, `boolean`, `select`, `custom`
|
|
438
|
-
|
|
439
|
-
## Game Events
|
|
440
|
-
|
|
441
|
-
A general-purpose event system for game-wide communication. Handles physics events, gameplay events, and any custom events.
|
|
442
|
-
|
|
443
|
-
### Core API
|
|
444
|
-
|
|
445
|
-
```tsx
|
|
446
|
-
import { gameEvents, useGameEvent } from 'react-three-game';
|
|
447
|
-
|
|
448
|
-
// Emit events
|
|
449
|
-
gameEvents.emit('player:death', { playerId: 'p1', cause: 'lava' });
|
|
450
|
-
gameEvents.emit('score:change', { delta: 100, total: 500 });
|
|
451
|
-
|
|
452
|
-
// Subscribe (React hook - auto cleanup on unmount)
|
|
453
|
-
useGameEvent('player:death', (payload) => {
|
|
454
|
-
showGameOver(payload.cause);
|
|
455
|
-
}, []);
|
|
456
|
-
|
|
457
|
-
// Subscribe (manual - returns unsubscribe function)
|
|
458
|
-
const unsub = gameEvents.on('score:change', (payload) => {
|
|
459
|
-
updateUI(payload.total);
|
|
460
|
-
});
|
|
461
|
-
unsub(); // cleanup
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
### Built-in Physics Events
|
|
465
|
-
|
|
466
|
-
Physics components automatically emit these events:
|
|
467
|
-
|
|
468
|
-
| Event | When | Payload |
|
|
469
|
-
|-------|------|---------|
|
|
470
|
-
| `sensor:enter` | Something enters a sensor collider | `{ sourceEntityId, targetEntityId, targetRigidBody }` |
|
|
471
|
-
| `sensor:exit` | Something exits a sensor collider | `{ sourceEntityId, targetEntityId, targetRigidBody }` |
|
|
472
|
-
| `collision:enter` | A collision starts | `{ sourceEntityId, targetEntityId, targetRigidBody }` |
|
|
473
|
-
| `collision:exit` | A collision ends | `{ sourceEntityId, targetEntityId, targetRigidBody }` |
|
|
474
|
-
|
|
475
|
-
**Collision filtering**: By default, kinematic/fixed bodies don't detect each other. For kinematic sensors or projectiles to detect walls/floors, add `"activeCollisionTypes": "all"` to the Physics properties.
|
|
476
|
-
|
|
477
|
-
See [Advanced Physics](./rules/ADVANCED_PHYSICS.md) for sensor setup and collision handling patterns.
|
|
478
|
-
|
|
479
|
-
### TypeScript: Typed Custom Events
|
|
480
|
-
|
|
481
|
-
Extend `GameEventMap` for type-safe custom events:
|
|
482
|
-
|
|
483
|
-
```typescript
|
|
484
|
-
declare module 'react-three-game' {
|
|
485
|
-
interface GameEventMap {
|
|
486
|
-
'player:death': { playerId: string; cause: string };
|
|
487
|
-
'score:change': { delta: number; total: number };
|
|
488
|
-
'level:complete': { levelId: number; time: number };
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
### Common Patterns
|
|
494
|
-
|
|
495
|
-
```tsx
|
|
496
|
-
// Gameplay controller
|
|
497
|
-
function GameController() {
|
|
498
|
-
const [score, setScore] = useState(0);
|
|
499
|
-
|
|
500
|
-
useGameEvent('score:change', ({ total }) => setScore(total), []);
|
|
501
|
-
useGameEvent('player:death', () => setGameOver(true), []);
|
|
502
|
-
|
|
503
|
-
return <ScoreUI score={score} />;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Pickup system
|
|
507
|
-
useGameEvent('sensor:enter', (payload) => {
|
|
508
|
-
if (payload.sourceEntityId.startsWith('coin-')) {
|
|
509
|
-
gameEvents.emit('score:change', { delta: 10, total: score + 10 });
|
|
510
|
-
removeEntity(payload.sourceEntityId);
|
|
511
|
-
}
|
|
512
|
-
}, [score]);
|
|
513
|
-
```
|
|
514
|
-
|