@thewhateverapp/tile-sdk 0.15.3 → 0.15.5

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 (54) hide show
  1. package/dist/bridge/TileBridge.d.ts +29 -0
  2. package/dist/bridge/TileBridge.d.ts.map +1 -1
  3. package/dist/bridge/TileBridge.js +78 -0
  4. package/dist/excalibur/index.d.ts +48 -0
  5. package/dist/excalibur/index.d.ts.map +1 -0
  6. package/dist/excalibur/index.js +51 -0
  7. package/dist/react/ExcaliburGame.d.ts +109 -0
  8. package/dist/react/ExcaliburGame.d.ts.map +1 -0
  9. package/dist/react/ExcaliburGame.js +215 -0
  10. package/dist/react/index.js +3 -3
  11. package/dist/scene/index.d.ts +3 -41
  12. package/dist/scene/index.d.ts.map +1 -1
  13. package/dist/scene/index.js +1 -49
  14. package/dist/spec/schema.d.ts +12 -12
  15. package/package.json +7 -7
  16. package/dist/pixi/index.d.ts +0 -43
  17. package/dist/pixi/index.d.ts.map +0 -1
  18. package/dist/pixi/index.js +0 -46
  19. package/dist/react/PixiGame.d.ts +0 -138
  20. package/dist/react/PixiGame.d.ts.map +0 -1
  21. package/dist/react/PixiGame.js +0 -237
  22. package/dist/scene/SceneContext.d.ts +0 -173
  23. package/dist/scene/SceneContext.d.ts.map +0 -1
  24. package/dist/scene/SceneContext.js +0 -89
  25. package/dist/scene/SceneFromJson.d.ts +0 -34
  26. package/dist/scene/SceneFromJson.d.ts.map +0 -1
  27. package/dist/scene/SceneFromJson.js +0 -97
  28. package/dist/scene/SceneRenderer.d.ts +0 -29
  29. package/dist/scene/SceneRenderer.d.ts.map +0 -1
  30. package/dist/scene/SceneRenderer.js +0 -312
  31. package/dist/scene/camera/CameraController.d.ts +0 -6
  32. package/dist/scene/camera/CameraController.d.ts.map +0 -1
  33. package/dist/scene/camera/CameraController.js +0 -90
  34. package/dist/scene/components/ComponentRunner.d.ts +0 -22
  35. package/dist/scene/components/ComponentRunner.d.ts.map +0 -1
  36. package/dist/scene/components/ComponentRunner.js +0 -210
  37. package/dist/scene/effects/GlowFilter.d.ts +0 -38
  38. package/dist/scene/effects/GlowFilter.d.ts.map +0 -1
  39. package/dist/scene/effects/GlowFilter.js +0 -40
  40. package/dist/scene/effects/ParticleSystem.d.ts +0 -52
  41. package/dist/scene/effects/ParticleSystem.d.ts.map +0 -1
  42. package/dist/scene/effects/ParticleSystem.js +0 -107
  43. package/dist/scene/entities/EntityGraphics.d.ts +0 -26
  44. package/dist/scene/entities/EntityGraphics.d.ts.map +0 -1
  45. package/dist/scene/entities/EntityGraphics.js +0 -226
  46. package/dist/scene/input/InputManager.d.ts +0 -18
  47. package/dist/scene/input/InputManager.d.ts.map +0 -1
  48. package/dist/scene/input/InputManager.js +0 -86
  49. package/dist/scene/physics/PhysicsEngine.d.ts +0 -15
  50. package/dist/scene/physics/PhysicsEngine.d.ts.map +0 -1
  51. package/dist/scene/physics/PhysicsEngine.js +0 -260
  52. package/dist/scene/timeline/TimelineExecutor.d.ts +0 -6
  53. package/dist/scene/timeline/TimelineExecutor.d.ts.map +0 -1
  54. package/dist/scene/timeline/TimelineExecutor.js +0 -241
