react-three-game 0.0.12 → 0.0.14

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.
Binary file
Binary file
@@ -75,17 +75,14 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
75
75
  localMatrix.decompose(lp, lq, ls);
76
76
  const le = new Euler().setFromQuaternion(lq);
77
77
  // 4. Write back LOCAL transform into the prefab node
78
- const newRoot = updatePrefabNode(data.root, selectedId, (node) => {
79
- var _a, _b, _c;
80
- return (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node === null || node === void 0 ? void 0 : node.components), { transform: {
81
- type: "Transform",
82
- properties: {
83
- position: [lp.x, lp.y, lp.z],
84
- rotation: [le.x, le.y, le.z],
85
- scale: [ls.x, ls.y, ls.z],
86
- },
87
- }, geometry: (_a = node.components) === null || _a === void 0 ? void 0 : _a.geometry, material: (_b = node.components) === null || _b === void 0 ? void 0 : _b.material, model: (_c = node.components) === null || _c === void 0 ? void 0 : _c.model }) }));
88
- });
78
+ const newRoot = updatePrefabNode(data.root, selectedId, (node) => (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node === null || node === void 0 ? void 0 : node.components), { transform: {
79
+ type: "Transform",
80
+ properties: {
81
+ position: [lp.x, lp.y, lp.z],
82
+ rotation: [le.x, le.y, le.z],
83
+ scale: [ls.x, ls.y, ls.z],
84
+ },
85
+ } }) })));
89
86
  onPrefabChange(Object.assign(Object.assign({}, data), { root: newRoot }));
90
87
  };
