react-three-game 0.0.14 → 0.0.16
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 +50 -66
- package/dist/helpers/index.d.ts +35 -0
- package/dist/helpers/index.js +44 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/shared/GameCanvas.js +13 -13
- package/dist/tools/dragdrop/DragDropLoader.js +1 -0
- package/dist/tools/prefabeditor/EditorTree.js +116 -4
- package/dist/tools/prefabeditor/EditorUI.js +160 -6
- package/dist/tools/prefabeditor/PrefabEditor.js +56 -4
- package/dist/tools/prefabeditor/PrefabRoot.js +1 -10
- package/dist/tools/prefabeditor/types.d.ts +4 -4
- package/dist/tools/prefabeditor/types.js +1 -0
- package/package.json +4 -1
- package/src/helpers/index.ts +95 -0
- package/src/index.ts +3 -0
- package/src/shared/GameCanvas.tsx +5 -2
- package/src/tools/dragdrop/DragDropLoader.tsx +1 -0
- package/src/tools/prefabeditor/EditorTree.tsx +154 -16
- package/src/tools/prefabeditor/EditorUI.tsx +198 -51
- package/src/tools/prefabeditor/PrefabEditor.tsx +67 -7
- package/src/tools/prefabeditor/PrefabRoot.tsx +2 -13
- package/src/tools/prefabeditor/types.ts +5 -5
|
@@ -4,6 +4,46 @@ import EditorTree from './EditorTree';
|
|
|
4
4
|
import { getAllComponents } from './components/ComponentRegistry';
|
|
5
5
|
function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transformMode, setTransformMode, basePath }) {
|
|
6
6
|
const [isInspectorCollapsed, setIsInspectorCollapsed] = useState(false);
|
|
7
|
+
const ui = {
|
|
8
|
+
panel: {
|
|
9
|
+
position: 'absolute',
|
|
10
|
+
top: 8,
|
|
11
|
+
right: 8,
|
|
12
|
+
zIndex: 20,
|
|
13
|
+
width: 260,
|
|
14
|
+
background: 'rgba(0,0,0,0.55)',
|
|
15
|
+
color: 'rgba(255,255,255,0.9)',
|
|
16
|
+
border: '1px solid rgba(255,255,255,0.12)',
|
|
17
|
+
borderRadius: 6,
|
|
18
|
+
overflow: 'hidden',
|
|
19
|
+
backdropFilter: 'blur(6px)',
|
|
20
|
+
WebkitBackdropFilter: 'blur(6px)',
|
|
21
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
22
|
+
fontSize: 11,
|
|
23
|
+
lineHeight: 1.2,
|
|
24
|
+
},
|
|
25
|
+
header: {
|
|
26
|
+
padding: '4px 6px',
|
|
27
|
+
display: 'flex',
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
justifyContent: 'space-between',
|
|
30
|
+
cursor: 'pointer',
|
|
31
|
+
background: 'rgba(255,255,255,0.05)',
|
|
32
|
+
borderBottom: '1px solid rgba(255,255,255,0.10)',
|
|
33
|
+
textTransform: 'uppercase',
|
|
34
|
+
letterSpacing: '0.08em',
|
|
35
|
+
fontSize: 10,
|
|
36
|
+
color: 'rgba(255,255,255,0.7)',
|
|
37
|
+
userSelect: 'none',
|
|
38
|
+
WebkitUserSelect: 'none',
|
|
39
|
+
},
|
|
40
|
+
left: {
|
|
41
|
+
position: 'absolute',
|
|
42
|
+
top: 8,
|
|
43
|
+
left: 8,
|
|
44
|
+
zIndex: 20,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
7
47
|
const updateNode = (updater) => {
|
|
8
48
|
if (!prefabData || !setPrefabData || !selectedId)
|
|
9
49
|
return;
|
|
@@ -24,12 +64,112 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, transf
|
|
|
24
64
|
};
|
|
25
65
|
const selectedNode = selectedId && prefabData ? findNode(prefabData.root, selectedId) : null;
|
|
26
66
|
// if (!selectedNode) return null;
|
|
27
|
-
return _jsxs(_Fragment, { children: [_jsxs("div", { style:
|
|
67
|
+
return _jsxs(_Fragment, { children: [_jsxs("div", { style: ui.panel, children: [_jsxs("div", { style: ui.header, onClick: () => setIsInspectorCollapsed(!isInspectorCollapsed), onPointerEnter: (e) => {
|
|
68
|
+
e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
|
|
69
|
+
}, onPointerLeave: (e) => {
|
|
70
|
+
e.currentTarget.style.background = 'rgba(255,255,255,0.05)';
|
|
71
|
+
}, children: [_jsx("span", { children: "Inspector" }), _jsx("span", { style: { fontSize: 10, opacity: 0.8 }, children: isInspectorCollapsed ? '◀' : '▶' })] }), !isInspectorCollapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNode, deleteNode: deleteNode, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }))] }), _jsx("div", { style: ui.left, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId }) })] });
|
|
28
72
|
}
|
|
29
73
|
function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransformMode, basePath }) {
|
|
30
74
|
const ALL_COMPONENTS = getAllComponents();
|
|
31
75
|
const allComponentKeys = Object.keys(ALL_COMPONENTS);
|
|
32
76
|
const [addComponentType, setAddComponentType] = useState(allComponentKeys[0]);
|
|
77
|
+
const s = {
|
|
78
|
+
root: {
|
|
79
|
+
display: 'flex',
|
|
80
|
+
flexDirection: 'column',
|
|
81
|
+
gap: 6,
|
|
82
|
+
padding: 6,
|
|
83
|
+
maxHeight: '80vh',
|
|
84
|
+
overflowY: 'auto',
|
|
85
|
+
},
|
|
86
|
+
section: {
|
|
87
|
+
paddingBottom: 6,
|
|
88
|
+
borderBottom: '1px solid rgba(255,255,255,0.10)',
|
|
89
|
+
},
|
|
90
|
+
label: {
|
|
91
|
+
display: 'block',
|
|
92
|
+
fontSize: 10,
|
|
93
|
+
opacity: 0.7,
|
|
94
|
+
textTransform: 'uppercase',
|
|
95
|
+
letterSpacing: '0.08em',
|
|
96
|
+
marginBottom: 4,
|
|
97
|
+
},
|
|
98
|
+
input: {
|
|
99
|
+
width: '100%',
|
|
100
|
+
background: 'rgba(255,255,255,0.06)',
|
|
101
|
+
border: '1px solid rgba(255,255,255,0.14)',
|
|
102
|
+
borderRadius: 4,
|
|
103
|
+
padding: '4px 6px',
|
|
104
|
+
color: 'rgba(255,255,255,0.92)',
|
|
105
|
+
font: 'inherit',
|
|
106
|
+
outline: 'none',
|
|
107
|
+
},
|
|
108
|
+
row: {
|
|
109
|
+
display: 'flex',
|
|
110
|
+
alignItems: 'center',
|
|
111
|
+
justifyContent: 'space-between',
|
|
112
|
+
gap: 8,
|
|
113
|
+
},
|
|
114
|
+
button: {
|
|
115
|
+
padding: '2px 6px',
|
|
116
|
+
background: 'transparent',
|
|
117
|
+
color: 'rgba(255,255,255,0.9)',
|
|
118
|
+
border: '1px solid rgba(255,255,255,0.14)',
|
|
119
|
+
borderRadius: 4,
|
|
120
|
+
cursor: 'pointer',
|
|
121
|
+
font: 'inherit',
|
|
122
|
+
},
|
|
123
|
+
buttonActive: {
|
|
124
|
+
background: 'rgba(255,255,255,0.10)',
|
|
125
|
+
},
|
|
126
|
+
smallDanger: {
|
|
127
|
+
background: 'transparent',
|
|
128
|
+
border: 'none',
|
|
129
|
+
cursor: 'pointer',
|
|
130
|
+
color: 'rgba(255,120,120,0.95)',
|
|
131
|
+
font: 'inherit',
|
|
132
|
+
padding: '2px 4px',
|
|
133
|
+
},
|
|
134
|
+
componentHeader: {
|
|
135
|
+
display: 'flex',
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
justifyContent: 'space-between',
|
|
138
|
+
padding: '4px 0',
|
|
139
|
+
borderBottom: '1px solid rgba(255,255,255,0.08)',
|
|
140
|
+
marginBottom: 4,
|
|
141
|
+
},
|
|
142
|
+
componentTitle: {
|
|
143
|
+
fontSize: 10,
|
|
144
|
+
textTransform: 'uppercase',
|
|
145
|
+
letterSpacing: '0.08em',
|
|
146
|
+
opacity: 0.8,
|
|
147
|
+
},
|
|
148
|
+
select: {
|
|
149
|
+
flex: 1,
|
|
150
|
+
background: 'rgba(255,255,255,0.06)',
|
|
151
|
+
border: '1px solid rgba(255,255,255,0.14)',
|
|
152
|
+
borderRadius: 4,
|
|
153
|
+
padding: '4px 6px',
|
|
154
|
+
color: 'rgba(255,255,255,0.92)',
|
|
155
|
+
font: 'inherit',
|
|
156
|
+
outline: 'none',
|
|
157
|
+
},
|
|
158
|
+
addButton: {
|
|
159
|
+
width: 28,
|
|
160
|
+
padding: '4px 0',
|
|
161
|
+
background: 'rgba(255,255,255,0.08)',
|
|
162
|
+
color: 'rgba(255,255,255,0.92)',
|
|
163
|
+
border: '1px solid rgba(255,255,255,0.14)',
|
|
164
|
+
borderRadius: 4,
|
|
165
|
+
cursor: 'pointer',
|
|
166
|
+
font: 'inherit',
|
|
167
|
+
},
|
|
168
|
+
disabled: {
|
|
169
|
+
opacity: 0.35,
|
|
170
|
+
cursor: 'not-allowed',
|
|
171
|
+
},
|
|
172
|
+
};
|
|
33
173
|
const componentKeys = Object.keys(node.components || {}).join(',');
|
|
34
174
|
useEffect(() => {
|
|
35
175
|
// Components stored on nodes use lowercase keys (e.g. 'geometry'),
|
|
@@ -39,19 +179,25 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
39
179
|
setAddComponentType(available[0] || "");
|
|
40
180
|
}
|
|
41
181
|
}, [componentKeys, addComponentType, node.components, allComponentKeys]);
|
|
42
|
-
return _jsxs("div", {
|
|
182
|
+
return _jsxs("div", { style: s.root, children: [_jsx("div", { style: s.section, children: _jsx("input", { style: s.input, value: node.id, onChange: e => updateNode(n => (Object.assign(Object.assign({}, n), { id: e.target.value }))) }) }), _jsxs("div", { style: Object.assign(Object.assign(Object.assign({}, s.row), s.section), { paddingBottom: 6 }), children: [_jsx("label", { style: Object.assign(Object.assign({}, s.label), { marginBottom: 0 }), children: "Components" }), _jsx("button", { onClick: deleteNode, style: s.smallDanger, title: "Delete node", children: "\u2715" })] }), _jsxs("div", { style: s.section, children: [_jsx("label", { style: s.label, children: "Mode" }), _jsx("div", { style: { display: 'flex', gap: 6 }, children: ["translate", "rotate", "scale"].map(mode => (_jsx("button", { onClick: () => setTransformMode(mode), style: Object.assign(Object.assign(Object.assign({}, s.button), { flex: 1 }), (transformMode === mode ? s.buttonActive : null)), onPointerEnter: (e) => {
|
|
183
|
+
if (transformMode !== mode)
|
|
184
|
+
e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
|
|
185
|
+
}, onPointerLeave: (e) => {
|
|
186
|
+
if (transformMode !== mode)
|
|
187
|
+
e.currentTarget.style.background = 'transparent';
|
|
188
|
+
}, children: mode[0].toUpperCase() }, mode))) })] }), node.components && Object.entries(node.components).map(([key, comp]) => {
|
|
43
189
|
if (!comp)
|
|
44
190
|
return null;
|
|
45
191
|
const componentDef = ALL_COMPONENTS[comp.type];
|
|
46
192
|
if (!componentDef)
|
|
47
|
-
return _jsxs("div", {
|
|
193
|
+
return _jsxs("div", { style: { padding: '4px 0', color: 'rgba(255,120,120,0.95)', fontSize: 11 }, children: ["Unknown component type: ", comp.type, _jsx("textarea", { defaultValue: JSON.stringify(comp) })] }, key);
|
|
48
194
|
const EditorComp = componentDef.Editor;
|
|
49
|
-
return (_jsxs("div", {
|
|
195
|
+
return (_jsxs("div", { style: { padding: '0 2px' }, children: [_jsxs("div", { style: s.componentHeader, children: [_jsx("span", { style: s.componentTitle, children: key }), _jsx("button", { onClick: () => updateNode(n => {
|
|
50
196
|
const components = Object.assign({}, n.components);
|
|
51
197
|
delete components[key];
|
|
52
198
|
return Object.assign(Object.assign({}, n), { components });
|
|
53
|
-
}),
|
|
54
|
-
}), _jsxs("div", {
|
|
199
|
+
}), style: s.smallDanger, title: "Remove component", children: "\u2715" })] }), EditorComp ? (_jsx(EditorComp, { component: comp, onUpdate: (newProps) => updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: Object.assign(Object.assign({}, comp), { properties: Object.assign(Object.assign({}, comp.properties), newProps) }) }) }))), basePath: basePath })) : null] }, key));
|
|
200
|
+
}), _jsxs("div", { style: Object.assign(Object.assign({}, s.section), { borderBottom: 'none', paddingBottom: 0 }), children: [_jsx("label", { style: s.label, children: "Add Component" }), _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx("select", { style: s.select, value: addComponentType, onChange: e => setAddComponentType(e.target.value), children: allComponentKeys.filter(k => { var _a; return !((_a = node.components) === null || _a === void 0 ? void 0 : _a[k.toLowerCase()]); }).map(k => (_jsx("option", { value: k, children: k }, k))) }), _jsx("button", { style: Object.assign(Object.assign({}, s.addButton), (!addComponentType ? s.disabled : null)), disabled: !addComponentType, onClick: () => {
|
|
55
201
|
var _a;
|
|
56
202
|
if (!addComponentType)
|
|
57
203
|
return;
|
|
@@ -60,6 +206,14 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
60
206
|
const key = addComponentType.toLowerCase();
|
|
61
207
|
updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: { type: def.name, properties: def.defaultProperties } }) })));
|
|
62
208
|
}
|
|
209
|
+
}, onPointerEnter: (e) => {
|
|
210
|
+
if (!addComponentType)
|
|
211
|
+
return;
|
|
212
|
+
e.currentTarget.style.background = 'rgba(255,255,255,0.12)';
|
|
213
|
+
}, onPointerLeave: (e) => {
|
|
214
|
+
if (!addComponentType)
|
|
215
|
+
return;
|
|
216
|
+
e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
|
|
63
217
|
}, children: "+" })] })] })] });
|
|
64
218
|
}
|
|
65
219
|
function findNode(root, id) {
|
|
@@ -21,8 +21,6 @@ const PrefabEditor = ({ basePath, initialPrefab, onPrefabChange, children }) =>
|
|
|
21
21
|
"name": "New Prefab",
|
|
22
22
|
"root": {
|
|
23
23
|
"id": "root",
|
|
24
|
-
"enabled": true,
|
|
25
|
-
"visible": true,
|
|
26
24
|
"components": {
|
|
27
25
|
"transform": {
|
|
28
26
|
"type": "Transform",
|
|
@@ -52,11 +50,65 @@ const PrefabEditor = ({ basePath, initialPrefab, onPrefabChange, children }) =>
|
|
|
52
50
|
};
|
|
53
51
|
return _jsxs(_Fragment, { children: [_jsx(GameCanvas, { children: _jsxs(Physics, { paused: editMode, children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { data: loadedPrefab, ref: prefabRef,
|
|
54
52
|
// props for edit mode
|
|
55
|
-
editMode: editMode, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }), children] }) }), _jsxs("div", { style: {
|
|
53
|
+
editMode: editMode, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath }), children] }) }), _jsxs("div", { style: {
|
|
54
|
+
position: "absolute",
|
|
55
|
+
top: 8,
|
|
56
|
+
left: "50%",
|
|
57
|
+
transform: "translateX(-50%)",
|
|
58
|
+
display: "flex",
|
|
59
|
+
alignItems: "center",
|
|
60
|
+
gap: 6,
|
|
61
|
+
padding: "2px 4px",
|
|
62
|
+
background: "rgba(0,0,0,0.55)",
|
|
63
|
+
border: "1px solid rgba(255,255,255,0.12)",
|
|
64
|
+
borderRadius: 4,
|
|
65
|
+
color: "rgba(255,255,255,0.9)",
|
|
66
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
67
|
+
fontSize: 11,
|
|
68
|
+
lineHeight: 1,
|
|
69
|
+
WebkitUserSelect: "none",
|
|
70
|
+
userSelect: "none",
|
|
71
|
+
}, children: [_jsx("button", { style: {
|
|
72
|
+
padding: "2px 6px",
|
|
73
|
+
font: "inherit",
|
|
74
|
+
background: "transparent",
|
|
75
|
+
color: "inherit",
|
|
76
|
+
border: "1px solid rgba(255,255,255,0.18)",
|
|
77
|
+
borderRadius: 3,
|
|
78
|
+
cursor: "pointer",
|
|
79
|
+
}, onClick: () => setEditMode(!editMode), onPointerEnter: (e) => {
|
|
80
|
+
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
81
|
+
}, onPointerLeave: (e) => {
|
|
82
|
+
e.currentTarget.style.background = "transparent";
|
|
83
|
+
}, children: editMode ? "▶" : "⏸" }), _jsx("span", { style: { opacity: 0.35 }, children: "|" }), _jsx("button", { style: {
|
|
84
|
+
padding: "2px 6px",
|
|
85
|
+
font: "inherit",
|
|
86
|
+
background: "transparent",
|
|
87
|
+
color: "inherit",
|
|
88
|
+
border: "1px solid rgba(255,255,255,0.18)",
|
|
89
|
+
borderRadius: 3,
|
|
90
|
+
cursor: "pointer",
|
|
91
|
+
}, onClick: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
56
92
|
const prefab = yield loadJson();
|
|
57
93
|
if (prefab)
|
|
58
94
|
setLoadedPrefab(prefab);
|
|
59
|
-
}),
|
|
95
|
+
}), onPointerEnter: (e) => {
|
|
96
|
+
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
97
|
+
}, onPointerLeave: (e) => {
|
|
98
|
+
e.currentTarget.style.background = "transparent";
|
|
99
|
+
}, children: "\uD83D\uDCE5" }), _jsx("button", { style: {
|
|
100
|
+
padding: "2px 6px",
|
|
101
|
+
font: "inherit",
|
|
102
|
+
background: "transparent",
|
|
103
|
+
color: "inherit",
|
|
104
|
+
border: "1px solid rgba(255,255,255,0.18)",
|
|
105
|
+
borderRadius: 3,
|
|
106
|
+
cursor: "pointer",
|
|
107
|
+
}, onClick: () => saveJson(loadedPrefab, "prefab"), onPointerEnter: (e) => {
|
|
108
|
+
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
109
|
+
}, onPointerLeave: (e) => {
|
|
110
|
+
e.currentTarget.style.background = "transparent";
|
|
111
|
+
}, children: "\uD83D\uDCBE" })] }), editMode && _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, transformMode: transformMode, setTransformMode: setTransformMode, basePath: basePath })] });
|
|
60
112
|
};
|
|
61
113
|
const saveJson = (data, filename) => {
|
|
62
114
|
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data, null, 2));
|
|
@@ -49,13 +49,6 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
|
|
|
49
49
|
setSelectedObject(null);
|
|
50
50
|
}
|
|
51
51
|
}, [selectedId]);
|
|
52
|
-
// const [transformMode, setTransformMode] = useState<"translate" | "rotate" | "scale">("translate"); // Removed local state
|
|
53
|
-
const updateNode = (updater) => {
|
|
54
|
-
if (!selectedId || !onPrefabChange)
|
|
55
|
-
return;
|
|
56
|
-
const newRoot = updatePrefabNode(data.root, selectedId, updater);
|
|
57
|
-
onPrefabChange(Object.assign(Object.assign({}, data), { root: newRoot }));
|
|
58
|
-
};
|
|
59
52
|
const onTransformChange = () => {
|
|
60
53
|
if (!selectedId || !onPrefabChange)
|
|
61
54
|
return;
|
|
@@ -158,7 +151,7 @@ function GameObjectRenderer({ gameObject, selectedId, onSelect, registerRef, loa
|
|
|
158
151
|
}
|
|
159
152
|
clickValid.current = false;
|
|
160
153
|
};
|
|
161
|
-
if (
|
|
154
|
+
if (gameObject.disabled === true || gameObject.hidden === true)
|
|
162
155
|
return null;
|
|
163
156
|
// --- 2. If instanced, short-circuit to a tiny clean branch ---
|
|
164
157
|
const isInstanced = !!((_c = (_b = (_a = gameObject.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);
|
|
@@ -170,8 +163,6 @@ function GameObjectRenderer({ gameObject, selectedId, onSelect, registerRef, loa
|
|
|
170
163
|
// --- 5. Render children (always relative transforms) ---
|
|
171
164
|
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)));
|
|
172
165
|
// --- 4. Wrap with physics if needed ---
|
|
173
|
-
// Only wrap the core content (geometry/model), not children
|
|
174
|
-
// Children should be siblings, not inside the physics body
|
|
175
166
|
const physicsWrapped = wrapPhysicsIfNeeded(gameObject, core, ctx);
|
|
176
167
|
// --- 6. Final group wrapper ---
|
|
177
168
|
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] }));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface Prefab {
|
|
2
|
-
id
|
|
3
|
-
name
|
|
2
|
+
id?: string;
|
|
3
|
+
name?: string;
|
|
4
4
|
description?: string;
|
|
5
5
|
author?: string;
|
|
6
6
|
version?: string;
|
|
@@ -12,8 +12,8 @@ export interface Prefab {
|
|
|
12
12
|
}
|
|
13
13
|
export interface GameObject {
|
|
14
14
|
id: string;
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
hidden?: boolean;
|
|
17
17
|
ref?: any;
|
|
18
18
|
children?: GameObject[];
|
|
19
19
|
components?: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-three-game",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "Batteries included React Three Fiber game engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -15,8 +15,11 @@
|
|
|
15
15
|
"author": "prnth",
|
|
16
16
|
"license": "VPL",
|
|
17
17
|
"type": "module",
|
|
18
|
+
"workspaces": ["docs"],
|
|
18
19
|
"peerDependencies": {
|
|
19
20
|
"@react-three/fiber": "^9.0.0",
|
|
21
|
+
"@react-three/drei": "^10.0.0",
|
|
22
|
+
"@react-three/rapier": "^2.0.0",
|
|
20
23
|
"react": "^18.0.0 || ^19.0.0",
|
|
21
24
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
22
25
|
"three": "^0.181.0"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { GameObject } from "../tools/prefabeditor/types";
|
|
2
|
+
|
|
3
|
+
export type Vec3 = [number, number, number];
|
|
4
|
+
|
|
5
|
+
export interface GroundOptions {
|
|
6
|
+
/** GameObject id. Defaults to "ground". */
|
|
7
|
+
id?: string;
|
|
8
|
+
|
|
9
|
+
/** Plane size. Defaults to 50. */
|
|
10
|
+
size?: number;
|
|
11
|
+
|
|
12
|
+
/** Transform overrides. */
|
|
13
|
+
position?: Vec3;
|
|
14
|
+
rotation?: Vec3;
|
|
15
|
+
scale?: Vec3;
|
|
16
|
+
|
|
17
|
+
/** Material overrides. */
|
|
18
|
+
color?: string;
|
|
19
|
+
texture?: string;
|
|
20
|
+
/** When true, set repeat wrapping. Defaults to true if texture is provided. */
|
|
21
|
+
repeat?: boolean;
|
|
22
|
+
/** Texture repeat counts when repeat=true. Defaults to [25,25]. */
|
|
23
|
+
repeatCount?: [number, number];
|
|
24
|
+
|
|
25
|
+
/** Physics body type. Defaults to "fixed". */
|
|
26
|
+
physicsType?: "fixed" | "dynamic" | "kinematic";
|
|
27
|
+
|
|
28
|
+
/** Set true to hide the node. */
|
|
29
|
+
hidden?: boolean;
|
|
30
|
+
/** Set true to disable the node. */
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a ready-to-use plane ground GameObject.
|
|
36
|
+
*
|
|
37
|
+
* Designed to reduce prefab boilerplate:
|
|
38
|
+
* - Transform (rotated to lie flat)
|
|
39
|
+
* - Geometry (plane)
|
|
40
|
+
* - Material (optional texture + repeat)
|
|
41
|
+
* - Physics (fixed by default)
|
|
42
|
+
*/
|
|
43
|
+
export function ground(options: GroundOptions = {}): GameObject {
|
|
44
|
+
const {
|
|
45
|
+
id = "ground",
|
|
46
|
+
size = 50,
|
|
47
|
+
position = [0, 0, 0],
|
|
48
|
+
rotation = [-Math.PI / 2, 0, 0],
|
|
49
|
+
scale = [1, 1, 1],
|
|
50
|
+
color = "white",
|
|
51
|
+
texture,
|
|
52
|
+
repeat = texture ? true : false,
|
|
53
|
+
repeatCount = [25, 25],
|
|
54
|
+
physicsType = "fixed",
|
|
55
|
+
hidden = false,
|
|
56
|
+
disabled = false,
|
|
57
|
+
} = options;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
id,
|
|
61
|
+
disabled,
|
|
62
|
+
hidden,
|
|
63
|
+
components: {
|
|
64
|
+
transform: {
|
|
65
|
+
type: "Transform",
|
|
66
|
+
properties: {
|
|
67
|
+
position,
|
|
68
|
+
rotation,
|
|
69
|
+
scale,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
geometry: {
|
|
73
|
+
type: "Geometry",
|
|
74
|
+
properties: {
|
|
75
|
+
geometryType: "plane",
|
|
76
|
+
args: [size, size],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
material: {
|
|
80
|
+
type: "Material",
|
|
81
|
+
properties: {
|
|
82
|
+
color,
|
|
83
|
+
...(texture ? { texture } : {}),
|
|
84
|
+
...(repeat ? { repeat: true, repeatCount } : {}),
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
physics: {
|
|
88
|
+
type: "Physics",
|
|
89
|
+
properties: {
|
|
90
|
+
type: physicsType,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import { Canvas, extend } from "@react-three/fiber";
|
|
2
4
|
import { WebGPURenderer, MeshBasicNodeMaterial, MeshStandardNodeMaterial, SpriteNodeMaterial, PCFShadowMap } from "three/webgpu";
|
|
3
5
|
import { Suspense, useState } from "react";
|
|
@@ -16,10 +18,10 @@ extend({
|
|
|
16
18
|
|
|
17
19
|
export default function GameCanvas({ loader = false, children, ...props }: { loader?: boolean, children: React.ReactNode, props?: WebGPURendererParameters }) {
|
|
18
20
|
const [frameloop, setFrameloop] = useState<"never" | "always">("never");
|
|
19
|
-
const [loading, setLoading] = useState(true);
|
|
20
21
|
|
|
21
22
|
return <>
|
|
22
23
|
<Canvas
|
|
24
|
+
style={{ touchAction: 'none' }}
|
|
23
25
|
shadows={{ type: PCFShadowMap, }}
|
|
24
26
|
frameloop={frameloop}
|
|
25
27
|
gl={async ({ canvas }) => {
|
|
@@ -42,7 +44,8 @@ export default function GameCanvas({ loader = false, children, ...props }: { loa
|
|
|
42
44
|
<Suspense>
|
|
43
45
|
{children}
|
|
44
46
|
</Suspense>
|
|
47
|
+
|
|
48
|
+
{loader ? <Loader /> : null}
|
|
45
49
|
</Canvas>
|
|
46
|
-
{loader ? <Loader /> : null}
|
|
47
50
|
</>;
|
|
48
51
|
}
|