react-three-game 0.0.62 → 0.0.63
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/README.md +5 -1
- package/dist/shared/GameCanvas.d.ts +1 -1
- package/dist/shared/GameCanvas.js +2 -2
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +2 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +9 -7
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +2 -2
- package/dist/tools/prefabeditor/PrefabRoot.js +9 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
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 @react-three/rapier three
|
|
6
|
+
npm i react-three-game @react-three/drei @react-three/fiber @react-three/rapier three
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|

|
|
@@ -102,8 +102,10 @@ interface GameObject {
|
|
|
102
102
|
## Custom Components
|
|
103
103
|
|
|
104
104
|
```tsx
|
|
105
|
+
import { useRef } from 'react';
|
|
105
106
|
import { Component, registerComponent, FieldRenderer, FieldDefinition } from 'react-three-game';
|
|
106
107
|
import { useFrame } from '@react-three/fiber';
|
|
108
|
+
import type { Group } from 'three';
|
|
107
109
|
|
|
108
110
|
const rotatorFields: FieldDefinition[] = [
|
|
109
111
|
{ name: 'speed', type: 'number', label: 'Speed', step: 0.1 },
|
|
@@ -214,6 +216,7 @@ export function EmbeddedEditor({ prefab, onPrefabChange }: {
|
|
|
214
216
|
showUI={false}
|
|
215
217
|
physics={false}
|
|
216
218
|
enableWindowDrop={false}
|
|
219
|
+
canvasProps={{ style: { height: '100%', width: '100%' } }}
|
|
217
220
|
/>
|
|
218
221
|
</div>
|
|
219
222
|
);
|
|
@@ -226,6 +229,7 @@ export function EmbeddedEditor({ prefab, onPrefabChange }: {
|
|
|
226
229
|
- `addModel(path, model, options?)` creates a model node and injects the runtime asset in one step.
|
|
227
230
|
- `addTexture(path, texture, options?)` creates a textured plane node and injects the runtime texture in one step.
|
|
228
231
|
- `exportGLBData()` returns the GLB `ArrayBuffer` without triggering a download.
|
|
232
|
+
- `canvasProps` forwards canvas-level sizing, camera, event, and style props to `GameCanvas`.
|
|
229
233
|
- `setPrefab(prefab)` remains as a backward-compatible alias for `replacePrefab(prefab)`.
|
|
230
234
|
|
|
231
235
|
Keys: **T**ranslate / **R**otate / **S**cale. Drag tree nodes to reparent. Physics only runs in play mode.
|
|
@@ -6,5 +6,5 @@ interface GameCanvasProps extends Omit<CanvasProps, 'children'> {
|
|
|
6
6
|
glConfig?: WebGPURendererParameters;
|
|
7
7
|
canvasRef?: React.RefObject<HTMLCanvasElement | null>;
|
|
8
8
|
}
|
|
9
|
-
export default function GameCanvas({ loader, children, glConfig, canvasRef, onCreated, ...props }: GameCanvasProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export default function GameCanvas({ loader, children, glConfig, canvasRef, onCreated, style, ...props }: GameCanvasProps): import("react/jsx-runtime").JSX.Element;
|
|
10
10
|
export {};
|
|
@@ -31,9 +31,9 @@ extend({
|
|
|
31
31
|
SpriteNodeMaterial: SpriteNodeMaterial,
|
|
32
32
|
});
|
|
33
33
|
export default function GameCanvas(_a) {
|
|
34
|
-
var { loader = false, children, glConfig, canvasRef, onCreated } = _a, props = __rest(_a, ["loader", "children", "glConfig", "canvasRef", "onCreated"]);
|
|
34
|
+
var { loader = false, children, glConfig, canvasRef, onCreated, style } = _a, props = __rest(_a, ["loader", "children", "glConfig", "canvasRef", "onCreated", "style"]);
|
|
35
35
|
const [frameloop, setFrameloop] = useState("never");
|
|
36
|
-
return _jsx(_Fragment, { children: _jsxs(Canvas, Object.assign({ style: { touchAction: 'none', userSelect: 'none' }, shadows: { type: PCFShadowMap, }, frameloop: frameloop, gl: (_a) => __awaiter(this, [_a], void 0, function* ({ canvas }) {
|
|
36
|
+
return _jsx(_Fragment, { children: _jsxs(Canvas, Object.assign({ style: Object.assign({ touchAction: 'none', userSelect: 'none' }, style), shadows: { type: PCFShadowMap, }, frameloop: frameloop, gl: (_a) => __awaiter(this, [_a], void 0, function* ({ canvas }) {
|
|
37
37
|
const renderer = new WebGPURenderer(Object.assign({ canvas: canvas,
|
|
38
38
|
// @ts-expect-error futuristic
|
|
39
39
|
shadowMap: true, antialias: true }, glConfig));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import GameCanvas from "../../shared/GameCanvas";
|
|
1
2
|
import { Object3D, Texture } from "three";
|
|
2
3
|
import { GameObject, Prefab } from "./types";
|
|
3
4
|
import { PrefabRootRef } from "./PrefabRoot";
|
|
@@ -25,6 +26,7 @@ export interface PrefabEditorProps {
|
|
|
25
26
|
onPrefabChange?: (prefab: Prefab) => void;
|
|
26
27
|
showUI?: boolean;
|
|
27
28
|
enableWindowDrop?: boolean;
|
|
29
|
+
canvasProps?: Omit<React.ComponentProps<typeof GameCanvas>, 'children' | 'canvasRef'>;
|
|
28
30
|
uiPlugins?: React.ReactNode[] | React.ReactNode;
|
|
29
31
|
children?: React.ReactNode;
|
|
30
32
|
}
|
|
@@ -30,7 +30,7 @@ const DEFAULT_PREFAB = {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
-
const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPrefabChange, showUI = true, enableWindowDrop = true, uiPlugins, children }, ref) => {
|
|
33
|
+
const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPrefabChange, showUI = true, enableWindowDrop = true, canvasProps, uiPlugins, children }, ref) => {
|
|
34
34
|
const [editMode, setEditMode] = useState(true);
|
|
35
35
|
const [loadedPrefab, setLoadedPrefab] = useState(initialPrefab !== null && initialPrefab !== void 0 ? initialPrefab : DEFAULT_PREFAB);
|
|
36
36
|
const [selectedId, setSelectedId] = useState(null);
|
|
@@ -46,6 +46,8 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
46
46
|
const canvasRef = useRef(null);
|
|
47
47
|
const onPrefabChangeRef = useRef(onPrefabChange);
|
|
48
48
|
const pendingPrefabChangeRef = useRef(null);
|
|
49
|
+
const [injectedModels, setInjectedModels] = useState({});
|
|
50
|
+
const [injectedTextures, setInjectedTextures] = useState({});
|
|
49
51
|
useEffect(() => {
|
|
50
52
|
onPrefabChangeRef.current = onPrefabChange;
|
|
51
53
|
}, [onPrefabChange]);
|
|
@@ -55,6 +57,8 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
55
57
|
lastDataRef.current = JSON.stringify(prefab);
|
|
56
58
|
pendingPrefabChangeRef.current = (options === null || options === void 0 ? void 0 : options.notifyChange) === false ? null : prefab;
|
|
57
59
|
setSelectedId(null);
|
|
60
|
+
setInjectedModels({});
|
|
61
|
+
setInjectedTextures({});
|
|
58
62
|
setHistory([prefab]);
|
|
59
63
|
setHistoryIndex(0);
|
|
60
64
|
setLoadedPrefab(prefab);
|
|
@@ -91,17 +95,15 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
91
95
|
return node;
|
|
92
96
|
};
|
|
93
97
|
const addModel = (path, model, options) => {
|
|
94
|
-
var _a;
|
|
95
98
|
const node = createModelNode(path, options === null || options === void 0 ? void 0 : options.name);
|
|
96
99
|
insertPrefabNode(node, options);
|
|
97
|
-
(
|
|
100
|
+
setInjectedModels(prev => (Object.assign(Object.assign({}, prev), { [path]: model })));
|
|
98
101
|
return node;
|
|
99
102
|
};
|
|
100
103
|
const addTexture = (path, texture, options) => {
|
|
101
|
-
var _a;
|
|
102
104
|
const node = createImageNode(path, options === null || options === void 0 ? void 0 : options.name);
|
|
103
105
|
insertPrefabNode(node, options);
|
|
104
|
-
(
|
|
106
|
+
setInjectedTextures(prev => (Object.assign(Object.assign({}, prev), { [path]: texture })));
|
|
105
107
|
return node;
|
|
106
108
|
};
|
|
107
109
|
const applyHistory = (index) => {
|
|
@@ -225,7 +227,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
225
227
|
addTexture,
|
|
226
228
|
rootRef: prefabRootRef
|
|
227
229
|
}), [loadedPrefab]);
|
|
228
|
-
const content = (_jsxs(_Fragment, { children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { ref: prefabRootRef, data: loadedPrefab, editMode: editMode, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, basePath: basePath }), children] }));
|
|
230
|
+
const content = (_jsxs(_Fragment, { children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { ref: prefabRootRef, data: loadedPrefab, editMode: editMode, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, basePath: basePath, injectedModels: injectedModels, injectedTextures: injectedTextures }), children] }));
|
|
229
231
|
return _jsxs(EditorContext.Provider, { value: {
|
|
230
232
|
transformMode,
|
|
231
233
|
setTransformMode,
|
|
@@ -238,7 +240,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
238
240
|
onFocusNode: handleFocusNode,
|
|
239
241
|
onScreenshot: handleScreenshot,
|
|
240
242
|
onExportGLB: handleExportGLB
|
|
241
|
-
}, children: [_jsx(GameCanvas, { camera: { position: [0, 5, 15] }, canvasRef: canvasRef, children: physics ? (_jsx(Physics, { debug: editMode, paused: editMode, children: content })) : content }), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: () => setEditMode(!editMode), children: editMode ? "▶" : "⏸" }), uiPlugins] }), _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 })] }))] });
|
|
243
|
+
}, children: [_jsx(GameCanvas, Object.assign({ camera: { position: [0, 5, 15] }, canvasRef: canvasRef }, canvasProps, { children: physics ? (_jsx(Physics, { debug: editMode, paused: editMode, children: content })) : content })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: () => setEditMode(!editMode), children: editMode ? "▶" : "⏸" }), uiPlugins] }), _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 })] }))] });
|
|
242
244
|
});
|
|
243
245
|
PrefabEditor.displayName = "PrefabEditor";
|
|
244
246
|
export default PrefabEditor;
|
|
@@ -4,8 +4,6 @@ import { Prefab, GameObject as GameObjectType } from "./types";
|
|
|
4
4
|
export interface PrefabRootRef {
|
|
5
5
|
root: Group | null;
|
|
6
6
|
rigidBodyRefs: Map<string, any>;
|
|
7
|
-
injectModel: (filename: string, model: Object3D) => void;
|
|
8
|
-
injectTexture: (filename: string, texture: Texture) => void;
|
|
9
7
|
focusNode: (nodeId: string) => void;
|
|
10
8
|
}
|
|
11
9
|
export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
|
|
@@ -16,6 +14,8 @@ export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
|
|
|
16
14
|
onSelect?: (id: string | null) => void;
|
|
17
15
|
onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
|
|
18
16
|
basePath?: string;
|
|
17
|
+
injectedModels?: Record<string, Object3D>;
|
|
18
|
+
injectedTextures?: Record<string, Texture>;
|
|
19
19
|
} & import("react").RefAttributes<PrefabRootRef>>;
|
|
20
20
|
export declare function GameObjectRenderer(props: RendererProps): import("react/jsx-runtime").JSX.Element | null;
|
|
21
21
|
interface RendererProps {
|
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
11
|
import { MapControls, TransformControls, useHelper } from "@react-three/drei";
|
|
12
|
-
import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
12
|
+
import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
13
13
|
import { BoxHelper, Euler, Matrix4, Quaternion, SRGBColorSpace, TextureLoader, Vector3, } from "three";
|
|
14
14
|
import { getComponent, registerComponent, getNonComposableKeys } from "./components/ComponentRegistry";
|
|
15
15
|
import components from "./components";
|
|
@@ -19,7 +19,7 @@ import { focusCameraOnObject, updateNode } from "./utils";
|
|
|
19
19
|
import { EditorContext } from "./EditorContext";
|
|
20
20
|
components.forEach(registerComponent);
|
|
21
21
|
const IDENTITY = new Matrix4();
|
|
22
|
-
export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, onClick, basePath = "" }, ref) => {
|
|
22
|
+
export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, onClick, basePath = "", injectedModels = {}, injectedTextures = {} }, ref) => {
|
|
23
23
|
var _a, _b, _c, _d;
|
|
24
24
|
// optional editor context
|
|
25
25
|
const editorContext = useContext(EditorContext);
|
|
@@ -37,18 +37,11 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
37
37
|
const [selectedObject, setSelectedObject] = useState(null);
|
|
38
38
|
const rootRef = useRef(null);
|
|
39
39
|
const controlsRef = useRef(null);
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
}, []);
|
|
43
|
-
const injectTexture = useCallback((filename, texture) => {
|
|
44
|
-
loading.current.add(filename);
|
|
45
|
-
setTextures(t => (Object.assign(Object.assign({}, t), { [filename]: texture })));
|
|
46
|
-
}, []);
|
|
40
|
+
const availableModels = useMemo(() => (Object.assign(Object.assign({}, models), injectedModels)), [models, injectedModels]);
|
|
41
|
+
const availableTextures = useMemo(() => (Object.assign(Object.assign({}, textures), injectedTextures)), [textures, injectedTextures]);
|
|
47
42
|
useImperativeHandle(ref, () => ({
|
|
48
43
|
root: rootRef.current,
|
|
49
44
|
rigidBodyRefs: rigidBodyRefs.current,
|
|
50
|
-
injectModel,
|
|
51
|
-
injectTexture,
|
|
52
45
|
focusNode: (nodeId) => {
|
|
53
46
|
const object = objectRefs.current[nodeId];
|
|
54
47
|
const controls = controlsRef.current;
|
|
@@ -57,7 +50,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
57
50
|
return;
|
|
58
51
|
focusCameraOnObject(object, camera, controls.target, () => { var _a; return (_a = controls.update) === null || _a === void 0 ? void 0 : _a.call(controls); });
|
|
59
52
|
}
|
|
60
|
-
}), [
|
|
53
|
+
}), []);
|
|
61
54
|
const registerRef = useCallback((id, obj) => {
|
|
62
55
|
objectRefs.current[id] = obj;
|
|
63
56
|
if (id === selectedId)
|
|
@@ -107,7 +100,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
107
100
|
texturesToLoad.add(node.components.material.properties.normalMapTexture);
|
|
108
101
|
});
|
|
109
102
|
modelsToLoad.forEach((file) => __awaiter(void 0, void 0, void 0, function* () {
|
|
110
|
-
if (
|
|
103
|
+
if (availableModels[file] || loading.current.has(file))
|
|
111
104
|
return;
|
|
112
105
|
loading.current.add(file);
|
|
113
106
|
const path = file.startsWith("/")
|
|
@@ -121,7 +114,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
121
114
|
}));
|
|
122
115
|
const loader = new TextureLoader();
|
|
123
116
|
texturesToLoad.forEach(file => {
|
|
124
|
-
if (
|
|
117
|
+
if (availableTextures[file] || loading.current.has(file) || failedTextures.current.has(file))
|
|
125
118
|
return;
|
|
126
119
|
loading.current.add(file);
|
|
127
120
|
// Handle full URLs (http/https) or regular paths
|
|
@@ -139,8 +132,8 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
139
132
|
failedTextures.current.add(file);
|
|
140
133
|
});
|
|
141
134
|
});
|
|
142
|
-
}, [data,
|
|
143
|
-
return (_jsxs("group", { ref: rootRef, children: [_jsx(GameInstanceProvider, { models:
|
|
135
|
+
}, [data, availableModels, availableTextures, basePath]);
|
|
136
|
+
return (_jsxs("group", { ref: rootRef, children: [_jsx(GameInstanceProvider, { models: availableModels, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(GameObjectRenderer, { gameObject: data.root, selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: onClick, registerRef: registerRef, registerRigidBodyRef: registerRigidBodyRef, loadedModels: availableModels, loadedTextures: availableTextures, editMode: editMode, parentMatrix: IDENTITY }) }), editMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, makeDefault: true }), selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: onTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: snapResolution > 0 ? snapResolution : undefined }, `transform-${transformMode}-${positionSnap}-${rotationSnap}-${snapResolution}`))] }))] }));
|
|
144
137
|
});
|
|
145
138
|
export function GameObjectRenderer(props) {
|
|
146
139
|
var _a, _b, _c;
|