react-three-game 0.0.1 → 0.0.2
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/.gitattributes +2 -0
- package/.github/copilot-instructions.md +207 -0
- package/LICENSE +661 -0
- package/README.md +664 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/shared/GameCanvas.d.ts +6 -0
- package/dist/shared/GameCanvas.js +48 -0
- package/dist/shared/extend-three.d.ts +1 -0
- package/dist/shared/extend-three.js +13 -0
- package/dist/tools/assetviewer/page.d.ts +21 -0
- package/dist/tools/assetviewer/page.js +153 -0
- package/dist/tools/dragdrop/DragDropLoader.d.ts +9 -0
- package/dist/tools/dragdrop/DragDropLoader.js +78 -0
- package/dist/tools/dragdrop/modelLoader.d.ts +7 -0
- package/dist/tools/dragdrop/modelLoader.js +53 -0
- package/dist/tools/dragdrop/page.d.ts +1 -0
- package/dist/tools/dragdrop/page.js +11 -0
- package/dist/tools/prefabeditor/EditorTree.d.ts +10 -0
- package/dist/tools/prefabeditor/EditorTree.js +182 -0
- package/dist/tools/prefabeditor/EditorUI.d.ts +11 -0
- package/dist/tools/prefabeditor/EditorUI.js +96 -0
- package/dist/tools/prefabeditor/EventSystem.d.ts +7 -0
- package/dist/tools/prefabeditor/EventSystem.js +23 -0
- package/dist/tools/prefabeditor/InstanceProvider.d.ts +30 -0
- package/dist/tools/prefabeditor/InstanceProvider.js +172 -0
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +4 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +89 -0
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +12 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +273 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +13 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.js +13 -0
- package/dist/tools/prefabeditor/components/GeometryComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/GeometryComponent.js +28 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +66 -0
- package/dist/tools/prefabeditor/components/ModelComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/ModelComponent.js +39 -0
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +19 -0
- package/dist/tools/prefabeditor/components/SpotLightComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +19 -0
- package/dist/tools/prefabeditor/components/TransformComponent.d.ts +8 -0
- package/dist/tools/prefabeditor/components/TransformComponent.js +22 -0
- package/dist/tools/prefabeditor/components/index.d.ts +2 -0
- package/dist/tools/prefabeditor/components/index.js +14 -0
- package/dist/tools/prefabeditor/page.d.ts +1 -0
- package/dist/tools/prefabeditor/page.js +5 -0
- package/dist/tools/prefabeditor/types.d.ts +29 -0
- package/dist/tools/prefabeditor/types.js +1 -0
- package/package.json +16 -4
- package/tsconfig.json +2 -1
- package/dist/GameCanvas.d.ts +0 -6
- package/dist/GameCanvas.js +0 -5
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
12
|
+
import { MapControls, TransformControls } from "@react-three/drei";
|
|
13
|
+
import { useState, useRef, useEffect, forwardRef, useCallback } from "react";
|
|
14
|
+
import { Vector3, Euler, Quaternion, SRGBColorSpace, TextureLoader, Matrix4 } from "three";
|
|
15
|
+
import { getComponent } from "./components/ComponentRegistry";
|
|
16
|
+
import { loadModel } from "../dragdrop/modelLoader";
|
|
17
|
+
import { GameInstance, GameInstanceProvider } from "./InstanceProvider";
|
|
18
|
+
// register all components
|
|
19
|
+
import { registerComponent } from './components/ComponentRegistry';
|
|
20
|
+
import components from './components/';
|
|
21
|
+
components.forEach(registerComponent);
|
|
22
|
+
function updatePrefabNode(root, id, update) {
|
|
23
|
+
if (root.id === id) {
|
|
24
|
+
return update(root);
|
|
25
|
+
}
|
|
26
|
+
if (root.children) {
|
|
27
|
+
return Object.assign(Object.assign({}, root), { children: root.children.map(child => updatePrefabNode(child, id, update)) });
|
|
28
|
+
}
|
|
29
|
+
return root;
|
|
30
|
+
}
|
|
31
|
+
export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, transformMode, setTransformMode }, ref) => {
|
|
32
|
+
const [loadedModels, setLoadedModels] = useState({});
|
|
33
|
+
const [loadedTextures, setLoadedTextures] = useState({});
|
|
34
|
+
// const [prefabRoot, setPrefabRoot] = useState<Prefab>(data); // Removed local state
|
|
35
|
+
const loadingRefs = useRef(new Set());
|
|
36
|
+
const objectRefs = useRef({});
|
|
37
|
+
const [selectedObject, setSelectedObject] = useState(null);
|
|
38
|
+
const registerRef = useCallback((id, obj) => {
|
|
39
|
+
objectRefs.current[id] = obj;
|
|
40
|
+
if (id === selectedId) {
|
|
41
|
+
setSelectedObject(obj);
|
|
42
|
+
}
|
|
43
|
+
}, [selectedId]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (selectedId) {
|
|
46
|
+
setSelectedObject(objectRefs.current[selectedId] || null);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
setSelectedObject(null);
|
|
50
|
+
}
|
|
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
|
+
const onTransformChange = () => {
|
|
60
|
+
if (!selectedId || !onPrefabChange)
|
|
61
|
+
return;
|
|
62
|
+
const obj = objectRefs.current[selectedId];
|
|
63
|
+
if (!obj)
|
|
64
|
+
return;
|
|
65
|
+
// 1. Get world matrix from the actual Three object
|
|
66
|
+
const worldMatrix = obj.matrixWorld.clone();
|
|
67
|
+
// 2. Compute parent world matrix from the prefab tree
|
|
68
|
+
const parentWorld = computeParentWorldMatrix(data.root, selectedId);
|
|
69
|
+
const parentInv = parentWorld.clone().invert();
|
|
70
|
+
// 3. Convert world -> local
|
|
71
|
+
const localMatrix = new Matrix4().multiplyMatrices(parentInv, worldMatrix);
|
|
72
|
+
const lp = new Vector3();
|
|
73
|
+
const lq = new Quaternion();
|
|
74
|
+
const ls = new Vector3();
|
|
75
|
+
localMatrix.decompose(lp, lq, ls);
|
|
76
|
+
const le = new Euler().setFromQuaternion(lq);
|
|
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
|
+
});
|
|
89
|
+
onPrefabChange(Object.assign(Object.assign({}, data), { root: newRoot }));
|
|
90
|
+
};
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
const loadAssets = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
|
+
const modelsToLoad = new Set();
|
|
94
|
+
const texturesToLoad = new Set();
|
|
95
|
+
const traverse = (node) => {
|
|
96
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
97
|
+
if (!node)
|
|
98
|
+
return;
|
|
99
|
+
if ((_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.filename) {
|
|
100
|
+
modelsToLoad.add(node.components.model.properties.filename);
|
|
101
|
+
}
|
|
102
|
+
if ((_f = (_e = (_d = node.components) === null || _d === void 0 ? void 0 : _d.material) === null || _e === void 0 ? void 0 : _e.properties) === null || _f === void 0 ? void 0 : _f.texture) {
|
|
103
|
+
texturesToLoad.add(node.components.material.properties.texture);
|
|
104
|
+
}
|
|
105
|
+
(_g = node.children) === null || _g === void 0 ? void 0 : _g.forEach(traverse);
|
|
106
|
+
};
|
|
107
|
+
traverse(data.root);
|
|
108
|
+
for (const filename of modelsToLoad) {
|
|
109
|
+
if (!loadedModels[filename] && !loadingRefs.current.has(filename)) {
|
|
110
|
+
loadingRefs.current.add(filename);
|
|
111
|
+
const result = yield loadModel(filename, "");
|
|
112
|
+
if (result.success && result.model) {
|
|
113
|
+
setLoadedModels(prev => (Object.assign(Object.assign({}, prev), { [filename]: result.model })));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const textureLoader = new TextureLoader();
|
|
118
|
+
for (const filename of texturesToLoad) {
|
|
119
|
+
if (!loadedTextures[filename] && !loadingRefs.current.has(filename)) {
|
|
120
|
+
loadingRefs.current.add(filename);
|
|
121
|
+
textureLoader.load(filename, (texture) => {
|
|
122
|
+
texture.colorSpace = SRGBColorSpace;
|
|
123
|
+
setLoadedTextures(prev => (Object.assign(Object.assign({}, prev), { [filename]: texture })));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
loadAssets();
|
|
129
|
+
}, [data, loadedModels, loadedTextures]);
|
|
130
|
+
return _jsxs("group", { ref: ref, children: [_jsx(GameInstanceProvider, { models: loadedModels, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(GameObjectRenderer, { gameObject: data.root, selectedId: selectedId, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: new Matrix4() }) }), editMode && _jsxs(_Fragment, { children: [_jsx(MapControls, { makeDefault: true }), selectedId && selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: onTransformChange }))] })] });
|
|
131
|
+
});
|
|
132
|
+
function GameObjectRenderer({ gameObject, selectedId, onSelect, registerRef, loadedModels, loadedTextures, editMode, parentMatrix = new Matrix4(), }) {
|
|
133
|
+
var _a, _b, _c, _d;
|
|
134
|
+
// Early return if gameObject is null or undefined
|
|
135
|
+
if (!gameObject)
|
|
136
|
+
return null;
|
|
137
|
+
// Build a small context object to avoid long param lists
|
|
138
|
+
const ctx = { gameObject, selectedId, onSelect, registerRef, loadedModels, loadedTextures, editMode };
|
|
139
|
+
// --- 1. Transform (local + world) ---
|
|
140
|
+
const transformProps = getNodeTransformProps(gameObject);
|
|
141
|
+
const localMatrix = new Matrix4().compose(new Vector3(...transformProps.position), new Quaternion().setFromEuler(new Euler(...transformProps.rotation)), new Vector3(...transformProps.scale));
|
|
142
|
+
const worldMatrix = parentMatrix.clone().multiply(localMatrix);
|
|
143
|
+
// preserve click/drag detection from previous implementation
|
|
144
|
+
const clickValid = useRef(false);
|
|
145
|
+
const handlePointerDown = (e) => {
|
|
146
|
+
e.stopPropagation();
|
|
147
|
+
clickValid.current = true;
|
|
148
|
+
};
|
|
149
|
+
const handlePointerMove = () => {
|
|
150
|
+
if (clickValid.current)
|
|
151
|
+
clickValid.current = false;
|
|
152
|
+
};
|
|
153
|
+
const handlePointerUp = (e) => {
|
|
154
|
+
if (clickValid.current) {
|
|
155
|
+
e.stopPropagation();
|
|
156
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(gameObject.id);
|
|
157
|
+
}
|
|
158
|
+
clickValid.current = false;
|
|
159
|
+
};
|
|
160
|
+
if (!gameObject.enabled || !gameObject.visible)
|
|
161
|
+
return null;
|
|
162
|
+
// --- 2. If instanced, short-circuit to a tiny clean branch ---
|
|
163
|
+
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);
|
|
164
|
+
if (isInstanced) {
|
|
165
|
+
return renderInstancedNode(gameObject, worldMatrix, ctx);
|
|
166
|
+
}
|
|
167
|
+
// --- 3. Core content decided by component registry ---
|
|
168
|
+
const core = renderCoreNode(gameObject, ctx, parentMatrix);
|
|
169
|
+
// --- 4. Wrap with physics if needed ---
|
|
170
|
+
const physicsWrapped = wrapPhysicsIfNeeded(gameObject, core, ctx);
|
|
171
|
+
// --- 5. Render children (always relative transforms) ---
|
|
172
|
+
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)));
|
|
173
|
+
// --- 6. Final group wrapper ---
|
|
174
|
+
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] }));
|
|
175
|
+
}
|
|
176
|
+
// Helper: render an instanced GameInstance (terminal node)
|
|
177
|
+
function renderInstancedNode(gameObject, worldMatrix, ctx) {
|
|
178
|
+
var _a, _b, _c, _d;
|
|
179
|
+
const physics = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.physics;
|
|
180
|
+
const wp = new Vector3();
|
|
181
|
+
const wq = new Quaternion();
|
|
182
|
+
const ws = new Vector3();
|
|
183
|
+
worldMatrix.decompose(wp, wq, ws);
|
|
184
|
+
const we = new Euler().setFromQuaternion(wq);
|
|
185
|
+
const modelUrl = (_d = (_c = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.properties) === null || _d === void 0 ? void 0 : _d.filename;
|
|
186
|
+
return (_jsx(GameInstance, { id: gameObject.id, modelUrl: modelUrl, position: [wp.x, wp.y, wp.z], rotation: [we.x, we.y, we.z], scale: [ws.x, ws.y, ws.z], physics: ctx.editMode ? undefined : physics === null || physics === void 0 ? void 0 : physics.properties }));
|
|
187
|
+
}
|
|
188
|
+
// Helper: render main model/geometry content for a non-instanced node
|
|
189
|
+
function renderCoreNode(gameObject, ctx, parentMatrix) {
|
|
190
|
+
var _a, _b, _c;
|
|
191
|
+
const geometry = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.geometry;
|
|
192
|
+
const material = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.material;
|
|
193
|
+
const modelComp = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model;
|
|
194
|
+
const geometryDef = geometry ? getComponent('Geometry') : undefined;
|
|
195
|
+
const materialDef = material ? getComponent('Material') : undefined;
|
|
196
|
+
const isModelAvailable = !!(modelComp && modelComp.properties && modelComp.properties.filename && ctx.loadedModels[modelComp.properties.filename]);
|
|
197
|
+
// Generic component views (exclude geometry/material/model)
|
|
198
|
+
const contextProps = {
|
|
199
|
+
loadedModels: ctx.loadedModels,
|
|
200
|
+
loadedTextures: ctx.loadedTextures,
|
|
201
|
+
isSelected: ctx.selectedId === gameObject.id,
|
|
202
|
+
editMode: ctx.editMode,
|
|
203
|
+
parentMatrix,
|
|
204
|
+
registerRef: ctx.registerRef,
|
|
205
|
+
};
|
|
206
|
+
const allComponentViews = gameObject.components
|
|
207
|
+
? Object.entries(gameObject.components)
|
|
208
|
+
.filter(([key]) => key !== 'geometry' && key !== 'material' && key !== 'model')
|
|
209
|
+
.map(([key, comp]) => {
|
|
210
|
+
const def = getComponent(key);
|
|
211
|
+
if (!def || !def.View || !comp)
|
|
212
|
+
return null;
|
|
213
|
+
return _jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key);
|
|
214
|
+
})
|
|
215
|
+
: null;
|
|
216
|
+
// If we have a model (non-instanced) render it as a primitive with material override
|
|
217
|
+
if (isModelAvailable) {
|
|
218
|
+
const modelObj = ctx.loadedModels[modelComp.properties.filename].clone();
|
|
219
|
+
return (_jsxs("primitive", { object: modelObj, children: [material && materialDef && materialDef.View && (_jsx(materialDef.View, { properties: material.properties, loadedTextures: ctx.loadedTextures, isSelected: ctx.selectedId === gameObject.id, editMode: ctx.editMode, parentMatrix: parentMatrix, registerRef: ctx.registerRef }, "material")), allComponentViews] }));
|
|
220
|
+
}
|
|
221
|
+
// Otherwise, if geometry present, render a mesh
|
|
222
|
+
if (geometry && geometryDef && geometryDef.View) {
|
|
223
|
+
return (_jsxs("mesh", { children: [_jsx(geometryDef.View, Object.assign({ properties: geometry.properties }, contextProps), "geometry"), material && materialDef && materialDef.View && (_jsx(materialDef.View, { properties: material.properties, loadedTextures: ctx.loadedTextures, isSelected: ctx.selectedId === gameObject.id, editMode: ctx.editMode, parentMatrix: parentMatrix, registerRef: ctx.registerRef }, "material")), allComponentViews] }));
|
|
224
|
+
}
|
|
225
|
+
// Default: render other component views (no geometry/model)
|
|
226
|
+
return _jsx(_Fragment, { children: allComponentViews });
|
|
227
|
+
}
|
|
228
|
+
// Helper: wrap core content with physics component when necessary
|
|
229
|
+
function wrapPhysicsIfNeeded(gameObject, content, ctx) {
|
|
230
|
+
var _a;
|
|
231
|
+
const physics = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.physics;
|
|
232
|
+
if (!physics)
|
|
233
|
+
return content;
|
|
234
|
+
const physicsDef = getComponent('Physics');
|
|
235
|
+
if (!physicsDef || !physicsDef.View)
|
|
236
|
+
return content;
|
|
237
|
+
return (_jsx(physicsDef.View, { properties: Object.assign(Object.assign({}, physics.properties), { id: gameObject.id }), registerRef: ctx.registerRef, editMode: ctx.editMode, children: content }));
|
|
238
|
+
}
|
|
239
|
+
export default PrefabRoot;
|
|
240
|
+
function getNodeTransformProps(node) {
|
|
241
|
+
var _a, _b, _c, _d, _e;
|
|
242
|
+
const t = (_b = (_a = node === null || node === void 0 ? void 0 : node.components) === null || _a === void 0 ? void 0 : _a.transform) === null || _b === void 0 ? void 0 : _b.properties;
|
|
243
|
+
return {
|
|
244
|
+
position: (_c = t === null || t === void 0 ? void 0 : t.position) !== null && _c !== void 0 ? _c : [0, 0, 0],
|
|
245
|
+
rotation: (_d = t === null || t === void 0 ? void 0 : t.rotation) !== null && _d !== void 0 ? _d : [0, 0, 0],
|
|
246
|
+
scale: (_e = t === null || t === void 0 ? void 0 : t.scale) !== null && _e !== void 0 ? _e : [1, 1, 1],
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function computeParentWorldMatrix(root, targetId) {
|
|
250
|
+
var _a;
|
|
251
|
+
const identity = new Matrix4();
|
|
252
|
+
function traverse(node, parentWorld) {
|
|
253
|
+
if (node.id === targetId) {
|
|
254
|
+
// parentWorld is what we want
|
|
255
|
+
return parentWorld.clone();
|
|
256
|
+
}
|
|
257
|
+
const { position, rotation, scale } = getNodeTransformProps(node);
|
|
258
|
+
const localPos = new Vector3(...position);
|
|
259
|
+
const localRot = new Euler(...rotation);
|
|
260
|
+
const localScale = new Vector3(...scale);
|
|
261
|
+
const localMat = new Matrix4().compose(localPos, new Quaternion().setFromEuler(localRot), localScale);
|
|
262
|
+
const worldMat = parentWorld.clone().multiply(localMat);
|
|
263
|
+
if (node.children) {
|
|
264
|
+
for (const child of node.children) {
|
|
265
|
+
const res = traverse(child, worldMat);
|
|
266
|
+
if (res)
|
|
267
|
+
return res;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
return (_a = traverse(root, identity)) !== null && _a !== void 0 ? _a : identity;
|
|
273
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
export interface Component {
|
|
3
|
+
name: string;
|
|
4
|
+
Editor: FC<{
|
|
5
|
+
component: any;
|
|
6
|
+
onUpdate: (newComp: any) => void;
|
|
7
|
+
}>;
|
|
8
|
+
defaultProperties: any;
|
|
9
|
+
View?: FC<any>;
|
|
10
|
+
}
|
|
11
|
+
export declare function registerComponent(component: Component): void;
|
|
12
|
+
export declare function getComponent(name: string): Component | undefined;
|
|
13
|
+
export declare function getAllComponents(): Record<string, Component>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const REGISTRY = {};
|
|
2
|
+
export function registerComponent(component) {
|
|
3
|
+
if (REGISTRY[component.name]) {
|
|
4
|
+
throw new Error(`Component with name ${component.name} already registered.`);
|
|
5
|
+
}
|
|
6
|
+
REGISTRY[component.name] = component;
|
|
7
|
+
}
|
|
8
|
+
export function getComponent(name) {
|
|
9
|
+
return REGISTRY[name];
|
|
10
|
+
}
|
|
11
|
+
export function getAllComponents() {
|
|
12
|
+
return Object.assign({}, REGISTRY);
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
function GeometryComponentEditor({ component, onUpdate }) {
|
|
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.geometryType, onChange: e => onUpdate({ geometryType: e.target.value }), children: [_jsx("option", { value: "box", children: "Box" }), _jsx("option", { value: "sphere", children: "Sphere" }), _jsx("option", { value: "plane", children: "Plane" })] })] });
|
|
4
|
+
}
|
|
5
|
+
// View for Geometry component
|
|
6
|
+
function GeometryComponentView({ properties, children }) {
|
|
7
|
+
const { geometryType, args = [] } = properties;
|
|
8
|
+
// Only return the geometry node, do not wrap in mesh or group
|
|
9
|
+
switch (geometryType) {
|
|
10
|
+
case "box":
|
|
11
|
+
return _jsx("boxGeometry", { args: args });
|
|
12
|
+
case "sphere":
|
|
13
|
+
return _jsx("sphereGeometry", { args: args });
|
|
14
|
+
case "plane":
|
|
15
|
+
return _jsx("planeGeometry", { args: args });
|
|
16
|
+
default:
|
|
17
|
+
return _jsx("boxGeometry", { args: [1, 1, 1] });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const GeometryComponent = {
|
|
21
|
+
name: 'Geometry',
|
|
22
|
+
Editor: GeometryComponentEditor,
|
|
23
|
+
View: GeometryComponentView,
|
|
24
|
+
defaultProperties: {
|
|
25
|
+
geometryType: 'box'
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export default GeometryComponent;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { TextureListViewer } from '../../assetviewer/page';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
function MaterialComponentEditor({ component, onUpdate }) {
|
|
5
|
+
var _a, _b, _c, _d;
|
|
6
|
+
const [textureFiles, setTextureFiles] = useState([]);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
fetch('/textures/manifest.json')
|
|
9
|
+
.then(r => r.json())
|
|
10
|
+
.then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
|
|
11
|
+
.catch(console.error);
|
|
12
|
+
}, []);
|
|
13
|
+
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", { className: "flex items-center gap-1 mb-1", children: [_jsx("input", { type: "checkbox", className: "w-3 h-3", checked: component.properties.wireframe || false, onChange: e => onUpdate({ 'wireframe': e.target.checked }) }), _jsx("label", { className: "text-[9px] text-cyan-400/60", children: "Wireframe" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Texture" }), _jsx("div", { className: "max-h-32 overflow-y-auto", children: _jsx(TextureListViewer, { files: textureFiles, selected: component.properties.texture || undefined, onSelect: (file) => onUpdate({ 'texture': file }) }) })] }), component.properties.texture && (_jsxs("div", { className: "border-t border-cyan-500/20 pt-1 mt-1", children: [_jsxs("div", { className: "flex items-center gap-1 mb-1", children: [_jsx("input", { type: "checkbox", className: "w-3 h-3", checked: component.properties.repeat || false, onChange: e => onUpdate({ 'repeat': e.target.checked }) }), _jsx("label", { className: "text-[9px] text-cyan-400/60", children: "Repeat Texture" })] }), component.properties.repeat && (_jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Repeat (X, Y)" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "number", 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: (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, onChange: e => {
|
|
14
|
+
var _a, _b;
|
|
15
|
+
const y = (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : 1;
|
|
16
|
+
onUpdate({ 'repeatCount': [parseFloat(e.target.value), y] });
|
|
17
|
+
} }), _jsx("input", { type: "number", 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: (_d = (_c = component.properties.repeatCount) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : 1, onChange: e => {
|
|
18
|
+
var _a, _b;
|
|
19
|
+
const x = (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1;
|
|
20
|
+
onUpdate({ 'repeatCount': [x, parseFloat(e.target.value)] });
|
|
21
|
+
} })] })] }))] }))] }));
|
|
22
|
+
}
|
|
23
|
+
;
|
|
24
|
+
import { useMemo } from 'react';
|
|
25
|
+
import { DoubleSide, RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace } from 'three';
|
|
26
|
+
// View for Material component
|
|
27
|
+
function MaterialComponentView({ properties, loadedTextures, isSelected }) {
|
|
28
|
+
var _a;
|
|
29
|
+
const textureName = properties === null || properties === void 0 ? void 0 : properties.texture;
|
|
30
|
+
const repeat = properties === null || properties === void 0 ? void 0 : properties.repeat;
|
|
31
|
+
const repeatCount = properties === null || properties === void 0 ? void 0 : properties.repeatCount;
|
|
32
|
+
const texture = textureName && loadedTextures ? loadedTextures[textureName] : undefined;
|
|
33
|
+
const finalTexture = useMemo(() => {
|
|
34
|
+
if (!texture)
|
|
35
|
+
return undefined;
|
|
36
|
+
const t = texture.clone();
|
|
37
|
+
if (repeat) {
|
|
38
|
+
t.wrapS = t.wrapT = RepeatWrapping;
|
|
39
|
+
if (repeatCount)
|
|
40
|
+
t.repeat.set(repeatCount[0], repeatCount[1]);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
t.wrapS = t.wrapT = ClampToEdgeWrapping;
|
|
44
|
+
t.repeat.set(1, 1);
|
|
45
|
+
}
|
|
46
|
+
t.colorSpace = SRGBColorSpace;
|
|
47
|
+
t.needsUpdate = true;
|
|
48
|
+
return t;
|
|
49
|
+
}, [texture, repeat, repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0], repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1]]);
|
|
50
|
+
if (!properties) {
|
|
51
|
+
return _jsx("meshStandardMaterial", { color: "red", wireframe: true });
|
|
52
|
+
}
|
|
53
|
+
const { color, wireframe = false } = properties;
|
|
54
|
+
const displayColor = isSelected ? "cyan" : color;
|
|
55
|
+
return _jsx("meshStandardMaterial", { color: displayColor, wireframe: wireframe, map: finalTexture, transparent: !!finalTexture, side: DoubleSide }, (_a = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _a !== void 0 ? _a : 'no-texture');
|
|
56
|
+
}
|
|
57
|
+
const MaterialComponent = {
|
|
58
|
+
name: 'Material',
|
|
59
|
+
Editor: MaterialComponentEditor,
|
|
60
|
+
View: MaterialComponentView,
|
|
61
|
+
defaultProperties: {
|
|
62
|
+
color: '#ffffff',
|
|
63
|
+
wireframe: false
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export default MaterialComponent;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { ModelListViewer } from '../../assetviewer/page';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
function ModelComponentEditor({ component, onUpdate }) {
|
|
5
|
+
const [modelFiles, setModelFiles] = useState([]);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
fetch('/models/manifest.json')
|
|
8
|
+
.then(r => r.json())
|
|
9
|
+
.then(data => setModelFiles(Array.isArray(data) ? data : data.files || []))
|
|
10
|
+
.catch(console.error);
|
|
11
|
+
}, []);
|
|
12
|
+
const handleModelSelect = (file) => {
|
|
13
|
+
// Remove leading slash for prefab compatibility
|
|
14
|
+
const filename = file.startsWith('/') ? file.slice(1) : file;
|
|
15
|
+
onUpdate({ 'filename': filename });
|
|
16
|
+
};
|
|
17
|
+
return _jsxs("div", { children: [_jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Model" }), _jsx("div", { className: "max-h-32 overflow-y-auto", children: _jsx(ModelListViewer, { files: modelFiles, selected: component.properties.filename ? `/${component.properties.filename}` : undefined, onSelect: handleModelSelect }) })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("input", { type: "checkbox", id: "instanced-checkbox", checked: component.properties.instanced || false, onChange: e => onUpdate({ 'instanced': e.target.checked }), className: "w-3 h-3" }), _jsx("label", { htmlFor: "instanced-checkbox", className: "text-[9px] text-cyan-400/60", children: "Instanced" })] })] });
|
|
18
|
+
}
|
|
19
|
+
// View for Model component
|
|
20
|
+
function ModelComponentView({ properties, loadedModels, children }) {
|
|
21
|
+
// Instanced models are handled elsewhere (GameInstance), so only render non-instanced here
|
|
22
|
+
if (!properties.filename || properties.instanced)
|
|
23
|
+
return children || null;
|
|
24
|
+
if (loadedModels && loadedModels[properties.filename]) {
|
|
25
|
+
return _jsxs(_Fragment, { children: [_jsx("primitive", { object: loadedModels[properties.filename].clone() }), children] });
|
|
26
|
+
}
|
|
27
|
+
// Optionally, render a placeholder if model is not loaded
|
|
28
|
+
return children || null;
|
|
29
|
+
}
|
|
30
|
+
const ModelComponent = {
|
|
31
|
+
name: 'Model',
|
|
32
|
+
Editor: ModelComponentEditor,
|
|
33
|
+
View: ModelComponentView,
|
|
34
|
+
defaultProperties: {
|
|
35
|
+
filename: '',
|
|
36
|
+
instanced: false
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
export default ModelComponent;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
function PhysicsComponentEditor({ component, onUpdate }) {
|
|
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
|
+
}
|
|
5
|
+
import { RigidBody } from "@react-three/rapier";
|
|
6
|
+
function PhysicsComponentView({ properties, children, registerRef, transform, editMode }) {
|
|
7
|
+
if (editMode)
|
|
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 }));
|
|
10
|
+
}
|
|
11
|
+
const PhysicsComponent = {
|
|
12
|
+
name: 'Physics',
|
|
13
|
+
Editor: PhysicsComponentEditor,
|
|
14
|
+
View: PhysicsComponentView,
|
|
15
|
+
defaultProperties: {
|
|
16
|
+
type: 'dynamic'
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export default PhysicsComponent;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
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) }) })] })] });
|
|
4
|
+
}
|
|
5
|
+
// The view component for SpotLight
|
|
6
|
+
function SpotLightView({ properties }) {
|
|
7
|
+
// You can expand this with more spotlight properties as needed
|
|
8
|
+
return _jsx("spotLight", { color: properties.color, intensity: properties.intensity });
|
|
9
|
+
}
|
|
10
|
+
const SpotLightComponent = {
|
|
11
|
+
name: 'SpotLight',
|
|
12
|
+
Editor: SpotLightComponentEditor,
|
|
13
|
+
View: SpotLightView,
|
|
14
|
+
defaultProperties: {
|
|
15
|
+
color: '#ffffff',
|
|
16
|
+
intensity: 1.0
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export default SpotLightComponent;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Component } from "./ComponentRegistry";
|
|
2
|
+
declare const TransformComponent: Component;
|
|
3
|
+
export default TransformComponent;
|
|
4
|
+
export declare function Vector3Input({ label, value, onChange }: {
|
|
5
|
+
label: string;
|
|
6
|
+
value: [number, number, number];
|
|
7
|
+
onChange: (v: [number, number, number]) => void;
|
|
8
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
function TransformComponentEditor({ component, onUpdate }) {
|
|
3
|
+
return _jsxs("div", { className: "flex flex-col", children: [_jsx(Vector3Input, { label: "Position", value: component.properties.position, onChange: v => onUpdate({ position: v }) }), _jsx(Vector3Input, { label: "Rotation", value: component.properties.rotation, onChange: v => onUpdate({ rotation: v }) }), _jsx(Vector3Input, { label: "Scale", value: component.properties.scale, onChange: v => onUpdate({ scale: v }) })] });
|
|
4
|
+
}
|
|
5
|
+
const TransformComponent = {
|
|
6
|
+
name: 'Transform',
|
|
7
|
+
Editor: TransformComponentEditor,
|
|
8
|
+
defaultProperties: {
|
|
9
|
+
position: [0, 0, 0],
|
|
10
|
+
rotation: [0, 0, 0],
|
|
11
|
+
scale: [1, 1, 1]
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export default TransformComponent;
|
|
15
|
+
export function Vector3Input({ label, value, onChange }) {
|
|
16
|
+
const handleChange = (index, val) => {
|
|
17
|
+
const newValue = [...value];
|
|
18
|
+
newValue[index] = parseFloat(val) || 0;
|
|
19
|
+
onChange(newValue);
|
|
20
|
+
};
|
|
21
|
+
return _jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: label }), _jsxs("div", { className: "flex gap-0.5", children: [_jsxs("div", { className: "relative flex-1", children: [_jsx("span", { className: "absolute left-0.5 top-0 text-[8px] text-red-400/80 font-mono", children: "X" }), _jsx("input", { className: "w-full bg-black/40 border border-cyan-500/30 pl-3 pr-0.5 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", type: "number", step: "0.1", value: value[0], onChange: e => handleChange(0, e.target.value) })] }), _jsxs("div", { className: "relative flex-1", children: [_jsx("span", { className: "absolute left-0.5 top-0 text-[8px] text-green-400/80 font-mono", children: "Y" }), _jsx("input", { className: "w-full bg-black/40 border border-cyan-500/30 pl-3 pr-0.5 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", type: "number", step: "0.1", value: value[1], onChange: e => handleChange(1, e.target.value) })] }), _jsxs("div", { className: "relative flex-1", children: [_jsx("span", { className: "absolute left-0.5 top-0 text-[8px] text-blue-400/80 font-mono", children: "Z" }), _jsx("input", { className: "w-full bg-black/40 border border-cyan-500/30 pl-3 pr-0.5 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", type: "number", step: "0.1", value: value[2], onChange: e => handleChange(2, e.target.value) })] })] })] });
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import GeometryComponent from './GeometryComponent';
|
|
2
|
+
import TransformComponent from './TransformComponent';
|
|
3
|
+
import MaterialComponent from './MaterialComponent';
|
|
4
|
+
import PhysicsComponent from './PhysicsComponent';
|
|
5
|
+
import SpotLightComponent from './SpotLightComponent';
|
|
6
|
+
import ModelComponent from './ModelComponent';
|
|
7
|
+
export default [
|
|
8
|
+
GeometryComponent,
|
|
9
|
+
TransformComponent,
|
|
10
|
+
MaterialComponent,
|
|
11
|
+
PhysicsComponent,
|
|
12
|
+
SpotLightComponent,
|
|
13
|
+
ModelComponent
|
|
14
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function PrefabEditorPage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import PrefabEditor from "./PrefabEditor";
|
|
3
|
+
export default function PrefabEditorPage() {
|
|
4
|
+
return _jsx("div", { className: "w-screen h-screen", children: _jsx(PrefabEditor, { children: _jsx("directionalLight", { position: [5, 10, 7.5], intensity: 1, castShadow: true }) }) });
|
|
5
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface Prefab {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
author?: string;
|
|
6
|
+
version?: string;
|
|
7
|
+
assets?: string[] | {
|
|
8
|
+
[assetName: string]: string;
|
|
9
|
+
};
|
|
10
|
+
onStart?: (target: any) => void;
|
|
11
|
+
root: GameObject;
|
|
12
|
+
}
|
|
13
|
+
export interface GameObject {
|
|
14
|
+
id: string;
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
visible: boolean;
|
|
17
|
+
ref?: any;
|
|
18
|
+
children?: GameObject[];
|
|
19
|
+
components?: {
|
|
20
|
+
[uuid: string]: Component | undefined;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
interface Component {
|
|
24
|
+
type: string;
|
|
25
|
+
properties: {
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|