react-three-game 0.0.17 → 0.0.19
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/.github/copilot-instructions.md +54 -183
- package/README.md +69 -214
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -0
- package/dist/tools/prefabeditor/EditorTree.d.ts +2 -4
- package/dist/tools/prefabeditor/EditorTree.js +20 -194
- package/dist/tools/prefabeditor/EditorUI.js +43 -224
- package/dist/tools/prefabeditor/InstanceProvider.d.ts +4 -4
- package/dist/tools/prefabeditor/InstanceProvider.js +21 -13
- package/dist/tools/prefabeditor/PrefabEditor.js +33 -99
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +0 -1
- package/dist/tools/prefabeditor/PrefabRoot.js +33 -50
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +102 -0
- package/dist/tools/prefabeditor/components/ModelComponent.js +12 -4
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +10 -5
- package/dist/tools/prefabeditor/components/index.js +2 -0
- package/dist/tools/prefabeditor/hooks/useModelLoader.d.ts +10 -0
- package/dist/tools/prefabeditor/hooks/useModelLoader.js +40 -0
- package/dist/tools/prefabeditor/styles.d.ts +1809 -0
- package/dist/tools/prefabeditor/styles.js +168 -0
- package/dist/tools/prefabeditor/types.d.ts +3 -14
- package/dist/tools/prefabeditor/types.js +0 -1
- package/dist/tools/prefabeditor/utils.d.ts +19 -0
- package/dist/tools/prefabeditor/utils.js +72 -0
- package/package.json +3 -3
- package/src/index.ts +5 -1
- package/src/tools/prefabeditor/EditorTree.tsx +38 -270
- package/src/tools/prefabeditor/EditorUI.tsx +105 -322
- package/src/tools/prefabeditor/InstanceProvider.tsx +43 -32
- package/src/tools/prefabeditor/PrefabEditor.tsx +40 -151
- package/src/tools/prefabeditor/PrefabRoot.tsx +41 -73
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +317 -0
- package/src/tools/prefabeditor/components/ModelComponent.tsx +14 -4
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +27 -7
- package/src/tools/prefabeditor/components/index.ts +2 -0
- package/src/tools/prefabeditor/styles.ts +195 -0
- package/src/tools/prefabeditor/types.ts +4 -12
- package/src/tools/prefabeditor/utils.ts +80 -0
|
@@ -3,6 +3,7 @@ import TransformComponent from './TransformComponent';
|
|
|
3
3
|
import MaterialComponent from './MaterialComponent';
|
|
4
4
|
import PhysicsComponent from './PhysicsComponent';
|
|
5
5
|
import SpotLightComponent from './SpotLightComponent';
|
|
6
|
+
import DirectionalLightComponent from './DirectionalLightComponent';
|
|
6
7
|
import ModelComponent from './ModelComponent';
|
|
7
8
|
|
|
8
9
|
export default [
|
|
@@ -11,6 +12,7 @@ export default [
|
|
|
11
12
|
MaterialComponent,
|
|
12
13
|
PhysicsComponent,
|
|
13
14
|
SpotLightComponent,
|
|
15
|
+
DirectionalLightComponent,
|
|
14
16
|
ModelComponent
|
|
15
17
|
];
|
|
16
18
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Shared editor styles - single source of truth for all prefab editor UI
|
|
2
|
+
|
|
3
|
+
export const colors = {
|
|
4
|
+
bg: 'rgba(0,0,0,0.6)',
|
|
5
|
+
bgLight: 'rgba(255,255,255,0.06)',
|
|
6
|
+
bgHover: 'rgba(255,255,255,0.1)',
|
|
7
|
+
border: 'rgba(255,255,255,0.15)',
|
|
8
|
+
borderLight: 'rgba(255,255,255,0.1)',
|
|
9
|
+
borderFaint: 'rgba(255,255,255,0.05)',
|
|
10
|
+
text: '#fff',
|
|
11
|
+
textMuted: 'rgba(255,255,255,0.7)',
|
|
12
|
+
danger: '#ffaaaa',
|
|
13
|
+
dangerBg: 'rgba(255,80,80,0.2)',
|
|
14
|
+
dangerBorder: 'rgba(255,80,80,0.4)',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const fonts = {
|
|
18
|
+
family: 'system-ui, sans-serif',
|
|
19
|
+
size: 11,
|
|
20
|
+
sizeSm: 10,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Base component styles
|
|
24
|
+
export const base = {
|
|
25
|
+
panel: {
|
|
26
|
+
background: colors.bg,
|
|
27
|
+
color: colors.text,
|
|
28
|
+
border: `1px solid ${colors.border}`,
|
|
29
|
+
borderRadius: 4,
|
|
30
|
+
overflow: 'hidden',
|
|
31
|
+
backdropFilter: 'blur(8px)',
|
|
32
|
+
fontFamily: fonts.family,
|
|
33
|
+
fontSize: fonts.size,
|
|
34
|
+
} as React.CSSProperties,
|
|
35
|
+
|
|
36
|
+
header: {
|
|
37
|
+
padding: '6px 8px',
|
|
38
|
+
display: 'flex',
|
|
39
|
+
alignItems: 'center',
|
|
40
|
+
justifyContent: 'space-between',
|
|
41
|
+
cursor: 'pointer',
|
|
42
|
+
background: colors.bgLight,
|
|
43
|
+
borderBottom: `1px solid ${colors.borderLight}`,
|
|
44
|
+
fontSize: fonts.size,
|
|
45
|
+
fontWeight: 500,
|
|
46
|
+
textTransform: 'uppercase',
|
|
47
|
+
letterSpacing: 0.5,
|
|
48
|
+
} as React.CSSProperties,
|
|
49
|
+
|
|
50
|
+
input: {
|
|
51
|
+
width: '100%',
|
|
52
|
+
background: colors.bgHover,
|
|
53
|
+
border: `1px solid ${colors.border}`,
|
|
54
|
+
borderRadius: 3,
|
|
55
|
+
padding: '4px 6px',
|
|
56
|
+
color: colors.text,
|
|
57
|
+
fontSize: fonts.size,
|
|
58
|
+
outline: 'none',
|
|
59
|
+
} as React.CSSProperties,
|
|
60
|
+
|
|
61
|
+
btn: {
|
|
62
|
+
background: colors.bgHover,
|
|
63
|
+
border: `1px solid ${colors.border}`,
|
|
64
|
+
borderRadius: 3,
|
|
65
|
+
padding: '4px 8px',
|
|
66
|
+
color: colors.text,
|
|
67
|
+
fontSize: fonts.size,
|
|
68
|
+
cursor: 'pointer',
|
|
69
|
+
outline: 'none',
|
|
70
|
+
} as React.CSSProperties,
|
|
71
|
+
|
|
72
|
+
btnDanger: {
|
|
73
|
+
background: colors.dangerBg,
|
|
74
|
+
borderColor: colors.dangerBorder,
|
|
75
|
+
color: colors.danger,
|
|
76
|
+
} as React.CSSProperties,
|
|
77
|
+
|
|
78
|
+
label: {
|
|
79
|
+
fontSize: fonts.sizeSm,
|
|
80
|
+
opacity: 0.7,
|
|
81
|
+
marginBottom: 4,
|
|
82
|
+
textTransform: 'uppercase',
|
|
83
|
+
letterSpacing: 0.5,
|
|
84
|
+
} as React.CSSProperties,
|
|
85
|
+
|
|
86
|
+
row: {
|
|
87
|
+
display: 'flex',
|
|
88
|
+
gap: 6,
|
|
89
|
+
} as React.CSSProperties,
|
|
90
|
+
|
|
91
|
+
section: {
|
|
92
|
+
paddingBottom: 8,
|
|
93
|
+
borderBottom: `1px solid ${colors.borderLight}`,
|
|
94
|
+
} as React.CSSProperties,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Specific panel styles
|
|
98
|
+
export const inspector = {
|
|
99
|
+
panel: {
|
|
100
|
+
...base.panel,
|
|
101
|
+
position: 'absolute' as const,
|
|
102
|
+
top: 8,
|
|
103
|
+
right: 8,
|
|
104
|
+
zIndex: 20,
|
|
105
|
+
width: 260,
|
|
106
|
+
},
|
|
107
|
+
content: {
|
|
108
|
+
padding: 8,
|
|
109
|
+
maxHeight: '80vh',
|
|
110
|
+
overflowY: 'auto' as const,
|
|
111
|
+
display: 'flex',
|
|
112
|
+
flexDirection: 'column' as const,
|
|
113
|
+
gap: 8,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const tree = {
|
|
118
|
+
panel: {
|
|
119
|
+
...base.panel,
|
|
120
|
+
maxHeight: '85vh',
|
|
121
|
+
display: 'flex',
|
|
122
|
+
flexDirection: 'column' as const,
|
|
123
|
+
userSelect: 'none' as const,
|
|
124
|
+
},
|
|
125
|
+
scroll: {
|
|
126
|
+
overflowY: 'auto' as const,
|
|
127
|
+
padding: 4,
|
|
128
|
+
},
|
|
129
|
+
row: {
|
|
130
|
+
display: 'flex',
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
padding: '3px 6px',
|
|
133
|
+
borderBottom: `1px solid ${colors.borderFaint}`,
|
|
134
|
+
cursor: 'pointer',
|
|
135
|
+
whiteSpace: 'nowrap' as const,
|
|
136
|
+
} as React.CSSProperties,
|
|
137
|
+
selected: {
|
|
138
|
+
background: 'rgba(255,255,255,0.12)',
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const menu = {
|
|
143
|
+
container: {
|
|
144
|
+
position: 'fixed' as const,
|
|
145
|
+
zIndex: 50,
|
|
146
|
+
minWidth: 120,
|
|
147
|
+
background: 'rgba(0,0,0,0.85)',
|
|
148
|
+
border: `1px solid ${colors.border}`,
|
|
149
|
+
borderRadius: 4,
|
|
150
|
+
overflow: 'hidden',
|
|
151
|
+
boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
|
|
152
|
+
backdropFilter: 'blur(8px)',
|
|
153
|
+
},
|
|
154
|
+
item: {
|
|
155
|
+
width: '100%',
|
|
156
|
+
textAlign: 'left' as const,
|
|
157
|
+
padding: '6px 8px',
|
|
158
|
+
background: 'transparent',
|
|
159
|
+
border: 'none',
|
|
160
|
+
color: colors.text,
|
|
161
|
+
fontSize: fonts.size,
|
|
162
|
+
cursor: 'pointer',
|
|
163
|
+
outline: 'none',
|
|
164
|
+
} as React.CSSProperties,
|
|
165
|
+
danger: {
|
|
166
|
+
color: colors.danger,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const toolbar = {
|
|
171
|
+
panel: {
|
|
172
|
+
position: 'absolute' as const,
|
|
173
|
+
top: 8,
|
|
174
|
+
left: '50%',
|
|
175
|
+
transform: 'translateX(-50%)',
|
|
176
|
+
display: 'flex',
|
|
177
|
+
gap: 6,
|
|
178
|
+
padding: '4px 6px',
|
|
179
|
+
background: colors.bg,
|
|
180
|
+
border: `1px solid ${colors.border}`,
|
|
181
|
+
borderRadius: 4,
|
|
182
|
+
color: colors.text,
|
|
183
|
+
fontFamily: fonts.family,
|
|
184
|
+
fontSize: fonts.size,
|
|
185
|
+
backdropFilter: 'blur(8px)',
|
|
186
|
+
},
|
|
187
|
+
divider: {
|
|
188
|
+
width: 1,
|
|
189
|
+
background: 'rgba(255,255,255,0.2)',
|
|
190
|
+
},
|
|
191
|
+
disabled: {
|
|
192
|
+
opacity: 0.4,
|
|
193
|
+
cursor: 'not-allowed',
|
|
194
|
+
},
|
|
195
|
+
};
|
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
// import { ThreeElements } from "@react-three/fiber"
|
|
2
|
-
|
|
3
1
|
export interface Prefab {
|
|
4
2
|
id?: string;
|
|
5
3
|
name?: string;
|
|
6
|
-
|
|
7
|
-
author?: string;
|
|
8
|
-
version?: string;
|
|
9
|
-
assets?: string[] | {[assetName: string]: string}; // List of asset URLs or a mapping of asset names to URLs
|
|
10
|
-
onStart?: (target: any) => void; // The logic function to run when the map starts
|
|
11
|
-
root: GameObject; // The root node of the scene graph
|
|
4
|
+
root: GameObject;
|
|
12
5
|
}
|
|
13
6
|
|
|
14
7
|
export interface GameObject {
|
|
15
8
|
id: string;
|
|
16
9
|
disabled?: boolean;
|
|
17
10
|
hidden?: boolean;
|
|
18
|
-
ref?: any;
|
|
19
11
|
children?: GameObject[];
|
|
20
12
|
components?: {
|
|
21
|
-
[
|
|
13
|
+
[key: string]: ComponentData | undefined;
|
|
22
14
|
};
|
|
23
15
|
}
|
|
24
16
|
|
|
25
|
-
interface
|
|
17
|
+
export interface ComponentData {
|
|
26
18
|
type: string;
|
|
27
|
-
properties:
|
|
19
|
+
properties: Record<string, any>;
|
|
28
20
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { GameObject } from "./types";
|
|
2
|
+
|
|
3
|
+
/** Find a node by ID in the tree */
|
|
4
|
+
export function findNode(root: GameObject, id: string): GameObject | null {
|
|
5
|
+
if (root.id === id) return root;
|
|
6
|
+
for (const child of root.children ?? []) {
|
|
7
|
+
const found = findNode(child, id);
|
|
8
|
+
if (found) return found;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Find the parent of a node by ID */
|
|
14
|
+
export function findParent(root: GameObject, id: string): GameObject | null {
|
|
15
|
+
for (const child of root.children ?? []) {
|
|
16
|
+
if (child.id === id) return root;
|
|
17
|
+
const found = findParent(child, id);
|
|
18
|
+
if (found) return found;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Find all nodes matching a predicate */
|
|
24
|
+
export function findAll(root: GameObject, predicate: (node: GameObject) => boolean): GameObject[] {
|
|
25
|
+
const results: GameObject[] = [];
|
|
26
|
+
if (predicate(root)) results.push(root);
|
|
27
|
+
for (const child of root.children ?? []) {
|
|
28
|
+
results.push(...findAll(child, predicate));
|
|
29
|
+
}
|
|
30
|
+
return results;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Find all nodes that have a specific component type */
|
|
34
|
+
export function findByComponent(root: GameObject, componentType: string): GameObject[] {
|
|
35
|
+
return findAll(root, node =>
|
|
36
|
+
Object.values(node.components ?? {}).some(c => c?.type === componentType)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Get a flattened list of all nodes */
|
|
41
|
+
export function flatten(root: GameObject): GameObject[] {
|
|
42
|
+
return findAll(root, () => true);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Immutably update a node by ID */
|
|
46
|
+
export function updateNode(root: GameObject, id: string, update: (node: GameObject) => GameObject): GameObject {
|
|
47
|
+
if (root.id === id) return update(root);
|
|
48
|
+
if (!root.children) return root;
|
|
49
|
+
return {
|
|
50
|
+
...root,
|
|
51
|
+
children: root.children.map(child => updateNode(child, id, update))
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Immutably delete a node by ID */
|
|
56
|
+
export function deleteNode(root: GameObject, id: string): GameObject | null {
|
|
57
|
+
if (root.id === id) return null;
|
|
58
|
+
if (!root.children) return root;
|
|
59
|
+
return {
|
|
60
|
+
...root,
|
|
61
|
+
children: root.children
|
|
62
|
+
.map(child => deleteNode(child, id))
|
|
63
|
+
.filter((child): child is GameObject => child !== null)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Deep clone a node with new IDs */
|
|
68
|
+
export function cloneNode(node: GameObject): GameObject {
|
|
69
|
+
return {
|
|
70
|
+
...node,
|
|
71
|
+
id: crypto.randomUUID(),
|
|
72
|
+
children: node.children?.map(cloneNode)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Get component data from a node */
|
|
77
|
+
export function getComponent<T = any>(node: GameObject, type: string): T | undefined {
|
|
78
|
+
const comp = Object.values(node.components ?? {}).find(c => c?.type === type);
|
|
79
|
+
return comp?.properties as T | undefined;
|
|
80
|
+
}
|