91
88
  useEffect(() => {
@@ -173,11 +170,11 @@ function GameObjectRenderer({ gameObject, selectedId, onSelect, registerRef, loa
173
170
  // --- 5. Render children (always relative transforms) ---
174
171
  const children = ((_d = gameObject.children) !== null && _d !== void 0 ? _d : []).map((child) => (_jsx(GameObjectRenderer, { gameObject: child, selectedId: selectedId, onSelect: onSelect, registerRef: registerRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: worldMatrix }, child.id)));
175
172
  // --- 4. Wrap with physics if needed ---
176
- // Combine core and children so they both get wrapped by physics (if present)
177
- const content = (_jsxs(_Fragment, { children: [core, children] }));
178
- const physicsWrapped = wrapPhysicsIfNeeded(gameObject, content, ctx);
173
+ // Only wrap the core content (geometry/model), not children
174
+ // Children should be siblings, not inside the physics body
175
+ const physicsWrapped = wrapPhysicsIfNeeded(gameObject, core, ctx);
179
176
  // --- 6. Final group wrapper ---
180
- return (_jsx("group", { ref: (el) => registerRef(gameObject.id, el), position: transformProps.position, rotation: transformProps.rotation, scale: transformProps.scale, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, children: physicsWrapped }));
177
+ return (_jsxs("group", { ref: (el) => registerRef(gameObject.id, el), position: transformProps.position, rotation: transformProps.rotation, scale: transformProps.scale, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, children: [physicsWrapped, children] }));
181
178
  }
182
179
  // Helper: render an instanced GameInstance (terminal node)
183
180
  function renderInstancedNode(gameObject, worldMatrix, ctx) {
@@ -211,10 +208,12 @@ function renderCoreNode(gameObject, ctx, parentMatrix) {
211
208
  };
212
209
  const allComponentViews = gameObject.components
213
210
  ? Object.entries(gameObject.components)
214
- .filter(([key]) => key !== 'geometry' && key !== 'material' && key !== 'model')
211
+ .filter(([key]) => key !== 'geometry' && key !== 'material' && key !== 'model' && key !== 'transform' && key !== 'physics')
215
212
  .map(([key, comp]) => {
216
- const def = getComponent(key);
217
- if (!def || !def.View || !comp)
213
+ if (!comp || !comp.type)
214
+ return null;
215
+ const def = getComponent(comp.type);
216
+ if (!def || !def.View)
218
217
  return null;
219
218
  return _jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key);
220
219
  })
@@ -240,7 +239,7 @@ function wrapPhysicsIfNeeded(gameObject, content, ctx) {
240
239
  const physicsDef = getComponent('Physics');
241
240
  if (!physicsDef || !physicsDef.View)
242
241
  return content;
243
- return (_jsx(physicsDef.View, { properties: Object.assign(Object.assign({}, physics.properties), { id: gameObject.id }), registerRef: ctx.registerRef, editMode: ctx.editMode, children: content }));
242
+ return (_jsx(physicsDef.View, { properties: Object.assign(Object.assign({}, physics.properties), { id: gameObject.id }), editMode: ctx.editMode, children: content }));
244
243
  }
245
244
  export default PrefabRoot;
246
245
  function getNodeTransformProps(node) {
@@ -3,10 +3,10 @@ function PhysicsComponentEditor({ component, onUpdate }) {
3
3
  return _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Type" }), _jsxs("select", { className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: component.properties.type, onChange: e => onUpdate({ type: e.target.value }), children: [_jsx("option", { value: "dynamic", children: "Dynamic" }), _jsx("option", { value: "fixed", children: "Fixed" })] })] });
4
4
  }
5
5
  import { RigidBody } from "@react-three/rapier";
6
- function PhysicsComponentView({ properties, children, registerRef, transform, editMode }) {
6
+ function PhysicsComponentView({ properties, children, editMode }) {
7
7
  if (editMode)
8
8
  return children;
9
- return (_jsx(RigidBody, { ref: el => registerRef && registerRef(properties.id, el), position: transform === null || transform === void 0 ? void 0 : transform.position, rotation: transform === null || transform === void 0 ? void 0 : transform.rotation, scale: transform === null || transform === void 0 ? void 0 : transform.scale, type: properties.type, colliders: "cuboid", children: children }));
9
+ return (_jsx(RigidBody, { type: properties.type, colliders: "cuboid", children: children }));
10
10
  }
11
11
  const PhysicsComponent = {
12
12
  name: 'Physics',
@@ -1,19 +1,33 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  function SpotLightComponentEditor({ component, onUpdate }) {
3
- return _jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Color" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "color", className: "h-5 w-5 bg-transparent border-none cursor-pointer", value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) }), _jsx("input", { type: "text", className: "flex-1 bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Intensity" }), _jsx("input", { type: "number", step: "0.1", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: component.properties.intensity, onChange: e => onUpdate({ 'intensity': parseFloat(e.target.value) }) })] })] });
3
+ var _a, _b, _c, _d, _e, _f;
4
+ // Provide default values to prevent NaN
5
+ const props = {
6
+ color: (_a = component.properties.color) !== null && _a !== void 0 ? _a : '#ffffff',
7
+ intensity: (_b = component.properties.intensity) !== null && _b !== void 0 ? _b : 1.0,
8
+ angle: (_c = component.properties.angle) !== null && _c !== void 0 ? _c : Math.PI / 6,
9
+ penumbra: (_d = component.properties.penumbra) !== null && _d !== void 0 ? _d : 0.5,
10
+ distance: (_e = component.properties.distance) !== null && _e !== void 0 ? _e : 100,
11
+ castShadow: (_f = component.properties.castShadow) !== null && _f !== void 0 ? _f : true
12
+ };
13
+ return _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Color" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "color", className: "h-5 w-5 bg-transparent border-none cursor-pointer", value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) }), _jsx("input", { type: "text", className: "flex-1 bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Intensity" }), _jsx("input", { type: "number", step: "0.1", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.intensity, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'intensity': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Angle" }), _jsx("input", { type: "number", step: "0.1", min: "0", max: Math.PI, className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.angle, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'angle': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Penumbra" }), _jsx("input", { type: "number", step: "0.1", min: "0", max: "1", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.penumbra, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'penumbra': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Distance" }), _jsx("input", { type: "number", step: "1", min: "0", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: props.distance, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'distance': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Cast Shadow" }), _jsx("input", { type: "checkbox", className: "h-4 w-4 bg-black/40 border border-cyan-500/30 cursor-pointer", checked: props.castShadow, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'castShadow': e.target.checked })) })] })] });
4
14
  }
5
15
  // The view component for SpotLight
6
16
  function SpotLightView({ properties }) {
7
- // You can expand this with more spotlight properties as needed
8
- return _jsx("spotLight", { color: properties.color, intensity: properties.intensity });
17
+ var _a, _b, _c, _d, _e, _f;
18
+ // Provide defaults in case properties are missing
19
+ const color = (_a = properties.color) !== null && _a !== void 0 ? _a : '#ffffff';
20
+ const intensity = (_b = properties.intensity) !== null && _b !== void 0 ? _b : 1.0;
21
+ const angle = (_c = properties.angle) !== null && _c !== void 0 ? _c : Math.PI / 6;
22
+ const penumbra = (_d = properties.penumbra) !== null && _d !== void 0 ? _d : 0.5;
23
+ const distance = (_e = properties.distance) !== null && _e !== void 0 ? _e : 100;
24
+ const castShadow = (_f = properties.castShadow) !== null && _f !== void 0 ? _f : true;
25
+ return (_jsx(_Fragment, { children: _jsx("spotLight", { color: color, intensity: intensity, angle: angle, penumbra: penumbra, distance: distance, castShadow: castShadow }) }));
9
26
  }
10
27
  const SpotLightComponent = {
11
28
  name: 'SpotLight',
12
29
  Editor: SpotLightComponentEditor,
13
30
  View: SpotLightView,
14
- defaultProperties: {
15
- color: '#ffffff',
16
- intensity: 1.0
17
- }
31
+ defaultProperties: {}
18
32
  };
19
33
  export default SpotLightComponent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Batteries included React Three Fiber game engine",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -103,9 +103,6 @@ export const PrefabRoot = forwardRef<Group, {
103
103
  scale: [ls.x, ls.y, ls.z] as [number, number, number],
104
104
  },
105
105
  },
106
- geometry: node.components?.geometry,
107
- material: node.components?.material,
108
- model: node.components?.model,
109
106
  },
110
107
  }));
