react-three-game 0.0.7 → 0.0.8

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.
@@ -0,0 +1,68 @@
1
+ import { ModelListViewer } from '../../assetviewer/page';
2
+ import { useEffect, useState } from 'react';
3
+ import { Component } from './ComponentRegistry';
4
+
5
+ function ModelComponentEditor({ component, onUpdate, basePath = "" }: { component: any; onUpdate: (newComp: any) => void; basePath?: string }) {
6
+ const [modelFiles, setModelFiles] = useState<string[]>([]);
7
+
8
+ useEffect(() => {
9
+ const base = basePath ? `${basePath}/` : '';
10
+ fetch(`/${base}models/manifest.json`)
11
+ .then(r => r.json())
12
+ .then(data => setModelFiles(Array.isArray(data) ? data : data.files || []))
13
+ .catch(console.error);
14
+ }, [basePath]);
15
+
16
+ const handleModelSelect = (file: string) => {
17
+ // Remove leading slash for prefab compatibility
18
+ const filename = file.startsWith('/') ? file.slice(1) : file;
19
+ onUpdate({ 'filename': filename });
20
+ };
21
+
22
+ return <div>
23
+ <div className="mb-1">
24
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Model</label>
25
+ <div className="max-h-32 overflow-y-auto">
26
+ <ModelListViewer
27
+ files={modelFiles}
28
+ selected={component.properties.filename ? `/${component.properties.filename}` : undefined}
29
+ onSelect={handleModelSelect}
30
+ basePath={basePath}
31
+ />
32
+ </div>
33
+ </div>
34
+ <div className="flex items-center gap-1">
35
+ <input
36
+ type="checkbox"
37
+ id="instanced-checkbox"
38
+ checked={component.properties.instanced || false}
39
+ onChange={e => onUpdate({ 'instanced': e.target.checked })}
40
+ className="w-3 h-3"
41
+ />
42
+ <label htmlFor="instanced-checkbox" className="text-[9px] text-cyan-400/60">Instanced</label>
43
+ </div>
44
+ </div>;
45
+ }
46
+
47
+ // View for Model component
48
+ function ModelComponentView({ properties, loadedModels, children }: { properties: any, loadedModels?: Record<string, any>, children?: React.ReactNode }) {
49
+ // Instanced models are handled elsewhere (GameInstance), so only render non-instanced here
50
+ if (!properties.filename || properties.instanced) return children || null;
51
+ if (loadedModels && loadedModels[properties.filename]) {
52
+ return <>{<primitive object={loadedModels[properties.filename].clone()} />}{children}</>;
53
+ }
54
+ // Optionally, render a placeholder if model is not loaded
55
+ return children || null;
56
+ }
57
+
58
+ const ModelComponent: Component = {
59
+ name: 'Model',
60
+ Editor: ModelComponentEditor,
61
+ View: ModelComponentView,
62
+ defaultProperties: {
63
+ filename: '',
64
+ instanced: false
65
+ }
66
+ };
67
+
68
+ export default ModelComponent;
@@ -0,0 +1,47 @@
1
+ import { Component } from "./ComponentRegistry";
2
+
3
+ function PhysicsComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
4
+ return <div>
5
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Type</label>
6
+ <select
7
+ 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"
8
+ value={component.properties.type}
9
+ onChange={e => onUpdate({ type: e.target.value })}
10
+ >
11
+ <option value="dynamic">Dynamic</option>
12
+ <option value="fixed">Fixed</option>
13
+ </select>
14
+ </div>;
15
+ }
16
+
17
+
18
+ import { RigidBody } from "@react-three/rapier";
19
+ import { Object3D } from "three";
20
+ import { useRef } from "react";
21
+
22
+ function PhysicsComponentView({ properties, children, registerRef, transform, editMode }: any) {
23
+ if (editMode) return children;
24
+ return (
25
+ <RigidBody
26
+ ref={el => registerRef && registerRef(properties.id, el as unknown as Object3D)}
27
+ position={transform?.position}
28
+ rotation={transform?.rotation}
29
+ scale={transform?.scale}
30
+ type={properties.type}
31
+ colliders="cuboid"
32
+ >
33
+ {children}
34
+ </RigidBody>
35
+ );
36
+ }
37
+
38
+ const PhysicsComponent: Component = {
39
+ name: 'Physics',
40
+ Editor: PhysicsComponentEditor,
41
+ View: PhysicsComponentView,
42
+ defaultProperties: {
43
+ type: 'dynamic'
44
+ }
45
+ };
46
+
47
+ export default PhysicsComponent;
@@ -0,0 +1,53 @@
1
+
2
+ import { Component } from "./ComponentRegistry";
3
+
4
+ function SpotLightComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
5
+ return <div className="flex flex-col">
6
+ <div className="mb-1">
7
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Color</label>
8
+ <div className="flex gap-0.5">
9
+ <input
10
+ type="color"
11
+ className="h-5 w-5 bg-transparent border-none cursor-pointer"
12
+ value={component.properties.color}
13
+ onChange={e => onUpdate({ 'color': e.target.value })}
14
+ />
15
+ <input
16
+ type="text"
17
+ 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"
18
+ value={component.properties.color}
19
+ onChange={e => onUpdate({ 'color': e.target.value })}
20
+ />
21
+ </div>
22
+ </div>
23
+ <div>
24
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">Intensity</label>
25
+ <input
26
+ type="number"
27
+ step="0.1"
28
+ 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"
29
+ value={component.properties.intensity}
30
+ onChange={e => onUpdate({ 'intensity': parseFloat(e.target.value) })}
31
+ />
32
+ </div>
33
+ </div>;
34
+ }
35
+
36
+
37
+ // The view component for SpotLight
38
+ function SpotLightView({ properties }: { properties: any }) {
39
+ // You can expand this with more spotlight properties as needed
40
+ return <spotLight color={properties.color} intensity={properties.intensity} />;
41
+ }
42
+
43
+ const SpotLightComponent: Component = {
44
+ name: 'SpotLight',
45
+ Editor: SpotLightComponentEditor,
46
+ View: SpotLightView,
47
+ defaultProperties: {
48
+ color: '#ffffff',
49
+ intensity: 1.0
50
+ }
51
+ };
52
+
53
+ export default SpotLightComponent;
@@ -0,0 +1,49 @@
1
+
2
+ import { Component } from "./ComponentRegistry";
3
+
4
+ function TransformComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
5
+ return <div className="flex flex-col">
6
+ <Vector3Input label="Position" value={component.properties.position} onChange={v => onUpdate({ position: v })} />
7
+ <Vector3Input label="Rotation" value={component.properties.rotation} onChange={v => onUpdate({ rotation: v })} />
8
+ <Vector3Input label="Scale" value={component.properties.scale} onChange={v => onUpdate({ scale: v })} />
9
+ </div>;
10
+ }
11
+
12
+ const TransformComponent: Component = {
13
+ name: 'Transform',
14
+ Editor: TransformComponentEditor,
15
+ defaultProperties: {
16
+ position: [0, 0, 0],
17
+ rotation: [0, 0, 0],
18
+ scale: [1, 1, 1]
19
+ }
20
+ };
21
+
22
+ export default TransformComponent;
23
+
24
+
25
+ export function Vector3Input({ label, value, onChange }: { label: string, value: [number, number, number], onChange: (v: [number, number, number]) => void }) {
26
+ const handleChange = (index: number, val: string) => {
27
+ const newValue = [...value] as [number, number, number];
28
+ newValue[index] = parseFloat(val) || 0;
29
+ onChange(newValue);
30
+ };
31
+
32
+ return <div className="mb-1">
33
+ <label className="block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5">{label}</label>
34
+ <div className="flex gap-0.5">
35
+ <div className="relative flex-1">
36
+ <span className="absolute left-0.5 top-0 text-[8px] text-red-400/80 font-mono">X</span>
37
+ <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)} />
38
+ </div>
39
+ <div className="relative flex-1">
40
+ <span className="absolute left-0.5 top-0 text-[8px] text-green-400/80 font-mono">Y</span>
41
+ <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)} />
42
+ </div>
43
+ <div className="relative flex-1">
44
+ <span className="absolute left-0.5 top-0 text-[8px] text-blue-400/80 font-mono">Z</span>
45
+ <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)} />
46
+ </div>
47
+ </div>
48
+ </div>
49
+ }
@@ -0,0 +1,16 @@
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
+
8
+ export default [
9
+ GeometryComponent,
10
+ TransformComponent,
11
+ MaterialComponent,
12
+ PhysicsComponent,
13
+ SpotLightComponent,
14
+ ModelComponent
15
+ ];
16
+
@@ -0,0 +1,10 @@
1
+ import PrefabEditor from "./PrefabEditor";
2
+
3
+
4
+ export default function PrefabEditorPage() {
5
+ return <div className="w-screen h-screen">
6
+ <PrefabEditor>
7
+ <directionalLight position={[5, 10, 7.5]} intensity={1} castShadow />
8
+ </PrefabEditor>
9
+ </div>
10
+ }
@@ -0,0 +1,28 @@
1
+ import { ThreeElements } from "@react-three/fiber"
2
+
3
+ export interface Prefab {
4
+ id: string;
5
+ name: string;
6
+ description?: string;
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
12
+ }
13
+
14
+ export interface GameObject {
15
+ id: string;
16
+ enabled: boolean;
17
+ visible: boolean;
18
+ ref?: any;
19
+ children?: GameObject[];
20
+ components?: {
21
+ [uuid: string]: Component | undefined;
22
+ };
23
+ }
24
+
25
+ interface Component {
26
+ type: string;
27
+ properties: { [key: string]: any };
28
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES6",
4
+ "lib": ["ES2017", "DOM"],
5
+ "module": "ESNext",
6
+ "jsx": "react-jsx",
7
+ "declaration": true,
8
+ "declarationDir": "dist",
9
+ "outDir": "dist",
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "moduleResolution": "bundler",
14
+ "allowSyntheticDefaultImports": true
15
+ },
16
+ "include": ["src"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }