react-three-game 0.0.48 → 0.0.49

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.
@@ -15,7 +15,6 @@ interface Prefab { id?: string; name?: string; root: GameObject; }
15
15
  interface GameObject {
16
16
  id: string; // Use crypto.randomUUID() for new nodes
17
17
  disabled?: boolean;
18
- hidden?: boolean;
19
18
  components?: Record<string, { type: string; properties: any }>;
20
19
  children?: GameObject[];
21
20
  }
@@ -38,11 +37,17 @@ const MyComponent: Component = {
38
37
  | File | Purpose |
39
38
  |------|---------|
40
39
  | `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 |
40
+ | `src/tools/prefabeditor/PrefabRoot.tsx` | Pure renderer - renders prefab as Three.js objects for R3F integration |
41
+ | `src/tools/prefabeditor/PrefabEditor.tsx` | Managed scene with editor UI and play/pause controls for physics |
43
42
  | `src/tools/prefabeditor/utils.ts` | Tree helpers: `findNode`, `updateNode`, `deleteNode`, `cloneNode` |
44
43
  | `src/shared/GameCanvas.tsx` | WebGPU renderer setup (use `MeshStandardNodeMaterial`) |
45
44
 
45
+ ## Usage Modes
46
+
47
+ **GameCanvas + PrefabRoot**: Pure renderer for embedding prefab data in standard R3F applications. Minimal wrapper - just renders the prefab as Three.js objects. Requires manual `<Physics>` setup. Physics always active. Use this to integrate prefabs into larger R3F scenes.
48
+
49
+ **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.
50
+
46
51
  ## Critical Patterns
47
52
 
48
53
  ### Tree Manipulation (Immutable)
package/README.md CHANGED
@@ -14,7 +14,13 @@ npm i react-three-game @react-three/fiber @react-three/rapier three
14
14
  npx skills add https://github.com/prnthh/react-three-game-skill
15
15
  ```
16
16
 
17
- ## Usage
17
+ ## Usage Modes
18
+
19
+ **GameCanvas + PrefabRoot**: Pure renderer for embedding prefab data in standard R3F applications. Minimal wrapper - just renders the prefab as Three.js objects. Requires manual `<Physics>` setup. Physics always active. Use this to integrate prefabs into larger R3F scenes.
20
+
21
+ **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.
22
+
23
+ ## Basic Usage
18
24
 
19
25
  ```jsx
20
26
  import { Physics } from '@react-three/rapier';
@@ -57,7 +63,6 @@ import { GameCanvas, PrefabRoot } from 'react-three-game';
57
63
  interface GameObject {
58
64
  id: string;
59
65
  disabled?: boolean;
60
- hidden?: boolean;
61
66
  components?: Record<string, { type: string; properties: any }>;
62
67
  children?: GameObject[];
63
68
  }
@@ -70,7 +75,7 @@ interface GameObject {
70
75
  | Transform | `position`, `rotation`, `scale` — all `[x,y,z]` arrays, rotation in radians |
71
76
  | Geometry | `geometryType`: box/sphere/plane/cylinder, `args`: dimension array |
72
77
  | Material | `color`, `texture?`, `metalness?`, `roughness?` |
73
- | Physics | `type`: dynamic/fixed |
78
+ | Physics | `type`: dynamic/fixed/kinematicPosition/kinematicVelocity, `mass?`, `restitution?` (bounciness), `friction?`, plus any Rapier props |
74
79
  | Model | `filename` (GLB/FBX path), `instanced?` for GPU batching |
75
80
  | SpotLight | `color`, `intensity`, `angle`, `penumbra` |
76
81
 
@@ -133,14 +138,21 @@ The `FieldRenderer` component auto-generates editor UI from a field schema:
133
138
  }
134
139
  ```
135
140
 
136
- ## Visual Editor
141
+ ## Prefab Editor
137
142
 
138
143
  ```jsx
139
144
  import { PrefabEditor } from 'react-three-game';
145
+
146
+ // Standalone editor
140
147
  <PrefabEditor initialPrefab={sceneData} onPrefabChange={setSceneData} />
148
+
149
+ // With custom R3F components
150
+ <PrefabEditor initialPrefab={sceneData}>
151
+ <CustomComponent />
152
+ </PrefabEditor>
141
153
  ```
142
154
 
143
- Keys: **T**ranslate / **R**otate / **S**cale. Drag tree nodes to reparent. Import/export JSON.
155
+ Keys: **T**ranslate / **R**otate / **S**cale. Drag tree nodes to reparent. Import/export JSON. Physics only runs in play mode.
144
156
 
145
157
  ## Internals
146
158
 
@@ -18,8 +18,6 @@ export interface GroundOptions {
18
18
  repeatCount?: [number, number];
19
19
  /** Physics body type. Defaults to "fixed". */
20
20
  physicsType?: "fixed" | "dynamic" | "kinematic";
21
- /** Set true to hide the node. */
22
- hidden?: boolean;
23
21
  /** Set true to disable the node. */
24
22
  disabled?: boolean;
25
23
  }
@@ -8,11 +8,10 @@
8
8
  * - Physics (fixed by default)
9
9
  */
10
10
  export function ground(options = {}) {
11
- const { id = "ground", size = 50, position = [0, 0, 0], rotation = [-Math.PI / 2, 0, 0], scale = [1, 1, 1], color = "white", texture, repeat = texture ? true : false, repeatCount = [25, 25], physicsType = "fixed", hidden = false, disabled = false, } = options;
11
+ const { id = "ground", size = 50, position = [0, 0, 0], rotation = [-Math.PI / 2, 0, 0], scale = [1, 1, 1], color = "white", texture, repeat = texture ? true : false, repeatCount = [25, 25], physicsType = "fixed", disabled = false, } = options;
12
12
  return {
13
13
  id,
14
14
  disabled,
15
- hidden,
16
15
  components: {
17
16
  transform: {
18
17
  type: "Transform",
@@ -108,7 +108,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
108
108
  export function GameObjectRenderer(props) {
109
109
  var _a, _b, _c;
110
110
  const node = props.gameObject;
111
- if (!node || node.hidden || node.disabled)
111
+ if (!node || node.disabled)
112
112
  return null;
113
113
  const isInstanced = (_c = (_b = (_a = node.components) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.instanced;
114
114
  const prevInstancedRef = useRef(undefined);
@@ -1,10 +1,5 @@
1
+ import type { RigidBodyOptions } from "@react-three/rapier";
1
2
  import { Component } from "./ComponentRegistry";
2
- export interface PhysicsProps {
3
- type: "fixed" | "dynamic";
4
- collider?: string;
5
- mass?: number;
6
- restitution?: number;
7
- friction?: number;
8
- }
3
+ export type PhysicsProps = RigidBodyOptions;
9
4
  declare const PhysicsComponent: Component;
10
5
  export default PhysicsComponent;
@@ -1,3 +1,14 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { jsx as _jsx } from "react/jsx-runtime";
2
13
  import { RigidBody } from "@react-three/rapier";
3
14
  import { FieldRenderer } from "./Input";
@@ -9,10 +20,12 @@ const physicsFields = [
9
20
  options: [
10
21
  { value: 'dynamic', label: 'Dynamic' },
11
22
  { value: 'fixed', label: 'Fixed' },
23
+ { value: 'kinematicPosition', label: 'Kinematic Position' },
24
+ { value: 'kinematicVelocity', label: 'Kinematic Velocity' },
12
25
  ],
13
26
  },
14
27
  {
15
- name: 'collider',
28
+ name: 'colliders',
16
29
  type: 'select',
17
30
  label: 'Collider',
18
31
  options: [
@@ -22,24 +35,65 @@ const physicsFields = [
22
35
  { value: 'ball', label: 'Ball (sphere)' },
23
36
  ],
24
37
  },
38
+ {
39
+ name: 'mass',
40
+ type: 'number',
41
+ label: 'Mass',
42
+ },
43
+ {
44
+ name: 'restitution',
45
+ type: 'number',
46
+ label: 'Restitution (Bounciness)',
47
+ min: 0,
48
+ max: 1,
49
+ step: 0.1,
50
+ },
51
+ {
52
+ name: 'friction',
53
+ type: 'number',
54
+ label: 'Friction',
55
+ min: 0,
56
+ step: 0.1,
57
+ },
58
+ {
59
+ name: 'linearDamping',
60
+ type: 'number',
61
+ label: 'Linear Damping',
62
+ min: 0,
63
+ step: 0.1,
64
+ },
65
+ {
66
+ name: 'angularDamping',
67
+ type: 'number',
68
+ label: 'Angular Damping',
69
+ min: 0,
70
+ step: 0.1,
71
+ },
72
+ {
73
+ name: 'gravityScale',
74
+ type: 'number',
75
+ label: 'Gravity Scale',
76
+ step: 0.1,
77
+ },
25
78
  ];
26
79
  function PhysicsComponentEditor({ component, onUpdate }) {
27
- return (_jsx(FieldRenderer, { fields: physicsFields, values: component.properties, onChange: onUpdate }));
80
+ return (_jsx(FieldRenderer, { fields: physicsFields, values: component.properties, onChange: (props) => onUpdate(Object.assign(Object.assign({}, component), { properties: Object.assign(Object.assign({}, component.properties), props) })) }));
28
81
  }
29
82
  function PhysicsComponentView({ properties, children, position, rotation, scale, editMode }) {
30
- const colliders = properties.collider || (properties.type === 'fixed' ? 'trimesh' : 'hull');
83
+ const { type, colliders } = properties, otherProps = __rest(properties, ["type", "colliders"]);
84
+ const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
31
85
  // In edit mode, include position/rotation in key to force remount when transform changes
32
86
  // This ensures the RigidBody debug visualization updates even when physics is paused
33
87
  const rbKey = editMode
34
- ? `${properties.type || 'dynamic'}_${colliders}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
35
- : `${properties.type || 'dynamic'}_${colliders}`;
36
- return (_jsx(RigidBody, { type: properties.type, colliders: colliders, position: position, rotation: rotation, scale: scale, children: children }, rbKey));
88
+ ? `${type || 'dynamic'}_${colliderType}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
89
+ : `${type || 'dynamic'}_${colliderType}`;
90
+ return (_jsx(RigidBody, Object.assign({ type: type, colliders: colliderType, position: position, rotation: rotation, scale: scale }, otherProps, { children: children }), rbKey));
37
91
  }
38
92
  const PhysicsComponent = {
39
93
  name: 'Physics',
40
94
  Editor: PhysicsComponentEditor,
41
95
  View: PhysicsComponentView,
42
96
  nonComposable: true,
43
- defaultProperties: { type: 'dynamic', collider: 'hull' }
97
+ defaultProperties: { type: 'dynamic', colliders: 'hull' }
44
98
  };
45
99
  export default PhysicsComponent;
@@ -7,7 +7,6 @@ export interface GameObject {
7
7
  id: string;
8
8
  name?: string;
9
9
  disabled?: boolean;
10
- hidden?: boolean;
11
10
  children?: GameObject[];
12
11
  components?: {
13
12
  [key: string]: ComponentData | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.48",
3
+ "version": "0.0.49",
4
4
  "description": "Batteries included React Three Fiber game engine",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -1 +1,7 @@
1
1
  Agent skill for [react-three-game](https://github.com/prnthh/react-three-game)
2
+
3
+ Gives your agent the ability to make 3D scenes, physics simulations and games.
4
+
5
+ ```
6
+ npx skills add https://github.com/prnthh/react-three-game-skill
7
+ ```
@@ -82,7 +82,6 @@ Every game object follows this schema:
82
82
  interface GameObject {
83
83
  id: string;
84
84
  disabled?: boolean;
85
- hidden?: boolean;
86
85
  components?: Record<string, { type: string; properties: any }>;
87
86
  children?: GameObject[];
88
87
  }
@@ -117,7 +116,7 @@ Scenes are defined as JSON prefabs with a root node containing children:
117
116
  | Transform | `Transform` | `position: [x,y,z]`, `rotation: [x,y,z]` (radians), `scale: [x,y,z]` |
118
117
  | Geometry | `Geometry` | `geometryType`: box/sphere/plane/cylinder, `args`: dimension array |
119
118
  | Material | `Material` | `color`, `texture?`, `metalness?`, `roughness?`, `repeat?`, `repeatCount?` |
120
- | Physics | `Physics` | `type`: "dynamic" or "fixed" |
119
+ | Physics | `Physics` | `type`: dynamic/fixed/kinematicPosition/kinematicVelocity, `mass?`, `restitution?`, `friction?`, `linearDamping?`, `angularDamping?`, `gravityScale?`, plus any Rapier RigidBody props |
121
120
  | Model | `Model` | `filename` (GLB/FBX path), `instanced?` for GPU batching |
122
121
  | SpotLight | `SpotLight` | `color`, `intensity`, `angle`, `penumbra`, `distance?`, `castShadow?` |
123
122
  | DirectionalLight | `DirectionalLight` | `color`, `intensity`, `castShadow?`, `targetOffset?: [x,y,z]` |
@@ -165,7 +164,7 @@ Use radians: `1.57` = 90°, `3.14` = 180°, `-1.57` = -90°
165
164
 
166
165
  ### Usage Modes
167
166
 
168
- **GameCanvas + PrefabRoot**: Production gameplay. Requires explicit `<Physics>` wrapper. Physics always active. Can compose with other R3F components. For headless mode, use `<PrefabRoot>` without GameCanvas.
167
+ **GameCanvas + PrefabRoot**: Pure renderer for embedding prefab data in standard R3F applications. Minimal wrapper - just renders the prefab as Three.js objects. Requires manual `<Physics>` setup. Physics always active. Use this to integrate prefabs into larger R3F scenes.
169
168
 
170
169
  ```jsx
171
170
  import { Physics } from '@react-three/rapier';
@@ -174,16 +173,19 @@ import { GameCanvas, PrefabRoot } from 'react-three-game';
174
173
  <GameCanvas>
175
174
  <Physics>
176
175
  <PrefabRoot data={prefabData} />
176
+ <CustomComponent />
177
177
  </Physics>
178
178
  </GameCanvas>
179
179
  ```
180
180
 
181
- **PrefabEditor**: Level editors, scene authoring, prototyping. Includes canvas, physics, UI. Physics activates in play mode only.
181
+ **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.
182
182
 
183
183
  ```jsx
184
184
  import { PrefabEditor } from 'react-three-game';
185
185
 
186
- <PrefabEditor initialPrefab={prefabData} />
186
+ <PrefabEditor initialPrefab={prefabData}>
187
+ <CustomComponent />
188
+ </PrefabEditor>
187
189
  ```
188
190
 
189
191
  ### Tree Utilities
@@ -25,8 +25,6 @@ export interface GroundOptions {
25
25
  /** Physics body type. Defaults to "fixed". */
26
26
  physicsType?: "fixed" | "dynamic" | "kinematic";
27
27
 
28
- /** Set true to hide the node. */
29
- hidden?: boolean;
30
28
  /** Set true to disable the node. */
31
29
  disabled?: boolean;
32
30
  }
@@ -52,14 +50,12 @@ export function ground(options: GroundOptions = {}): GameObject {
52
50
  repeat = texture ? true : false,
53
51
  repeatCount = [25, 25],
54
52
  physicsType = "fixed",
55
- hidden = false,
56
53
  disabled = false,
57
54
  } = options;
58
55
 
59
56
  return {
60
57
  id,
61
58
  disabled,
62
- hidden,
63
59
  components: {
64
60
  transform: {
65
61
  type: "Transform",
@@ -175,7 +175,7 @@ export const PrefabRoot = forwardRef<PrefabRootRef, {
175
175
 
176
176
  export function GameObjectRenderer(props: RendererProps) {
177
177
  const node = props.gameObject;
178
- if (!node || node.hidden || node.disabled) return null;
178
+ if (!node || node.disabled) return null;
179
179
 
180
180
  const isInstanced = node.components?.model?.properties?.instanced;
181
181
  const prevInstancedRef = useRef<boolean | undefined>(undefined);
@@ -1,17 +1,11 @@
1
1
  import { RigidBody, RapierRigidBody } from "@react-three/rapier";
2
+ import type { RigidBodyOptions } from "@react-three/rapier";
2
3
  import type { ReactNode } from 'react';
3
- import { useEffect, useRef } from 'react';
4
4
  import { Component } from "./ComponentRegistry";
5
5
  import { FieldRenderer, FieldDefinition } from "./Input";
6
- import { Quaternion, Euler } from 'three';
6
+ import { ComponentData } from "../types";
7
7
 
8
- export interface PhysicsProps {
9
- type: "fixed" | "dynamic";
10
- collider?: string;
11
- mass?: number;
12
- restitution?: number;
13
- friction?: number;
14
- }
8
+ export type PhysicsProps = RigidBodyOptions;
15
9
 
16
10
  const physicsFields: FieldDefinition[] = [
17
11
  {
@@ -21,10 +15,12 @@ const physicsFields: FieldDefinition[] = [
21
15
  options: [
22
16
  { value: 'dynamic', label: 'Dynamic' },
23
17
  { value: 'fixed', label: 'Fixed' },
18
+ { value: 'kinematicPosition', label: 'Kinematic Position' },
19
+ { value: 'kinematicVelocity', label: 'Kinematic Velocity' },
24
20
  ],
25
21
  },
26
22
  {
27
- name: 'collider',
23
+ name: 'colliders',
28
24
  type: 'select',
29
25
  label: 'Collider',
30
26
  options: [
@@ -34,20 +30,60 @@ const physicsFields: FieldDefinition[] = [
34
30
  { value: 'ball', label: 'Ball (sphere)' },
35
31
  ],
36
32
  },
33
+ {
34
+ name: 'mass',
35
+ type: 'number',
36
+ label: 'Mass',
37
+ },
38
+ {
39
+ name: 'restitution',
40
+ type: 'number',
41
+ label: 'Restitution (Bounciness)',
42
+ min: 0,
43
+ max: 1,
44
+ step: 0.1,
45
+ },
46
+ {
47
+ name: 'friction',
48
+ type: 'number',
49
+ label: 'Friction',
50
+ min: 0,
51
+ step: 0.1,
52
+ },
53
+ {
54
+ name: 'linearDamping',
55
+ type: 'number',
56
+ label: 'Linear Damping',
57
+ min: 0,
58
+ step: 0.1,
59
+ },
60
+ {
61
+ name: 'angularDamping',
62
+ type: 'number',
63
+ label: 'Angular Damping',
64
+ min: 0,
65
+ step: 0.1,
66
+ },
67
+ {
68
+ name: 'gravityScale',
69
+ type: 'number',
70
+ label: 'Gravity Scale',
71
+ step: 0.1,
72
+ },
37
73
  ];
38
74
 
39
- function PhysicsComponentEditor({ component, onUpdate }: { component: { properties: { type?: 'dynamic' | 'fixed'; collider?: string;[k: string]: any } }; onUpdate: (props: Partial<Record<string, any>>) => void }) {
75
+ function PhysicsComponentEditor({ component, onUpdate }: { component: ComponentData; onUpdate: (newComp: any) => void }) {
40
76
  return (
41
77
  <FieldRenderer
42
78
  fields={physicsFields}
43
79
  values={component.properties}
44
- onChange={onUpdate}
80
+ onChange={(props) => onUpdate({ ...component, properties: { ...component.properties, ...props } })}
45
81
  />
46
82
  );
47
83
  }
48
84
 
49
85
  interface PhysicsViewProps {
50
- properties: { type?: 'dynamic' | 'fixed'; collider?: string };
86
+ properties: PhysicsProps;
51
87
  editMode?: boolean;
52
88
  children?: ReactNode;
53
89
  position?: [number, number, number];
@@ -56,22 +92,24 @@ interface PhysicsViewProps {
56
92
  }
57
93
 
58
94
  function PhysicsComponentView({ properties, children, position, rotation, scale, editMode }: PhysicsViewProps) {
59
- const colliders = properties.collider || (properties.type === 'fixed' ? 'trimesh' : 'hull');
95
+ const { type, colliders, ...otherProps } = properties;
96
+ const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
60
97
 
61
98
  // In edit mode, include position/rotation in key to force remount when transform changes
62
99
  // This ensures the RigidBody debug visualization updates even when physics is paused
63
100
  const rbKey = editMode
64
- ? `${properties.type || 'dynamic'}_${colliders}_${position?.join(',')}_${rotation?.join(',')}`
65
- : `${properties.type || 'dynamic'}_${colliders}`;
101
+ ? `${type || 'dynamic'}_${colliderType}_${position?.join(',')}_${rotation?.join(',')}`
102
+ : `${type || 'dynamic'}_${colliderType}`;
66
103
 
67
104
  return (
68
105
  <RigidBody
69
106
  key={rbKey}
70
- type={properties.type}
71
- colliders={colliders as any}
107
+ type={type}
108
+ colliders={colliderType as any}
72
109
  position={position}
73
110
  rotation={rotation}
74
111
  scale={scale}
112
+ {...otherProps}
75
113
  >
76
114
  {children}
77
115
  </RigidBody>
@@ -83,7 +121,7 @@ const PhysicsComponent: Component = {
83
121
  Editor: PhysicsComponentEditor,
84
122
  View: PhysicsComponentView,
85
123
  nonComposable: true,
86
- defaultProperties: { type: 'dynamic', collider: 'hull' }
124
+ defaultProperties: { type: 'dynamic', colliders: 'hull' }
87
125
  };
88
126
 
89
- export default PhysicsComponent;
127
+ export default PhysicsComponent;
@@ -8,7 +8,6 @@ export interface GameObject {
8
8
  id: string;
9
9
  name?: string;
10
10
  disabled?: boolean;
11
- hidden?: boolean;
12
11
  children?: GameObject[];
13
12
  components?: {
14
13
  [key: string]: ComponentData | undefined;