111
108
 
@@ -272,14 +269,9 @@ function GameObjectRenderer({
272
269
  ));
273
270
 
274
271
  // --- 4. Wrap with physics if needed ---
275
- // Combine core and children so they both get wrapped by physics (if present)
276
- const content = (
277
- <>
278
- {core}
279
- {children}
280
- </>
281
- );
282
- const physicsWrapped = wrapPhysicsIfNeeded(gameObject, content, ctx);
272
+ // Only wrap the core content (geometry/model), not children
273
+ // Children should be siblings, not inside the physics body
274
+ const physicsWrapped = wrapPhysicsIfNeeded(gameObject, core, ctx);
283
275
 
284
276
  // --- 6. Final group wrapper ---
285
277
  return (
@@ -293,6 +285,7 @@ function GameObjectRenderer({
293
285
  onPointerUp={handlePointerUp}
294
286
  >
295
287
  {physicsWrapped}
288
+ {children}
296
289
  </group>
297
290
  );
298
291
  }
@@ -340,10 +333,11 @@ function renderCoreNode(gameObject: GameObjectType, ctx: any, parentMatrix: Matr
340
333
  };
341
334
  const allComponentViews = gameObject.components
342
335
  ? Object.entries(gameObject.components)
343
- .filter(([key]) => key !== 'geometry' && key !== 'material' && key !== 'model')
336
+ .filter(([key]) => key !== 'geometry' && key !== 'material' && key !== 'model' && key !== 'transform' && key !== 'physics')
344
337
  .map(([key, comp]) => {
345
- const def = getComponent(key);
346
- if (!def || !def.View || !comp) return null;
338
+ if (!comp || !comp.type) return null;
339
+ const def = getComponent(comp.type);
340
+ if (!def || !def.View) return null;
347
341
  return <def.View key={key} properties={comp.properties} {...contextProps} />;
348
342
  })
349
343
  : null;
@@ -403,7 +397,6 @@ function wrapPhysicsIfNeeded(gameObject: GameObjectType, content: React.ReactNod
403
397
  return (
404
398
  <physicsDef.View
405
399
  properties={{ ...physics.properties, id: gameObject.id }}
406
- registerRef={ctx.registerRef}
407
400
  editMode={ctx.editMode}
408
401
  >
409
402
  {content}
@@ -16,17 +16,11 @@ function PhysicsComponentEditor({ component, onUpdate }: { component: any; onUpd
16
16
 
17
17
 
18
18
  import { RigidBody } from "@react-three/rapier";
19
- import { Object3D } from "three";
20
- import { useRef } from "react";
21
19
 
22
- function PhysicsComponentView({ properties, children, registerRef, transform, editMode }: any) {
20
+ function PhysicsComponentView({ properties, children, editMode }: any) {
23
21
  if (editMode) return children;
24
22
  return (
25
23
  <RigidBody
26
- ref={el => registerRef && registerRef(properties.id, el as unknown as Object3D)}
27
- position={transform?.position}
28
- rotation={transform?.rotation}
29
- scale={transform?.scale}
30
24
  type={properties.type}
31
25
  colliders="cuboid"
32
26
  >
@@ -1,22 +1,33 @@
1
1
 
2
2
  import { Component } from "./ComponentRegistry";
3
+ import { useRef } from "react";
3
4
 
4
5
  function SpotLightComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
5
- return <div className="flex flex-col">
6
- <div className="mb-1">
6
+ // Provide default values to prevent NaN
7
+ const props = {
8
+ color: component.properties.color ?? '#ffffff',
9
+ intensity: component.properties.intensity ?? 1.0,
10
+ angle: component.properties.angle ?? Math.PI / 6,
11
+ penumbra: component.properties.penumbra ?? 0.5,
12
+ distance: component.properties.distance ?? 100,
13
+ castShadow: component.properties.castShadow ?? true
14
+ };
15
+
16
+ return <div className="flex flex-col gap-2">
17
+ <div>
7
18
  <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Color</label>
8
19
  <div className="flex gap-0.5">
9
20
  <input
10
21
  type="color"
11
22
  className="h-5 w-5 bg-transparent border-none cursor-pointer"
12
- value={component.properties.color}
13
- onChange={e => onUpdate({ 'color': e.target.value })}
23
+ value={props.color}
24
+ onChange={e => onUpdate({ ...component.properties, 'color': e.target.value })}
14
25
  />
15
26
  <input
16
27
  type="text"
17
28
  className="flex-1 bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50"
18
- value={component.properties.color}
19
- onChange={e => onUpdate({ 'color': e.target.value })}
29
+ value={props.color}
30
+ onChange={e => onUpdate({ ...component.properties, 'color': e.target.value })}
20
31
  />
21
32
  </div>
22
33
  </div>
@@ -26,8 +37,52 @@ function SpotLightComponentEditor({ component, onUpdate }: { component: any; onU
26
37
  type="number"
27
38
  step="0.1"
28
39
  className="w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50"
29
- value={component.properties.intensity}
30
- onChange={e => onUpdate({ 'intensity': parseFloat(e.target.value) })}
40
+ value={props.intensity}
41
+ onChange={e => onUpdate({ ...component.properties, 'intensity': parseFloat(e.target.value) })}
42
+ />
43
+ </div>
44
+ <div>
45
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Angle</label>
46
+ <input
47
+ type="number"
48
+ step="0.1"
49
+ min="0"
50
+ max={Math.PI}
51
+ className="w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50"
52
+ value={props.angle}
53
+ onChange={e => onUpdate({ ...component.properties, 'angle': parseFloat(e.target.value) })}
54
+ />
55
+ </div>
56
+ <div>
57
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Penumbra</label>
58
+ <input
59
+ type="number"
60
+ step="0.1"
61
+ min="0"
62
+ max="1"
63
+ className="w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50"
64
+ value={props.penumbra}
65
+ onChange={e => onUpdate({ ...component.properties, 'penumbra': parseFloat(e.target.value) })}
66
+ />
67
+ </div>
68
+ <div>
69
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Distance</label>
70
+ <input
71
+ type="number"
72
+ step="1"
73
+ min="0"
74
+ className="w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50"
75
+ value={props.distance}
76
+ onChange={e => onUpdate({ ...component.properties, 'distance': parseFloat(e.target.value) })}
77
+ />
78
+ </div>
79
+ <div>
80
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Cast Shadow</label>
81
+ <input
82
+ type="checkbox"
83
+ className="h-4 w-4 bg-black/40 border border-cyan-500/30 cursor-pointer"
84
+ checked={props.castShadow}
85
+ onChange={e => onUpdate({ ...component.properties, 'castShadow': e.target.checked })}
31
86
  />
32
87
  </div>
33
88
  </div>;
@@ -36,18 +91,33 @@ function SpotLightComponentEditor({ component, onUpdate }: { component: any; onU
36
91
 
37
92
  // The view component for SpotLight
38
93
  function SpotLightView({ properties }: { properties: any }) {
39
- // You can expand this with more spotlight properties as needed
40
- return <spotLight color={properties.color} intensity={properties.intensity} />;
94
+ // Provide defaults in case properties are missing
95
+ const color = properties.color ?? '#ffffff';
96
+ const intensity = properties.intensity ?? 1.0;
97
+ const angle = properties.angle ?? Math.PI / 6;
98
+ const penumbra = properties.penumbra ?? 0.5;
99
+ const distance = properties.distance ?? 100;
100
+ const castShadow = properties.castShadow ?? true;
101
+
102
+ return (
103
+ <>
104
+ <spotLight
105
+ color={color}
106
+ intensity={intensity}
107
+ angle={angle}
108
+ penumbra={penumbra}
109
+ distance={distance}
110
+ castShadow={castShadow}
111
+ />
112
+ </>
113
+ );
41
114
  }
42
115
 
43
116
  const SpotLightComponent: Component = {
44
117
  name: 'SpotLight',
45
118
  Editor: SpotLightComponentEditor,
46
119
  View: SpotLightView,
47
- defaultProperties: {
48
- color: '#ffffff',
49
- intensity: 1.0
50
- }
120
+ defaultProperties: {}
51
121
  };
52
122
 
53
123
  export default SpotLightComponent;