@@ -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
- }
@@ -1,22 +0,0 @@
1
- import type { SceneContextValue, EntityState } from '../SceneContext.js';
2
- /**
3
- * Generic component type
4
- */
5
- interface GenericComponent {
6
- type: string;
7
- params?: Record<string, unknown>;
8
- }
9
- /**
10
- * Component handler function type
11
- */
12
- type ComponentHandler = (state: EntityState, component: GenericComponent, context: SceneContextValue, delta: number) => void;
13
- /**
14
- * Hook to run component logic each frame
15
- */
16
- export declare function useComponentRunner(context: SceneContextValue): void;
17
- /**
18
- * Register a custom component handler
19
- */
20
- export declare function registerComponent(type: string, handler: ComponentHandler): void;
21
- export {};
22
- //# sourceMappingURL=ComponentRunner.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ComponentRunner.d.ts","sourceRoot":"","sources":["../../../src/scene/components/ComponentRunner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAazE;;GAEG;AACH,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,KAAK,gBAAgB,GAAG,CACtB,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,MAAM,KACV,IAAI,CAAC;AAcV;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,QAkB5D;AA8ND;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,QAExE"}
@@ -1,210 +0,0 @@
1
- 'use client';
2
- import { useGameLoop } from '../../pixi/index.js';
3
- import { isJumpPressed } from '../input/InputManager.js';
4
- import { setVelocity } from '../physics/PhysicsEngine.js';
5
- import Matter from 'matter-js';
6
- const { Body } = Matter;
7
- /**
8
- * Registry of component handlers
9
- */
10
- const componentHandlers = {
11
- Move: handleMove,
12
- Rotate: handleRotate,
13
- Oscillate: handleOscillate,
14
- Follow: handleFollow,
15
- DashPlayer: handleDashPlayer,
16
- // Add more handlers as needed
17
- };
18
- /**
19
- * Hook to run component logic each frame
20
- */
21
- export function useComponentRunner(context) {
22
- const { entities } = context;
23
- useGameLoop((delta) => {
24
- for (const [, state] of entities.current) {
25
- if (state.destroyed)
26
- continue;
27
- const components = state.entity.components;
28
- if (!components)
29
- continue;
30
- for (const component of components) {
31
- const handler = componentHandlers[component.type];
32
- if (handler) {
33
- handler(state, component, context, delta);
34
- }
35
- }
36
- }
37
- });
38
- }
39
- /**
40
- * Move component - applies constant velocity/acceleration
41
- */
42
- function handleMove(state, component, context, delta) {
43
- const params = (component.params ?? {});
44
- // Apply acceleration
45
- if (params.accelX) {
46
- state.velocityX += params.accelX * delta * 0.016;
47
- }
48
- if (params.accelY) {
49
- state.velocityY += params.accelY * delta * 0.016;
50
- }
51
- // Apply constant velocity (overrides physics for non-physics entities)
52
- if (!state.body) {
53
- if (params.velocityX !== undefined) {
54
- state.x += params.velocityX * delta * 0.016;
55
- }
56
- if (params.velocityY !== undefined) {
57
- state.y += params.velocityY * delta * 0.016;
58
- }
59
- }
60
- else if (params.velocityX !== undefined || params.velocityY !== undefined) {
61
- // For physics bodies, set velocity directly
62
- setVelocity(state.body, params.velocityX ?? state.velocityX, params.velocityY ?? state.velocityY);
63
- }
64
- }
65
- /**
66
- * Rotate component - spins the entity
67
- */
68
- function handleRotate(state, component, context, delta) {
69
- const params = (component.params ?? { speed: 0 });
70
- const rotationSpeed = (params.speed * Math.PI) / 180; // Convert to radians/second
71
- state.rotation += rotationSpeed * delta * 0.016;
72
- // Update physics body rotation if present
73
- if (state.body) {
74
- Body.setAngle(state.body, state.rotation);
75
- }
76
- }
77
- /**
78
- * Oscillate component - sine wave motion
79
- */
80
- function handleOscillate(state, component, context, delta) {
81
- const params = (component.params ?? { axis: 'y', amplitude: 10, frequency: 1 });
82
- const { axis, amplitude, frequency, phase = 0 } = params;
83
- // Get or initialize oscillation state
84
- if (state.componentState.oscillateTime === undefined) {
85
- state.componentState.oscillateTime = 0;
86
- state.componentState.oscillateStartX = state.x;
87
- state.componentState.oscillateStartY = state.y;
88
- }
89
- state.componentState.oscillateTime += delta * 0.016;
90
- const time = state.componentState.oscillateTime;
91
- const offset = Math.sin(time * frequency * Math.PI * 2 + phase) * amplitude;
92
- if (axis === 'x') {
93
- state.x = state.componentState.oscillateStartX + offset;
94
- }
95
- else {
96
- state.y = state.componentState.oscillateStartY + offset;
97
- }
98
- // Update physics body position if present and static
99
- if (state.body && state.entity.body?.isStatic) {
100
- Body.setPosition(state.body, { x: state.x, y: state.y });
101
- }
102
- }
103
- /**
104
- * Follow component - tracks another entity
105
- */
106
- function handleFollow(state, component, context, delta) {
107
- const params = (component.params ?? { target: '' });
108
- const { target, offsetX = 0, offsetY = 0, smoothing = 0.1 } = params;
109
- const targetEntity = context.getEntity(target);
110
- if (!targetEntity)
111
- return;
112
- const targetX = targetEntity.x + offsetX;
113
- const targetY = targetEntity.y + offsetY;
114
- // Lerp towards target
115
- state.x += (targetX - state.x) * smoothing;
116
- state.y += (targetY - state.y) * smoothing;
117
- // Update physics body if present
118
- if (state.body) {
119
- Body.setPosition(state.body, { x: state.x, y: state.y });
120
- }
121
- }
122
- /**
123
- * DashPlayer component - main player controller for Geometry Dash style games
124
- */
125
- function handleDashPlayer(state, component, context, delta) {
126
- const { player, camera, emitEvent, respawnPlayer } = context;
127
- const playerState = player.current;
128
- // Handle jump input (check before started check so we can start the game)
129
- const jumpPressed = isJumpPressed(context);
130
- const wasJumpPressed = state.componentState.wasJumpPressed ?? false;
131
- const justPressed = jumpPressed && !wasJumpPressed;
132
- state.componentState.wasJumpPressed = jumpPressed;
133
- // Start the game on first jump
134
- if (!playerState.started && justPressed) {
135
- playerState.started = true;
136
- emitEvent('game.started');
137
- return; // Don't process jump on the start frame
138
- }
139
- // Don't run game logic until started
140
- if (!playerState.started) {
141
- return;
142
- }
143
- // Handle death
144
- if (playerState.dead) {
145
- // Wait a moment then respawn
146
- if (state.componentState.deathTimer === undefined) {
147
- state.componentState.deathTimer = 0;
148
- }
149
- state.componentState.deathTimer += delta * 16.67;
150
- if (state.componentState.deathTimer > 500) {
151
- state.componentState.deathTimer = 0;
152
- respawnPlayer();
153
- }
154
- return;
155
- }
156
- // Handle jump (only after game started)
157
- if (justPressed) {
158
- // Check if touching an orb
159
- if (playerState.touchingOrb) {
160
- const orb = context.getEntity(playerState.touchingOrb);
161
- if (orb) {
162
- const orbComponent = orb.entity.components?.find((c) => c.type === 'JumpOrb');
163
- const impulse = orbComponent?.params?.impulse ?? -900;
164
- if (state.body) {
165
- Body.setVelocity(state.body, {
166
- x: state.body.velocity.x,
167
- y: impulse * 0.01 * playerState.gravityDir,
168
- });
169
- }
170
- emitEvent('orb.used', { orbId: playerState.touchingOrb });
171
- playerState.touchingOrb = null;
172
- }
173
- }
174
- // Normal jump from ground
175
- else if (playerState.grounded) {
176
- const jumpVelocity = -15 * playerState.gravityDir;
177
- if (state.body) {
178
- Body.setVelocity(state.body, {
179
- x: state.body.velocity.x,
180
- y: jumpVelocity,
181
- });
182
- }
183
- playerState.grounded = false;
184
- playerState.jumpCount++;
185
- emitEvent('player.jump', { jumpCount: playerState.jumpCount });
186
- }
187
- }
188
- // Clear orb touching state if not colliding anymore
189
- // This is handled in physics collision detection
190
- // Rotate player based on movement (visual only)
191
- if (state.body && Math.abs(state.body.velocity.y) > 1) {
192
- state.rotation += 0.1 * delta * playerState.gravityDir;
193
- }
194
- // Apply constant forward velocity (for auto-runners)
195
- // Physics velocity is in px/s, engine runs at 16.67ms steps
196
- // To match camera scroll: velocity = scrollSpeed (direct, physics handles timing)
197
- const scrollSpeed = (context.spec.camera?.scrollSpeed ?? 320) * playerState.speedMultiplier;
198
- if (state.body) {
199
- Body.setVelocity(state.body, {
200
- x: scrollSpeed,
201
- y: state.body.velocity.y,
202
- });
203
- }
204
- }
205
- /**
206
- * Register a custom component handler
207
- */
208
- export function registerComponent(type, handler) {
209
- componentHandlers[type] = handler;
210
- }
@@ -1,38 +0,0 @@
1
- /**
2
- * Glow effect utilities
3
- *
4
- * Note: Full glow filter implementation requires @pixi/filter-glow
5
- * which is an optional peer dependency. This file provides helper
6
- * functions for creating and managing glow effects.
7
- */
8
- export interface GlowOptions {
9
- color?: string;
10
- strength?: number;
11
- blur?: number;
12
- quality?: number;
13
- }
14
- /**
15
- * Parse color string to number
16
- */
17
- export declare function parseColor(color: string): number;
18
- /**
19
- * Create glow filter options
20
- * Returns options compatible with @pixi/filter-glow GlowFilter
21
- */
22
- export declare function createGlowOptions(options?: GlowOptions): {
23
- color: number;
24
- outerStrength: number;
25
- innerStrength: number;
26
- distance: number;
27
- quality: number;
28
- };
29
- /**
30
- * Check if glow filter is available
31
- * Returns true if @pixi/filter-glow is installed
32
- */
33
- export declare function isGlowFilterAvailable(): boolean;
34
- /**
35
- * Create a CSS text-shadow style for glow effect (fallback)
36
- */
37
- export declare function createGlowShadow(color: string, strength: number, blur: number): string;
38
- //# sourceMappingURL=GlowFilter.d.ts.map