@thewhateverapp/tile-sdk 0.15.3 → 0.15.4

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.
Files changed (51) hide show
  1. package/dist/excalibur/index.d.ts +48 -0
  2. package/dist/excalibur/index.d.ts.map +1 -0
  3. package/dist/excalibur/index.js +51 -0
  4. package/dist/react/ExcaliburGame.d.ts +109 -0
  5. package/dist/react/ExcaliburGame.d.ts.map +1 -0
  6. package/dist/react/ExcaliburGame.js +215 -0
  7. package/dist/react/index.js +3 -3
  8. package/dist/scene/index.d.ts +3 -41
  9. package/dist/scene/index.d.ts.map +1 -1
  10. package/dist/scene/index.js +1 -49
  11. package/dist/spec/schema.d.ts +12 -12
  12. package/package.json +7 -7
  13. package/dist/pixi/index.d.ts +0 -43
  14. package/dist/pixi/index.d.ts.map +0 -1
  15. package/dist/pixi/index.js +0 -46
  16. package/dist/react/PixiGame.d.ts +0 -138
  17. package/dist/react/PixiGame.d.ts.map +0 -1
  18. package/dist/react/PixiGame.js +0 -237
  19. package/dist/scene/SceneContext.d.ts +0 -173
  20. package/dist/scene/SceneContext.d.ts.map +0 -1
  21. package/dist/scene/SceneContext.js +0 -89
  22. package/dist/scene/SceneFromJson.d.ts +0 -34
  23. package/dist/scene/SceneFromJson.d.ts.map +0 -1
  24. package/dist/scene/SceneFromJson.js +0 -97
  25. package/dist/scene/SceneRenderer.d.ts +0 -29
  26. package/dist/scene/SceneRenderer.d.ts.map +0 -1
  27. package/dist/scene/SceneRenderer.js +0 -312
  28. package/dist/scene/camera/CameraController.d.ts +0 -6
  29. package/dist/scene/camera/CameraController.d.ts.map +0 -1
  30. package/dist/scene/camera/CameraController.js +0 -90
  31. package/dist/scene/components/ComponentRunner.d.ts +0 -22
  32. package/dist/scene/components/ComponentRunner.d.ts.map +0 -1
  33. package/dist/scene/components/ComponentRunner.js +0 -210
  34. package/dist/scene/effects/GlowFilter.d.ts +0 -38
  35. package/dist/scene/effects/GlowFilter.d.ts.map +0 -1
  36. package/dist/scene/effects/GlowFilter.js +0 -40
  37. package/dist/scene/effects/ParticleSystem.d.ts +0 -52
  38. package/dist/scene/effects/ParticleSystem.d.ts.map +0 -1
  39. package/dist/scene/effects/ParticleSystem.js +0 -107
  40. package/dist/scene/entities/EntityGraphics.d.ts +0 -26
  41. package/dist/scene/entities/EntityGraphics.d.ts.map +0 -1
  42. package/dist/scene/entities/EntityGraphics.js +0 -226
  43. package/dist/scene/input/InputManager.d.ts +0 -18
  44. package/dist/scene/input/InputManager.d.ts.map +0 -1
  45. package/dist/scene/input/InputManager.js +0 -86
  46. package/dist/scene/physics/PhysicsEngine.d.ts +0 -15
  47. package/dist/scene/physics/PhysicsEngine.d.ts.map +0 -1
  48. package/dist/scene/physics/PhysicsEngine.js +0 -260
  49. package/dist/scene/timeline/TimelineExecutor.d.ts +0 -6
  50. package/dist/scene/timeline/TimelineExecutor.d.ts.map +0 -1
  51. package/dist/scene/timeline/TimelineExecutor.js +0 -241
