react-three-game 0.0.62 → 0.0.64
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 +3 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +22 -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";
|
|
@@ -11,6 +12,7 @@ export interface PrefabEditorRef {
|
|
|
11
12
|
screenshot: () => void;
|
|
12
13
|
exportGLB: (options?: ExportGLBOptions) => Promise<ArrayBuffer | object | undefined>;
|
|
13
14
|
exportGLBData: () => Promise<ArrayBuffer | undefined>;
|
|
15
|
+
clearSelection: () => Promise<void>;
|
|
14
16
|
prefab: Prefab;
|
|
15
17
|
setPrefab: (prefab: Prefab) => void;
|
|
16
18
|
replacePrefab: (prefab: Prefab) => void;
|
|
@@ -25,6 +27,7 @@ export interface PrefabEditorProps {
|
|
|
25
27
|
onPrefabChange?: (prefab: Prefab) => void;
|
|
26
28
|
showUI?: boolean;
|
|
27
29
|
enableWindowDrop?: boolean;
|
|
30
|
+
canvasProps?: Omit<React.ComponentProps<typeof GameCanvas>, 'children' | 'canvasRef'>;
|
|
28
31
|
uiPlugins?: React.ReactNode[] | React.ReactNode;
|
|
29
32
|
children?: React.ReactNode;
|
|
30
33
|
}
|
|
@@ -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) => {
|
|
@@ -160,8 +162,19 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
160
162
|
URL.revokeObjectURL(url);
|
|
161
163
|
});
|
|
162
164
|
};
|
|
165
|
+
const clearSelection = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
166
|
+
if (!selectedId)
|
|
167
|
+
return;
|
|
168
|
+
setSelectedId(null);
|
|
169
|
+
yield new Promise(resolve => {
|
|
170
|
+
requestAnimationFrame(() => {
|
|
171
|
+
requestAnimationFrame(() => resolve());
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
163
175
|
const handleExportGLB = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (options = {}) {
|
|
164
176
|
var _a;
|
|
177
|
+
yield clearSelection();
|
|
165
178
|
const sceneRoot = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root;
|
|
166
179
|
if (!sceneRoot)
|
|
167
180
|
return;
|
|
@@ -169,6 +182,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
169
182
|
});
|
|
170
183
|
const handleExportGLBData = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
171
184
|
var _a;
|
|
185
|
+
yield clearSelection();
|
|
172
186
|
const sceneRoot = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root;
|
|
173
187
|
if (!sceneRoot)
|
|
174
188
|
return;
|
|
@@ -218,6 +232,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
218
232
|
screenshot: handleScreenshot,
|
|
219
233
|
exportGLB: handleExportGLB,
|
|
220
234
|
exportGLBData: handleExportGLBData,
|
|
235
|
+
clearSelection,
|
|
221
236
|
prefab: loadedPrefab,
|
|
222
237
|
setPrefab: replacePrefab,
|
|
223
238
|
replacePrefab,
|
|
@@ -225,7 +240,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
225
240
|
addTexture,
|
|
226
241
|
rootRef: prefabRootRef
|
|
227
242
|
}), [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] }));
|
|
243
|
+
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
244
|
return _jsxs(EditorContext.Provider, { value: {
|
|
230
245
|
transformMode,
|
|
231
246
|
setTransformMode,
|
|
@@ -238,7 +253,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
238
253
|
onFocusNode: handleFocusNode,
|
|
239
254
|
onScreenshot: handleScreenshot,
|
|
240
255
|
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 })] }))] });
|
|
256
|
+
}, 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
257
|
});
|
|
243
258
|
PrefabEditor.displayName = "PrefabEditor";
|
|
244
259
|
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;
|