@thewhateverapp/tile-sdk 0.13.37 → 0.14.1
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/dist/scene/SceneContext.d.ts +167 -0
- package/dist/scene/SceneContext.d.ts.map +1 -0
- package/dist/scene/SceneContext.js +86 -0
- package/dist/scene/SceneFromJson.d.ts +27 -0
- package/dist/scene/SceneFromJson.d.ts.map +1 -0
- package/dist/scene/SceneFromJson.js +74 -0
- package/dist/scene/SceneRenderer.d.ts +26 -0
- package/dist/scene/SceneRenderer.d.ts.map +1 -0
- package/dist/scene/SceneRenderer.js +201 -0
- package/dist/scene/camera/CameraController.d.ts +6 -0
- package/dist/scene/camera/CameraController.d.ts.map +1 -0
- package/dist/scene/camera/CameraController.js +84 -0
- package/dist/scene/components/ComponentRunner.d.ts +22 -0
- package/dist/scene/components/ComponentRunner.d.ts.map +1 -0
- package/dist/scene/components/ComponentRunner.js +197 -0
- package/dist/scene/effects/GlowFilter.d.ts +38 -0
- package/dist/scene/effects/GlowFilter.d.ts.map +1 -0
- package/dist/scene/effects/GlowFilter.js +40 -0
- package/dist/scene/effects/ParticleSystem.d.ts +52 -0
- package/dist/scene/effects/ParticleSystem.d.ts.map +1 -0
- package/dist/scene/effects/ParticleSystem.js +107 -0
- package/dist/scene/entities/EntityRenderer.d.ts +14 -0
- package/dist/scene/entities/EntityRenderer.d.ts.map +1 -0
- package/dist/scene/entities/EntityRenderer.js +203 -0
- package/dist/scene/index.d.ts +46 -0
- package/dist/scene/index.d.ts.map +1 -0
- package/dist/scene/index.js +50 -0
- package/dist/scene/input/InputManager.d.ts +18 -0
- package/dist/scene/input/InputManager.d.ts.map +1 -0
- package/dist/scene/input/InputManager.js +86 -0
- package/dist/scene/physics/PhysicsEngine.d.ts +15 -0
- package/dist/scene/physics/PhysicsEngine.d.ts.map +1 -0
- package/dist/scene/physics/PhysicsEngine.js +252 -0
- package/dist/scene/timeline/TimelineExecutor.d.ts +6 -0
- package/dist/scene/timeline/TimelineExecutor.d.ts.map +1 -0
- package/dist/scene/timeline/TimelineExecutor.js +236 -0
- package/dist/spec/schema.d.ts +12 -12
- package/package.json +14 -2
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { type MutableRefObject } from 'react';
|
|
2
|
+
import type { SceneSpecV1, Entity, CameraConfig } from '@thewhateverapp/scene-sdk';
|
|
3
|
+
import type { Engine, Body } from 'matter-js';
|
|
4
|
+
/**
|
|
5
|
+
* Runtime entity state - mutable for performance
|
|
6
|
+
*/
|
|
7
|
+
export interface EntityState {
|
|
8
|
+
/** Entity definition from spec */
|
|
9
|
+
entity: Entity;
|
|
10
|
+
/** Current position (updated by physics/components) */
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
/** Current rotation in radians */
|
|
14
|
+
rotation: number;
|
|
15
|
+
/** Current scale */
|
|
16
|
+
scaleX: number;
|
|
17
|
+
scaleY: number;
|
|
18
|
+
/** Current velocity (for physics/movement) */
|
|
19
|
+
velocityX: number;
|
|
20
|
+
velocityY: number;
|
|
21
|
+
/** Whether entity is visible */
|
|
22
|
+
visible: boolean;
|
|
23
|
+
/** Current fill color (can be animated) */
|
|
24
|
+
fill?: string;
|
|
25
|
+
/** Current alpha (can be animated) */
|
|
26
|
+
alpha: number;
|
|
27
|
+
/** Matter.js body (if entity has physics) */
|
|
28
|
+
body?: Body;
|
|
29
|
+
/** Whether entity is destroyed */
|
|
30
|
+
destroyed: boolean;
|
|
31
|
+
/** Custom component state */
|
|
32
|
+
componentState: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Player-specific state for dash games
|
|
36
|
+
*/
|
|
37
|
+
export interface PlayerState {
|
|
38
|
+
/** Is player on ground */
|
|
39
|
+
grounded: boolean;
|
|
40
|
+
/** Is player dead */
|
|
41
|
+
dead: boolean;
|
|
42
|
+
/** Current jump count (for double jump) */
|
|
43
|
+
jumpCount: number;
|
|
44
|
+
/** Last checkpoint position */
|
|
45
|
+
checkpointX: number;
|
|
46
|
+
checkpointY: number;
|
|
47
|
+
/** Is touching a jump orb */
|
|
48
|
+
touchingOrb: string | null;
|
|
49
|
+
/** Gravity direction (1 = down, -1 = up) */
|
|
50
|
+
gravityDir: number;
|
|
51
|
+
/** Current speed multiplier */
|
|
52
|
+
speedMultiplier: number;
|
|
53
|
+
/** Death count */
|
|
54
|
+
deaths: number;
|
|
55
|
+
/** Level complete */
|
|
56
|
+
complete: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Camera state
|
|
60
|
+
*/
|
|
61
|
+
export interface CameraState {
|
|
62
|
+
x: number;
|
|
63
|
+
y: number;
|
|
64
|
+
zoom: number;
|
|
65
|
+
shakeIntensity: number;
|
|
66
|
+
shakeTimeRemaining: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Input state
|
|
70
|
+
*/
|
|
71
|
+
export interface InputState {
|
|
72
|
+
/** Is jump/action pressed (space, tap, or up arrow) */
|
|
73
|
+
jumpPressed: boolean;
|
|
74
|
+
/** Is currently touching (for hold detection) */
|
|
75
|
+
touching: boolean;
|
|
76
|
+
/** Keys currently held */
|
|
77
|
+
keys: Record<string, boolean>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Timeline state
|
|
81
|
+
*/
|
|
82
|
+
export interface TimelineState {
|
|
83
|
+
/** Elapsed time in ms */
|
|
84
|
+
elapsedMs: number;
|
|
85
|
+
/** Current beat (based on BPM) */
|
|
86
|
+
currentBeat: number;
|
|
87
|
+
/** Index of next event to process */
|
|
88
|
+
nextEventIndex: number;
|
|
89
|
+
/** Active tweens */
|
|
90
|
+
activeTweens: ActiveTween[];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Active tween animation
|
|
94
|
+
*/
|
|
95
|
+
export interface ActiveTween {
|
|
96
|
+
targetId: string;
|
|
97
|
+
property: string;
|
|
98
|
+
startValue: number;
|
|
99
|
+
endValue: number;
|
|
100
|
+
startTime: number;
|
|
101
|
+
duration: number;
|
|
102
|
+
easing: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Scene context value - all scene runtime state
|
|
106
|
+
*/
|
|
107
|
+
export interface SceneContextValue {
|
|
108
|
+
/** Compiled scene spec */
|
|
109
|
+
spec: SceneSpecV1;
|
|
110
|
+
/** Entity states by ID */
|
|
111
|
+
entities: MutableRefObject<Map<string, EntityState>>;
|
|
112
|
+
/** Layer order (front to back) */
|
|
113
|
+
layers: string[];
|
|
114
|
+
/** Player state (for dash games) */
|
|
115
|
+
player: MutableRefObject<PlayerState>;
|
|
116
|
+
/** Camera state */
|
|
117
|
+
camera: MutableRefObject<CameraState>;
|
|
118
|
+
/** Input state */
|
|
119
|
+
input: MutableRefObject<InputState>;
|
|
120
|
+
/** Timeline state */
|
|
121
|
+
timeline: MutableRefObject<TimelineState>;
|
|
122
|
+
/** Matter.js engine */
|
|
123
|
+
engine: MutableRefObject<Engine | null>;
|
|
124
|
+
/** Emit an event to parent */
|
|
125
|
+
emitEvent: (event: string, data?: unknown) => void;
|
|
126
|
+
/** Get entity state by ID */
|
|
127
|
+
getEntity: (id: string) => EntityState | undefined;
|
|
128
|
+
/** Spawn an entity from prefab */
|
|
129
|
+
spawnEntity: (prefabId: string, x: number, y: number) => void;
|
|
130
|
+
/** Destroy an entity */
|
|
131
|
+
destroyEntity: (id: string) => void;
|
|
132
|
+
/** Respawn player at checkpoint */
|
|
133
|
+
respawnPlayer: () => void;
|
|
134
|
+
/** BPM for beat calculations */
|
|
135
|
+
bpm: number;
|
|
136
|
+
/** Start time for timeline */
|
|
137
|
+
startTime: number;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Scene context
|
|
141
|
+
*/
|
|
142
|
+
export declare const SceneContext: import("react").Context<SceneContextValue | null>;
|
|
143
|
+
/**
|
|
144
|
+
* Hook to access scene context
|
|
145
|
+
*/
|
|
146
|
+
export declare function useScene(): SceneContextValue;
|
|
147
|
+
/**
|
|
148
|
+
* Create initial entity state from entity definition
|
|
149
|
+
*/
|
|
150
|
+
export declare function createEntityState(entity: Entity): EntityState;
|
|
151
|
+
/**
|
|
152
|
+
* Create initial player state
|
|
153
|
+
*/
|
|
154
|
+
export declare function createPlayerState(): PlayerState;
|
|
155
|
+
/**
|
|
156
|
+
* Create initial camera state from config
|
|
157
|
+
*/
|
|
158
|
+
export declare function createCameraState(config?: CameraConfig): CameraState;
|
|
159
|
+
/**
|
|
160
|
+
* Create initial input state
|
|
161
|
+
*/
|
|
162
|
+
export declare function createInputState(): InputState;
|
|
163
|
+
/**
|
|
164
|
+
* Create initial timeline state
|
|
165
|
+
*/
|
|
166
|
+
export declare function createTimelineState(): TimelineState;
|
|
167
|
+
//# sourceMappingURL=SceneContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SceneContext.d.ts","sourceRoot":"","sources":["../../src/scene/SceneContext.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Scene context
|
|
5
|
+
*/
|
|
6
|
+
export const SceneContext = createContext(null);
|
|
7
|
+
/**
|
|
8
|
+
* Hook to access scene context
|
|
9
|
+
*/
|
|
10
|
+
export function useScene() {
|
|
11
|
+
const context = useContext(SceneContext);
|
|
12
|
+
if (!context) {
|
|
13
|
+
throw new Error('useScene must be used within a SceneRenderer');
|
|
14
|
+
}
|
|
15
|
+
return context;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create initial entity state from entity definition
|
|
19
|
+
*/
|
|
20
|
+
export function createEntityState(entity) {
|
|
21
|
+
return {
|
|
22
|
+
entity,
|
|
23
|
+
x: entity.transform.x,
|
|
24
|
+
y: entity.transform.y,
|
|
25
|
+
rotation: (entity.transform.rotation ?? 0) * (Math.PI / 180), // Convert to radians
|
|
26
|
+
scaleX: entity.transform.scaleX ?? 1,
|
|
27
|
+
scaleY: entity.transform.scaleY ?? 1,
|
|
28
|
+
velocityX: 0,
|
|
29
|
+
velocityY: 0,
|
|
30
|
+
visible: entity.render?.visible ?? true,
|
|
31
|
+
fill: entity.render?.fill,
|
|
32
|
+
alpha: entity.render?.alpha ?? 1,
|
|
33
|
+
destroyed: false,
|
|
34
|
+
componentState: {},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create initial player state
|
|
39
|
+
*/
|
|
40
|
+
export function createPlayerState() {
|
|
41
|
+
return {
|
|
42
|
+
grounded: false,
|
|
43
|
+
dead: false,
|
|
44
|
+
jumpCount: 0,
|
|
45
|
+
checkpointX: 0,
|
|
46
|
+
checkpointY: 0,
|
|
47
|
+
touchingOrb: null,
|
|
48
|
+
gravityDir: 1,
|
|
49
|
+
speedMultiplier: 1,
|
|
50
|
+
deaths: 0,
|
|
51
|
+
complete: false,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create initial camera state from config
|
|
56
|
+
*/
|
|
57
|
+
export function createCameraState(config) {
|
|
58
|
+
return {
|
|
59
|
+
x: config?.initialX ?? 0,
|
|
60
|
+
y: config?.initialY ?? 0,
|
|
61
|
+
zoom: config?.zoom ?? 1,
|
|
62
|
+
shakeIntensity: 0,
|
|
63
|
+
shakeTimeRemaining: 0,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create initial input state
|
|
68
|
+
*/
|
|
69
|
+
export function createInputState() {
|
|
70
|
+
return {
|
|
71
|
+
jumpPressed: false,
|
|
72
|
+
touching: false,
|
|
73
|
+
keys: {},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create initial timeline state
|
|
78
|
+
*/
|
|
79
|
+
export function createTimelineState() {
|
|
80
|
+
return {
|
|
81
|
+
elapsedMs: 0,
|
|
82
|
+
currentBeat: 0,
|
|
83
|
+
nextEventIndex: 0,
|
|
84
|
+
activeTweens: [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type SceneRendererProps } from './SceneRenderer';
|
|
3
|
+
/**
|
|
4
|
+
* Props for SceneFromJson
|
|
5
|
+
*/
|
|
6
|
+
export interface SceneFromJsonProps extends Omit<SceneRendererProps, 'spec'> {
|
|
7
|
+
/** The scene spec JSON object (imported from scene.json) */
|
|
8
|
+
json: unknown;
|
|
9
|
+
/** Show validation errors in UI instead of throwing */
|
|
10
|
+
showErrors?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* SceneFromJson - Renders a scene from a JSON object with validation
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* // In your tile page:
|
|
18
|
+
* import { SceneFromJson } from '@thewhateverapp/tile-sdk/scene';
|
|
19
|
+
* import sceneJson from './scene.json';
|
|
20
|
+
*
|
|
21
|
+
* export default function TilePage() {
|
|
22
|
+
* return <SceneFromJson json={sceneJson} />;
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function SceneFromJson({ json, showErrors, onEvent, ...props }: SceneFromJsonProps): React.JSX.Element;
|
|
27
|
+
//# sourceMappingURL=SceneFromJson.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SceneFromJson.d.ts","sourceRoot":"","sources":["../../src/scene/SceneFromJson.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC;IAC1E,4DAA4D;IAC5D,IAAI,EAAE,OAAO,CAAC;IACd,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,UAAiB,EACjB,OAAO,EACP,GAAG,KAAK,EACT,EAAE,kBAAkB,qBAuEpB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { validateScene } from '@thewhateverapp/scene-sdk';
|
|
4
|
+
import { SceneRenderer } from './SceneRenderer';
|
|
5
|
+
/**
|
|
6
|
+
* SceneFromJson - Renders a scene from a JSON object with validation
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* // In your tile page:
|
|
11
|
+
* import { SceneFromJson } from '@thewhateverapp/tile-sdk/scene';
|
|
12
|
+
* import sceneJson from './scene.json';
|
|
13
|
+
*
|
|
14
|
+
* export default function TilePage() {
|
|
15
|
+
* return <SceneFromJson json={sceneJson} />;
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function SceneFromJson({ json, showErrors = true, onEvent, ...props }) {
|
|
20
|
+
// Validate the JSON
|
|
21
|
+
const validationResult = React.useMemo(() => {
|
|
22
|
+
try {
|
|
23
|
+
return validateScene(json);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return {
|
|
27
|
+
valid: false,
|
|
28
|
+
errors: [{ path: 'root', message: String(error), code: 'PARSE_ERROR' }],
|
|
29
|
+
warnings: [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}, [json]);
|
|
33
|
+
// Report validation errors to agent for correction
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
if (!validationResult.valid) {
|
|
36
|
+
const errorMessage = `Scene Validation Errors:\n${validationResult.errors
|
|
37
|
+
.map((e) => `${e.path}: ${e.message}`)
|
|
38
|
+
.join('\n')}`;
|
|
39
|
+
// Report to agent via preview error reporting mechanism
|
|
40
|
+
if (typeof window !== 'undefined' && window.__PREVIEW_REPORT_ERROR__) {
|
|
41
|
+
window.__PREVIEW_REPORT_ERROR__(errorMessage, null, null);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}, [validationResult]);
|
|
45
|
+
// Show errors if validation failed
|
|
46
|
+
if (!validationResult.valid) {
|
|
47
|
+
if (showErrors) {
|
|
48
|
+
return (React.createElement("div", { style: {
|
|
49
|
+
width: '100%',
|
|
50
|
+
height: '100%',
|
|
51
|
+
backgroundColor: '#1a0a0a',
|
|
52
|
+
color: '#ff4444',
|
|
53
|
+
padding: 16,
|
|
54
|
+
fontFamily: 'monospace',
|
|
55
|
+
fontSize: 12,
|
|
56
|
+
overflow: 'auto',
|
|
57
|
+
} },
|
|
58
|
+
React.createElement("div", { style: { fontWeight: 'bold', marginBottom: 8 } }, "Scene Validation Errors:"),
|
|
59
|
+
validationResult.errors.map((error, i) => (React.createElement("div", { key: i, style: { marginBottom: 4 } },
|
|
60
|
+
React.createElement("span", { style: { color: '#ff8888' } },
|
|
61
|
+
error.path,
|
|
62
|
+
":"),
|
|
63
|
+
" ",
|
|
64
|
+
error.message)))));
|
|
65
|
+
}
|
|
66
|
+
// Throw if not showing errors
|
|
67
|
+
throw new Error(`Scene validation failed: ${validationResult.errors.map((e) => e.message).join(', ')}`);
|
|
68
|
+
}
|
|
69
|
+
// Show warnings in console
|
|
70
|
+
if (validationResult.warnings.length > 0) {
|
|
71
|
+
console.warn('Scene validation warnings:', validationResult.warnings);
|
|
72
|
+
}
|
|
73
|
+
return (React.createElement(SceneRenderer, { spec: json, onEvent: onEvent, ...props }));
|
|
74
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SceneSpecV1 } from '@thewhateverapp/scene-sdk';
|
|
3
|
+
/**
|
|
4
|
+
* Props for SceneRenderer
|
|
5
|
+
*/
|
|
6
|
+
export interface SceneRendererProps {
|
|
7
|
+
/** Scene specification to render */
|
|
8
|
+
spec: SceneSpecV1;
|
|
9
|
+
/** Callback for scene events (player.death, level.complete, etc.) */
|
|
10
|
+
onEvent?: (event: string, data?: unknown) => void;
|
|
11
|
+
/** Whether the scene is paused */
|
|
12
|
+
paused?: boolean;
|
|
13
|
+
/** Width override (default: TILE_WIDTH) */
|
|
14
|
+
width?: number;
|
|
15
|
+
/** Height override (default: TILE_HEIGHT) */
|
|
16
|
+
height?: number;
|
|
17
|
+
/** Enable debug rendering */
|
|
18
|
+
debug?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* SceneRenderer - Renders a SceneSpecV1 with physics, components, and timeline
|
|
22
|
+
*/
|
|
23
|
+
export declare function SceneRenderer({ spec: inputSpec, onEvent, paused, width, height, debug, }: SceneRendererProps): React.JSX.Element;
|
|
24
|
+
import { useScene } from './SceneContext';
|
|
25
|
+
export { useScene };
|
|
26
|
+
//# sourceMappingURL=SceneRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SceneRenderer.d.ts","sourceRoot":"","sources":["../../src/scene/SceneRenderer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAkD,MAAM,OAAO,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,2BAA2B,CAAC;AAqBrE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,oCAAoC;IACpC,IAAI,EAAE,WAAW,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,kCAAkC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EAAE,SAAS,EACf,OAAO,EACP,MAAc,EACd,KAAkB,EAClB,MAAoB,EACpB,KAAa,GACd,EAAE,kBAAkB,qBA8BpB;AAuOD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { useEffect, useRef, useCallback, useMemo } from 'react';
|
|
3
|
+
import { compileScene } from '@thewhateverapp/scene-sdk';
|
|
4
|
+
import { PixiGame, useGameLoop, Container } from '../pixi';
|
|
5
|
+
import { SceneContext, createEntityState, createPlayerState, createCameraState, createInputState, createTimelineState, } from './SceneContext';
|
|
6
|
+
import { EntityRenderer } from './entities/EntityRenderer';
|
|
7
|
+
import { usePhysicsEngine } from './physics/PhysicsEngine';
|
|
8
|
+
import { useInputManager } from './input/InputManager';
|
|
9
|
+
import { useComponentRunner } from './components/ComponentRunner';
|
|
10
|
+
import { useTimelineExecutor } from './timeline/TimelineExecutor';
|
|
11
|
+
import { useCameraController } from './camera/CameraController';
|
|
12
|
+
import { TILE_WIDTH, TILE_HEIGHT } from '../react/PixiGame';
|
|
13
|
+
/**
|
|
14
|
+
* SceneRenderer - Renders a SceneSpecV1 with physics, components, and timeline
|
|
15
|
+
*/
|
|
16
|
+
export function SceneRenderer({ spec: inputSpec, onEvent, paused = false, width = TILE_WIDTH, height = TILE_HEIGHT, debug = false, }) {
|
|
17
|
+
// Compile patterns once on mount
|
|
18
|
+
const compiledSpec = useMemo(() => compileScene(inputSpec), [inputSpec]);
|
|
19
|
+
// Get background color from spec
|
|
20
|
+
const backgroundColor = useMemo(() => {
|
|
21
|
+
const bg = compiledSpec.style?.background?.color;
|
|
22
|
+
if (bg) {
|
|
23
|
+
return parseInt(bg.replace('#', ''), 16);
|
|
24
|
+
}
|
|
25
|
+
return 0x0a0a1a; // Default dark background
|
|
26
|
+
}, [compiledSpec]);
|
|
27
|
+
return (React.createElement(PixiGame, { width: width, height: height, background: backgroundColor, paused: paused },
|
|
28
|
+
React.createElement(SceneContent, { spec: compiledSpec, onEvent: onEvent, paused: paused, width: width, height: height, debug: debug })));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Inner component that runs inside PixiGame context
|
|
32
|
+
*/
|
|
33
|
+
function SceneContent({ spec, onEvent, paused, width, height, debug, }) {
|
|
34
|
+
// Initialize refs for all state
|
|
35
|
+
const entitiesRef = useRef(new Map());
|
|
36
|
+
const playerRef = useRef(createPlayerState());
|
|
37
|
+
const cameraRef = useRef(createCameraState(spec.camera));
|
|
38
|
+
const inputRef = useRef(createInputState());
|
|
39
|
+
const timelineRef = useRef(createTimelineState());
|
|
40
|
+
const engineRef = useRef(null);
|
|
41
|
+
const startTimeRef = useRef(Date.now());
|
|
42
|
+
// Get layers from spec or use defaults
|
|
43
|
+
const layers = useMemo(() => {
|
|
44
|
+
if (spec.layers && spec.layers.length > 0) {
|
|
45
|
+
return spec.layers;
|
|
46
|
+
}
|
|
47
|
+
// Default dash layers
|
|
48
|
+
return ['bg', 'deco', 'solids', 'hazards', 'orbs', 'player', 'fx', 'ui'];
|
|
49
|
+
}, [spec.layers]);
|
|
50
|
+
// Get BPM from spec
|
|
51
|
+
const bpm = spec.meta?.bpm ?? 120;
|
|
52
|
+
// Event emitter callback
|
|
53
|
+
const emitEvent = useCallback((event, data) => {
|
|
54
|
+
onEvent?.(event, data);
|
|
55
|
+
}, [onEvent]);
|
|
56
|
+
// Get entity by ID
|
|
57
|
+
const getEntity = useCallback((id) => {
|
|
58
|
+
return entitiesRef.current.get(id);
|
|
59
|
+
}, []);
|
|
60
|
+
// Spawn entity from prefab
|
|
61
|
+
const spawnEntity = useCallback((prefabId, x, y) => {
|
|
62
|
+
const prefab = spec.prefabs?.[prefabId];
|
|
63
|
+
if (!prefab) {
|
|
64
|
+
console.warn(`Prefab not found: ${prefabId}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const id = `${prefabId}_${Date.now()}`;
|
|
68
|
+
const entity = {
|
|
69
|
+
id,
|
|
70
|
+
kind: prefab.kind ?? 'rect',
|
|
71
|
+
layer: prefab.layer ?? 'fx',
|
|
72
|
+
transform: {
|
|
73
|
+
x,
|
|
74
|
+
y,
|
|
75
|
+
...prefab.transform,
|
|
76
|
+
},
|
|
77
|
+
geom: prefab.geom ?? { w: 20, h: 20 },
|
|
78
|
+
render: prefab.render,
|
|
79
|
+
body: prefab.body,
|
|
80
|
+
tags: prefab.tags,
|
|
81
|
+
components: prefab.components,
|
|
82
|
+
};
|
|
83
|
+
entitiesRef.current.set(id, createEntityState(entity));
|
|
84
|
+
}, [spec.prefabs]);
|
|
85
|
+
// Destroy entity
|
|
86
|
+
const destroyEntity = useCallback((id) => {
|
|
87
|
+
const entity = entitiesRef.current.get(id);
|
|
88
|
+
if (entity) {
|
|
89
|
+
entity.destroyed = true;
|
|
90
|
+
// Body cleanup happens in physics engine
|
|
91
|
+
}
|
|
92
|
+
}, []);
|
|
93
|
+
// Respawn player at checkpoint
|
|
94
|
+
const respawnPlayer = useCallback(() => {
|
|
95
|
+
const player = playerRef.current;
|
|
96
|
+
player.dead = false;
|
|
97
|
+
player.jumpCount = 0;
|
|
98
|
+
player.gravityDir = 1;
|
|
99
|
+
// Find player entity and reset position
|
|
100
|
+
for (const [, state] of entitiesRef.current) {
|
|
101
|
+
if (state.entity.tags?.includes('player')) {
|
|
102
|
+
state.x = player.checkpointX || 128;
|
|
103
|
+
state.y = player.checkpointY || (height - 100);
|
|
104
|
+
state.velocityX = 0;
|
|
105
|
+
state.velocityY = 0;
|
|
106
|
+
state.rotation = 0;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
emitEvent('player.respawn', { x: player.checkpointX, y: player.checkpointY });
|
|
111
|
+
}, [height, emitEvent]);
|
|
112
|
+
// Build context value
|
|
113
|
+
const contextValue = useMemo(() => ({
|
|
114
|
+
spec,
|
|
115
|
+
entities: entitiesRef,
|
|
116
|
+
layers,
|
|
117
|
+
player: playerRef,
|
|
118
|
+
camera: cameraRef,
|
|
119
|
+
input: inputRef,
|
|
120
|
+
timeline: timelineRef,
|
|
121
|
+
engine: engineRef,
|
|
122
|
+
emitEvent,
|
|
123
|
+
getEntity,
|
|
124
|
+
spawnEntity,
|
|
125
|
+
destroyEntity,
|
|
126
|
+
respawnPlayer,
|
|
127
|
+
bpm,
|
|
128
|
+
startTime: startTimeRef.current,
|
|
129
|
+
}), [spec, layers, bpm, emitEvent, getEntity, spawnEntity, destroyEntity, respawnPlayer]);
|
|
130
|
+
// Initialize entities from spec
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const entityMap = new Map();
|
|
133
|
+
for (const entity of spec.entities) {
|
|
134
|
+
entityMap.set(entity.id, createEntityState(entity));
|
|
135
|
+
}
|
|
136
|
+
entitiesRef.current = entityMap;
|
|
137
|
+
// Find player and set initial checkpoint
|
|
138
|
+
for (const entity of spec.entities) {
|
|
139
|
+
if (entity.tags?.includes('player')) {
|
|
140
|
+
playerRef.current.checkpointX = entity.transform.x;
|
|
141
|
+
playerRef.current.checkpointY = entity.transform.y;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Reset start time
|
|
146
|
+
startTimeRef.current = Date.now();
|
|
147
|
+
timelineRef.current = createTimelineState();
|
|
148
|
+
}, [spec]);
|
|
149
|
+
// Initialize physics engine
|
|
150
|
+
usePhysicsEngine(contextValue, width, height);
|
|
151
|
+
// Initialize input handling
|
|
152
|
+
useInputManager(contextValue);
|
|
153
|
+
// Run component logic
|
|
154
|
+
useComponentRunner(contextValue);
|
|
155
|
+
// Run timeline
|
|
156
|
+
useTimelineExecutor(contextValue);
|
|
157
|
+
// Update camera
|
|
158
|
+
useCameraController(contextValue, width, height);
|
|
159
|
+
// Main game loop
|
|
160
|
+
useGameLoop((delta) => {
|
|
161
|
+
if (paused)
|
|
162
|
+
return;
|
|
163
|
+
// Update timeline elapsed time
|
|
164
|
+
const now = Date.now();
|
|
165
|
+
timelineRef.current.elapsedMs = now - startTimeRef.current;
|
|
166
|
+
timelineRef.current.currentBeat = (timelineRef.current.elapsedMs / 1000) * (bpm / 60);
|
|
167
|
+
}, !paused);
|
|
168
|
+
// Render entities grouped by layer
|
|
169
|
+
const renderLayers = useMemo(() => {
|
|
170
|
+
return layers.map((layer, layerIndex) => (React.createElement(Container, { key: layer, zIndex: layerIndex },
|
|
171
|
+
React.createElement(LayerRenderer, { layer: layer, debug: debug }))));
|
|
172
|
+
}, [layers, debug]);
|
|
173
|
+
return (React.createElement(SceneContext.Provider, { value: contextValue },
|
|
174
|
+
React.createElement(Container, { x: -cameraRef.current.x + width / 2, y: -cameraRef.current.y + height / 2, scale: cameraRef.current.zoom }, renderLayers)));
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Renders all entities in a layer
|
|
178
|
+
*/
|
|
179
|
+
function LayerRenderer({ layer, debug }) {
|
|
180
|
+
const { entities } = useScene();
|
|
181
|
+
// Filter and sort entities for this layer
|
|
182
|
+
const layerEntities = useMemo(() => {
|
|
183
|
+
const result = [];
|
|
184
|
+
for (const [, state] of entities.current) {
|
|
185
|
+
if (state.entity.layer === layer && !state.destroyed && state.visible) {
|
|
186
|
+
result.push(state);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Sort by zIndex within layer
|
|
190
|
+
result.sort((a, b) => {
|
|
191
|
+
const zA = a.entity.render?.zIndex ?? 0;
|
|
192
|
+
const zB = b.entity.render?.zIndex ?? 0;
|
|
193
|
+
return zA - zB;
|
|
194
|
+
});
|
|
195
|
+
return result;
|
|
196
|
+
}, [entities, layer]);
|
|
197
|
+
return (React.createElement(React.Fragment, null, layerEntities.map((state) => (React.createElement(EntityRenderer, { key: state.entity.id, state: state, debug: debug })))));
|
|
198
|
+
}
|
|
199
|
+
// Re-export useScene for convenience
|
|
200
|
+
import { useScene } from './SceneContext';
|
|
201
|
+
export { useScene };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CameraController.d.ts","sourceRoot":"","sources":["../../../src/scene/camera/CameraController.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,QAsFf"}
|