@@ -1,89 +0,0 @@
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
- started: false,
43
- grounded: false,
44
- dead: false,
45
- jumpCount: 0,
46
- checkpointX: 0,
47
- checkpointY: 0,
48
- touchingOrb: null,
49
- gravityDir: 1,
50
- speedMultiplier: 1,
51
- deaths: 0,
52
- complete: false,
53
- invincible: false,
54
- invincibilityTimeRemaining: 0,
55
- };
56
- }
57
- /**
58
- * Create initial camera state from config
59
- */
60
- export function createCameraState(config) {
61
- return {
62
- x: config?.initialX ?? 0,
63
- y: config?.initialY ?? 0,
64
- zoom: config?.zoom ?? 1,
65
- shakeIntensity: 0,
66
- shakeTimeRemaining: 0,
67
- };
68
- }
69
- /**
70
- * Create initial input state
71
- */
72
- export function createInputState() {
73
- return {
74
- jumpPressed: false,
75
- touching: false,
76
- keys: {},
77
- };
78
- }
79
- /**
80
- * Create initial timeline state
81
- */
82
- export function createTimelineState() {
83
- return {
84
- elapsedMs: 0,
85
- currentBeat: 0,
86
- nextEventIndex: 0,
87
- activeTweens: [],
88
- };
89
- }
@@ -1,34 +0,0 @@
1
- import React from 'react';
2
- import { type SceneRendererProps } from './SceneRenderer.js';
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
- * Container sizing mode:
13
- * - 'tile': Fills parent container (w-full h-full) - default
14
- * - 'page': Fills viewport (w-full h-screen)
15
- * - 'none': No container wrapper (you manage sizing)
16
- */
17
- container?: 'tile' | 'page' | 'none';
18
- }
19
- /**
20
- * SceneFromJson - Renders a scene from a JSON object with validation
21
- *
22
- * @example
23
- * ```tsx
24
- * // In your tile page:
25
- * import { SceneFromJson } from '@thewhateverapp/tile-sdk/scene';
26
- * import sceneJson from './scene.json';
27
- *
28
- * export default function TilePage() {
29
- * return <SceneFromJson json={sceneJson} />;
30
- * }
31
- * ```
32
- */
33
- export declare function SceneFromJson({ json, showErrors, onEvent, container, ...props }: SceneFromJsonProps): React.JSX.Element;
34
- //# sourceMappingURL=SceneFromJson.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SceneFromJson.d.ts","sourceRoot":"","sources":["../../src/scene/SceneFromJson.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAG/D,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE5E;;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;IACrB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CACtC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,UAAiB,EACjB,OAAO,EACP,SAAkB,EAClB,GAAG,KAAK,EACT,EAAE,kBAAkB,qBAoGpB"}
@@ -1,97 +0,0 @@
1
- 'use client';
2
- import React, { useEffect, useMemo, useCallback } from 'react';
3
- import { validateScene } from '@thewhateverapp/scene-sdk';
4
- import { SceneRenderer } from './SceneRenderer.js';
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, container = 'tile', ...props }) {
20
- // Wrap onEvent to forward to parent window via postMessage
21
- const wrappedOnEvent = useCallback((event, data) => {
22
- // Call user's onEvent handler
23
- onEvent?.(event, data);
24
- // Forward to parent window for tile containers to handle
25
- if (typeof window !== 'undefined' && window.parent !== window) {
26
- window.parent.postMessage({
27
- type: 'tile:event',
28
- payload: { event, data },
29
- timestamp: Date.now(),
30
- }, '*' // Allow any parent origin (tile containers validate origin)
31
- );
32
- }
33
- }, [onEvent]);
34
- // Container styles based on mode
35
- const containerStyle = container === 'none'
36
- ? undefined
37
- : {
38
- width: '100%',
39
- height: container === 'page' ? '100vh' : '100%',
40
- };
41
- // Validate the JSON
42
- const validationResult = useMemo(() => {
43
- try {
44
- return validateScene(json);
45
- }
46
- catch (error) {
47
- return {
48
- valid: false,
49
- errors: [{ path: 'root', message: String(error), code: 'PARSE_ERROR' }],
50
- warnings: [],
51
- };
52
- }
53
- }, [json]);
54
- // Report validation errors to agent for correction
55
- useEffect(() => {
56
- if (!validationResult.valid) {
57
- const errorMessage = `Scene Validation Errors:\n${validationResult.errors
58
- .map((e) => `${e.path}: ${e.message}`)
59
- .join('\n')}`;
60
- // Report to agent via preview error reporting mechanism
61
- if (typeof window !== 'undefined' && window.__PREVIEW_REPORT_ERROR__) {
62
- window.__PREVIEW_REPORT_ERROR__(errorMessage, null, null);
63
- }
64
- }
65
- }, [validationResult]);
66
- // Show errors if validation failed
67
- if (!validationResult.valid) {
68
- if (showErrors) {
69
- const errorContent = (React.createElement("div", { style: {
70
- width: '100%',
71
- height: '100%',
72
- backgroundColor: '#1a0a0a',
73
- color: '#ff4444',
74
- padding: 16,
75
- fontFamily: 'monospace',
76
- fontSize: 12,
77
- overflow: 'auto',
78
- } },
79
- React.createElement("div", { style: { fontWeight: 'bold', marginBottom: 8 } }, "Scene Validation Errors:"),
80
- validationResult.errors.map((error, i) => (React.createElement("div", { key: i, style: { marginBottom: 4 } },
81
- React.createElement("span", { style: { color: '#ff8888' } },
82
- error.path,
83
- ":"),
84
- " ",
85
- error.message)))));
86
- return containerStyle ? React.createElement("div", { style: containerStyle }, errorContent) : errorContent;
87
- }
88
- // Throw if not showing errors
89
- throw new Error(`Scene validation failed: ${validationResult.errors.map((e) => e.message).join(', ')}`);
90
- }
91
- // Show warnings in console
92
- if (validationResult.warnings.length > 0) {
93
- console.warn('Scene validation warnings:', validationResult.warnings);
94
- }
95
- const sceneContent = (React.createElement(SceneRenderer, { spec: json, onEvent: wrappedOnEvent, ...props }));
96
- return containerStyle ? React.createElement("div", { style: containerStyle }, sceneContent) : sceneContent;
97
- }
@@ -1,29 +0,0 @@
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
- /** Fixed width (if not set, fills container responsively) */
14
- width?: number;
15
- /** Fixed height (if not set, fills container responsively) */
16
- height?: number;
17
- /** Enable debug rendering */
18
- debug?: boolean;
19
- }
20
- /**
21
- * SceneRenderer - Renders a SceneSpecV1 with physics, components, and timeline
22
- *
23
- * By default, fills its parent container responsively. Pass explicit width/height
24
- * to override with fixed dimensions.
25
- */
26
- export declare function SceneRenderer({ spec: inputSpec, onEvent, paused, width: fixedWidth, height: fixedHeight, debug, }: SceneRendererProps): React.JSX.Element;
27
- import { useScene } from './SceneContext.js';
28
- export { useScene };
29
- //# sourceMappingURL=SceneRenderer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SceneRenderer.d.ts","sourceRoot":"","sources":["../../src/scene/SceneRenderer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAGjF,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,2BAA2B,CAAC;AAwDrE;;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,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EAAE,SAAS,EACf,OAAO,EACP,MAAc,EACd,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,WAAW,EACnB,KAAa,GACd,EAAE,kBAAkB,qBA0CpB;AAgTD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -1,312 +0,0 @@
1
- 'use client';
2
- import React, { useEffect, useRef, useCallback, useMemo, useState } from 'react';
3
- import * as PIXI from 'pixi.js';
4
- import Matter from 'matter-js';
5
- import { compileScene } from '@thewhateverapp/scene-sdk';
6
- import { PixiGame, usePixiApp, useGameLoop, TILE_WIDTH, TILE_HEIGHT } from '../pixi/index.js';
7
- import { SceneContext, createEntityState, createPlayerState, createCameraState, createInputState, createTimelineState, } from './SceneContext.js';
8
- import { usePhysicsEngine } from './physics/PhysicsEngine.js';
9
- import { useInputManager } from './input/InputManager.js';
10
- import { useComponentRunner } from './components/ComponentRunner.js';
11
- import { useTimelineExecutor } from './timeline/TimelineExecutor.js';
12
- import { useCameraController } from './camera/CameraController.js';
13
- import { createEntityGraphics, updateEntityGraphics, } from './entities/EntityGraphics.js';
14
- const { Body } = Matter;
15
- /**
16
- * Hook to track container size using ResizeObserver
17
- */
18
- function useContainerSize(containerRef) {
19
- const [size, setSize] = useState({ width: TILE_WIDTH, height: TILE_HEIGHT });
20
- useEffect(() => {
21
- const container = containerRef.current;
22
- if (!container)
23
- return;
24
- const updateSize = () => {
25
- const rect = container.getBoundingClientRect();
26
- if (rect.width > 0 && rect.height > 0) {
27
- setSize({ width: rect.width, height: rect.height });
28
- }
29
- };
30
- // Initial size
31
- updateSize();
32
- // Watch for resize
33
- const observer = new ResizeObserver(updateSize);
34
- observer.observe(container);
35
- return () => observer.disconnect();
36
- }, [containerRef]);
37
- return size;
38
- }
39
- /**
40
- * SceneRenderer - Renders a SceneSpecV1 with physics, components, and timeline
41
- *
42
- * By default, fills its parent container responsively. Pass explicit width/height
43
- * to override with fixed dimensions.
44
- */
45
- export function SceneRenderer({ spec: inputSpec, onEvent, paused = false, width: fixedWidth, height: fixedHeight, debug = false, }) {
46
- const containerRef = useRef(null);
47
- const autoSize = useContainerSize(containerRef);
48
- // Use fixed dimensions if provided, otherwise auto-size to container
49
- const width = fixedWidth ?? autoSize.width;
50
- const height = fixedHeight ?? autoSize.height;
51
- // Compile patterns once on mount
52
- const compiledSpec = useMemo(() => compileScene(inputSpec), [inputSpec]);
53
- // Get background color from spec
54
- const backgroundColor = useMemo(() => {
55
- const bg = compiledSpec.style?.background?.color;
56
- if (bg) {
57
- return parseInt(bg.replace('#', ''), 16);
58
- }
59
- return 0x0a0a1a; // Default dark background
60
- }, [compiledSpec]);
61
- return (React.createElement("div", { ref: containerRef, style: { width: '100%', height: '100%' } },
62
- React.createElement(PixiGame, { width: width, height: height, background: backgroundColor, paused: paused },
63
- React.createElement(SceneContent, { spec: compiledSpec, onEvent: onEvent, paused: paused, width: width, height: height, debug: debug }))));
64
- }
65
- /**
66
- * Inner component that runs inside PixiGame context
67
- */
68
- function SceneContent({ spec, onEvent, paused, width, height, debug, }) {
69
- const app = usePixiApp();
70
- // Pixi containers
71
- const worldContainerRef = useRef(null);
72
- const layerContainersRef = useRef(new Map());
73
- const entityGraphicsRef = useRef(new Map());
74
- // Initialize refs for all state
75
- const entitiesRef = useRef(new Map());
76
- const playerRef = useRef(createPlayerState());
77
- const cameraRef = useRef(createCameraState(spec.camera));
78
- const inputRef = useRef(createInputState());
79
- const timelineRef = useRef(createTimelineState());
80
- const engineRef = useRef(null);
81
- const startTimeRef = useRef(Date.now());
82
- // Get layers from spec or use defaults
83
- const layers = useMemo(() => {
84
- if (spec.layers && spec.layers.length > 0) {
85
- return spec.layers;
86
- }
87
- // Default dash layers
88
- return ['bg', 'deco', 'solids', 'hazards', 'orbs', 'player', 'fx', 'ui'];
89
- }, [spec.layers]);
90
- // Get BPM from spec
91
- const bpm = spec.meta?.bpm ?? 120;
92
- // Event emitter callback
93
- const emitEvent = useCallback((event, data) => {
94
- onEvent?.(event, data);
95
- }, [onEvent]);
96
- // Get entity by ID
97
- const getEntity = useCallback((id) => {
98
- return entitiesRef.current.get(id);
99
- }, []);
100
- // Spawn entity from prefab
101
- const spawnEntity = useCallback((prefabId, x, y) => {
102
- const prefab = spec.prefabs?.[prefabId];
103
- if (!prefab) {
104
- console.warn(`Prefab not found: ${prefabId}`);
105
- return;
106
- }
107
- const id = `${prefabId}_${Date.now()}`;
108
- const entity = {
109
- id,
110
- kind: prefab.kind ?? 'rect',
111
- layer: prefab.layer ?? 'fx',
112
- transform: {
113
- x,
114
- y,
115
- ...prefab.transform,
116
- },
117
- geom: prefab.geom ?? { w: 20, h: 20 },
118
- render: prefab.render,
119
- body: prefab.body,
120
- tags: prefab.tags,
121
- components: prefab.components,
122
- };
123
- const state = createEntityState(entity);
124
- entitiesRef.current.set(id, state);
125
- // Create graphics for the new entity
126
- const layerContainer = layerContainersRef.current.get(entity.layer);
127
- if (layerContainer) {
128
- const graphics = createEntityGraphics(state, debug);
129
- entityGraphicsRef.current.set(id, graphics);
130
- layerContainer.addChild(graphics.container);
131
- }
132
- }, [spec.prefabs, debug]);
133
- // Destroy entity
134
- const destroyEntity = useCallback((id) => {
135
- const entity = entitiesRef.current.get(id);
136
- if (entity) {
137
- entity.destroyed = true;
138
- // Body cleanup happens in physics engine
139
- // Graphics cleanup happens in render loop
140
- }
141
- }, []);
142
- // Respawn player at checkpoint
143
- const respawnPlayer = useCallback(() => {
144
- const player = playerRef.current;
145
- player.dead = false;
146
- player.jumpCount = 0;
147
- player.gravityDir = 1;
148
- // Grant 1 second of invincibility to prevent instant re-death
149
- player.invincible = true;
150
- player.invincibilityTimeRemaining = 1000;
151
- // Find player entity and reset position
152
- for (const [, state] of entitiesRef.current) {
153
- if (state.entity.tags?.includes('player')) {
154
- const respawnX = player.checkpointX ?? state.entity.transform.x;
155
- const respawnY = player.checkpointY ?? state.entity.transform.y;
156
- state.x = respawnX;
157
- state.y = respawnY;
158
- state.velocityX = 0;
159
- state.velocityY = 0;
160
- state.rotation = 0;
161
- // Update physics body if it exists
162
- if (state.body) {
163
- Body.setPosition(state.body, { x: respawnX, y: respawnY });
164
- Body.setVelocity(state.body, { x: 0, y: 0 });
165
- Body.setAngle(state.body, 0);
166
- }
167
- break;
168
- }
169
- }
170
- emitEvent('player.respawn', { x: player.checkpointX, y: player.checkpointY });
171
- }, [emitEvent]);
172
- // Build context value
173
- const contextValue = useMemo(() => ({
174
- spec,
175
- entities: entitiesRef,
176
- layers,
177
- player: playerRef,
178
- camera: cameraRef,
179
- input: inputRef,
180
- timeline: timelineRef,
181
- engine: engineRef,
182
- emitEvent,
183
- getEntity,
184
- spawnEntity,
185
- destroyEntity,
186
- respawnPlayer,
187
- bpm,
188
- startTime: startTimeRef.current,
189
- }), [spec, layers, bpm, emitEvent, getEntity, spawnEntity, destroyEntity, respawnPlayer]);
190
- // Initialize pixi containers
191
- useEffect(() => {
192
- if (!app)
193
- return;
194
- // Create world container
195
- const worldContainer = new PIXI.Container();
196
- worldContainer.sortableChildren = true;
197
- app.stage.addChild(worldContainer);
198
- worldContainerRef.current = worldContainer;
199
- // Create layer containers
200
- const layerContainers = new Map();
201
- layers.forEach((layer, index) => {
202
- const container = new PIXI.Container();
203
- container.zIndex = index;
204
- worldContainer.addChild(container);
205
- layerContainers.set(layer, container);
206
- });
207
- layerContainersRef.current = layerContainers;
208
- return () => {
209
- // Cleanup
210
- entityGraphicsRef.current.forEach((graphics) => {
211
- graphics.container.destroy({ children: true });
212
- });
213
- entityGraphicsRef.current.clear();
214
- layerContainersRef.current.clear();
215
- worldContainer.destroy({ children: true });
216
- worldContainerRef.current = null;
217
- };
218
- }, [app, layers]);
219
- // Initialize entities from spec
220
- useEffect(() => {
221
- const entityMap = new Map();
222
- for (const entity of spec.entities) {
223
- entityMap.set(entity.id, createEntityState(entity));
224
- }
225
- entitiesRef.current = entityMap;
226
- // Find player and set initial checkpoint
227
- for (const entity of spec.entities) {
228
- if (entity.tags?.includes('player')) {
229
- playerRef.current.checkpointX = entity.transform.x;
230
- playerRef.current.checkpointY = entity.transform.y;
231
- break;
232
- }
233
- }
234
- // Reset start time
235
- startTimeRef.current = Date.now();
236
- timelineRef.current = createTimelineState();
237
- // Create graphics for all entities
238
- entityGraphicsRef.current.forEach((graphics) => {
239
- graphics.container.destroy({ children: true });
240
- });
241
- entityGraphicsRef.current.clear();
242
- for (const [id, state] of entityMap) {
243
- const layerContainer = layerContainersRef.current.get(state.entity.layer);
244
- if (layerContainer) {
245
- const graphics = createEntityGraphics(state, debug);
246
- entityGraphicsRef.current.set(id, graphics);
247
- layerContainer.addChild(graphics.container);
248
- }
249
- }
250
- }, [spec, debug]);
251
- // Initialize physics engine
252
- usePhysicsEngine(contextValue, width, height);
253
- // Initialize input handling
254
- useInputManager(contextValue);
255
- // Run component logic
256
- useComponentRunner(contextValue);
257
- // Run timeline
258
- useTimelineExecutor(contextValue);
259
- // Update camera
260
- useCameraController(contextValue, width, height);
261
- // Main game loop - update graphics positions
262
- useGameLoop((delta) => {
263
- if (paused)
264
- return;
265
- const playerState = playerRef.current;
266
- // Update timeline elapsed time (only when game has started)
267
- if (playerState.started) {
268
- // Adjust start time on first frame after starting
269
- if (timelineRef.current.elapsedMs === 0) {
270
- startTimeRef.current = Date.now();
271
- }
272
- const now = Date.now();
273
- timelineRef.current.elapsedMs = now - startTimeRef.current;
274
- timelineRef.current.currentBeat = (timelineRef.current.elapsedMs / 1000) * (bpm / 60);
275
- }
276
- // Update camera position
277
- if (worldContainerRef.current) {
278
- const camera = cameraRef.current;
279
- worldContainerRef.current.x = -camera.x + width / 2;
280
- worldContainerRef.current.y = -camera.y + height / 2;
281
- worldContainerRef.current.scale.set(camera.zoom);
282
- }
283
- // Update entity graphics
284
- for (const [id, state] of entitiesRef.current) {
285
- const graphics = entityGraphicsRef.current.get(id);
286
- if (!graphics)
287
- continue;
288
- if (state.destroyed || !state.visible) {
289
- // Remove destroyed/hidden entities
290
- if (graphics.container.parent) {
291
- graphics.container.parent.removeChild(graphics.container);
292
- }
293
- if (state.destroyed) {
294
- graphics.container.destroy({ children: true });
295
- entityGraphicsRef.current.delete(id);
296
- entitiesRef.current.delete(id);
297
- }
298
- else {
299
- graphics.container.visible = false;
300
- }
301
- continue;
302
- }
303
- graphics.container.visible = true;
304
- updateEntityGraphics(graphics, state);
305
- }
306
- }, !paused);
307
- // Provide context for hooks that need it
308
- return (React.createElement(SceneContext.Provider, { value: contextValue }, null));
309
- }
310
- // Re-export useScene for convenience
311
- import { useScene } from './SceneContext.js';
312
- export { useScene };
@@ -1,6 +0,0 @@
1
- import type { SceneContextValue } from '../SceneContext.js';
2
- /**
3
- * Hook to control the camera
4
- */
5
- export declare function useCameraController(context: SceneContextValue, width: number, height: number): void;
6
- //# sourceMappingURL=CameraController.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CameraController.d.ts","sourceRoot":"","sources":["../../../src/scene/camera/CameraController.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,QA6Ff"}
@@ -1,90 +0,0 @@
1
- 'use client';
2
- import { useGameLoop } from '../../pixi/index.js';
3
- /**
4
- * Hook to control the camera
5
- */
6
- export function useCameraController(context, width, height) {
7
- const { spec, camera, entities, player, timeline } = context;
8
- const cameraConfig = spec.camera;
9
- useGameLoop((delta) => {
10
- const cameraState = camera.current;
11
- const playerState = player.current;
12
- const elapsedMs = timeline.current.elapsedMs;
13
- // Get camera mode
14
- const mode = cameraConfig?.mode ?? 'static';
15
- switch (mode) {
16
- case 'static': {
17
- // Camera stays at initial position
18
- cameraState.x = cameraConfig?.initialX ?? width / 2;
19
- cameraState.y = cameraConfig?.initialY ?? height / 2;
20
- break;
21
- }
22
- case 'scroll': {
23
- // Auto-scroll camera (only when game has started)
24
- if (!playerState.started) {
25
- // Initialize camera position when not started
26
- cameraState.x = cameraConfig?.initialX ?? width / 2;
27
- cameraState.y = cameraConfig?.initialY ?? height / 2;
28
- break;
29
- }
30
- const scrollSpeed = (cameraConfig?.scrollSpeed ?? 320) * playerState.speedMultiplier;
31
- const direction = cameraConfig?.scrollDirection ?? 'right';
32
- switch (direction) {
33
- case 'right':
34
- cameraState.x += scrollSpeed * delta * 0.016;
35
- break;
36
- case 'left':
37
- cameraState.x -= scrollSpeed * delta * 0.016;
38
- break;
39
- case 'down':
40
- cameraState.y += scrollSpeed * delta * 0.016;
41
- break;
42
- case 'up':
43
- cameraState.y -= scrollSpeed * delta * 0.016;
44
- break;
45
- }
46
- break;
47
- }
48
- case 'follow': {
49
- // Follow target entity
50
- const targetId = cameraConfig?.followTarget;
51
- if (!targetId)
52
- break;
53
- const targetEntity = entities.current.get(targetId);
54
- if (!targetEntity)
55
- break;
56
- const smoothing = cameraConfig?.smoothing ?? 0.1;
57
- const targetX = targetEntity.x;
58
- const targetY = targetEntity.y;
59
- // Lerp camera towards target
60
- cameraState.x += (targetX - cameraState.x) * smoothing;
61
- cameraState.y += (targetY - cameraState.y) * smoothing;
62
- break;
63
- }
64
- }
65
- // Apply camera bounds
66
- const bounds = cameraConfig?.bounds;
67
- if (bounds) {
68
- if (bounds.minX !== undefined)
69
- cameraState.x = Math.max(cameraState.x, bounds.minX);
70
- if (bounds.maxX !== undefined)
71
- cameraState.x = Math.min(cameraState.x, bounds.maxX);
72
- if (bounds.minY !== undefined)
73
- cameraState.y = Math.max(cameraState.y, bounds.minY);
74
- if (bounds.maxY !== undefined)
75
- cameraState.y = Math.min(cameraState.y, bounds.maxY);
76
- }
77
- // Apply camera shake
78
- if (cameraState.shakeTimeRemaining > 0) {
79
- const intensity = cameraState.shakeIntensity;
80
- cameraState.x += (Math.random() - 0.5) * intensity;
81
- cameraState.y += (Math.random() - 0.5) * intensity;
82
- cameraState.shakeTimeRemaining -= delta * 16.67;
83
- if (cameraState.shakeTimeRemaining <= 0) {
84
- cameraState.shakeIntensity = 0;
85
- }
86
- }
87
- // Apply zoom
88
- cameraState.zoom = cameraConfig?.zoom ?? 1;
89
- });
90
- }