@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.
- package/dist/bridge/TileBridge.d.ts +29 -0
- package/dist/bridge/TileBridge.d.ts.map +1 -1
- package/dist/bridge/TileBridge.js +78 -0
- package/dist/excalibur/index.d.ts +48 -0
- package/dist/excalibur/index.d.ts.map +1 -0
- package/dist/excalibur/index.js +51 -0
- package/dist/react/ExcaliburGame.d.ts +109 -0
- package/dist/react/ExcaliburGame.d.ts.map +1 -0
- package/dist/react/ExcaliburGame.js +215 -0
- package/dist/react/index.js +3 -3
- package/dist/scene/index.d.ts +3 -41
- package/dist/scene/index.d.ts.map +1 -1
- package/dist/scene/index.js +1 -49
- package/dist/spec/schema.d.ts +12 -12
- package/package.json +7 -7
- package/dist/pixi/index.d.ts +0 -43
- package/dist/pixi/index.d.ts.map +0 -1
- package/dist/pixi/index.js +0 -46
- package/dist/react/PixiGame.d.ts +0 -138
- package/dist/react/PixiGame.d.ts.map +0 -1
- package/dist/react/PixiGame.js +0 -237
- package/dist/scene/SceneContext.d.ts +0 -173
- package/dist/scene/SceneContext.d.ts.map +0 -1
- package/dist/scene/SceneContext.js +0 -89
- package/dist/scene/SceneFromJson.d.ts +0 -34
- package/dist/scene/SceneFromJson.d.ts.map +0 -1
- package/dist/scene/SceneFromJson.js +0 -97
- package/dist/scene/SceneRenderer.d.ts +0 -29
- package/dist/scene/SceneRenderer.d.ts.map +0 -1
- package/dist/scene/SceneRenderer.js +0 -312
- package/dist/scene/camera/CameraController.d.ts +0 -6
- package/dist/scene/camera/CameraController.d.ts.map +0 -1
- package/dist/scene/camera/CameraController.js +0 -90
- package/dist/scene/components/ComponentRunner.d.ts +0 -22
- package/dist/scene/components/ComponentRunner.d.ts.map +0 -1
- package/dist/scene/components/ComponentRunner.js +0 -210
- package/dist/scene/effects/GlowFilter.d.ts +0 -38
- package/dist/scene/effects/GlowFilter.d.ts.map +0 -1
- package/dist/scene/effects/GlowFilter.js +0 -40
- package/dist/scene/effects/ParticleSystem.d.ts +0 -52
- package/dist/scene/effects/ParticleSystem.d.ts.map +0 -1
- package/dist/scene/effects/ParticleSystem.js +0 -107
- package/dist/scene/entities/EntityGraphics.d.ts +0 -26
- package/dist/scene/entities/EntityGraphics.d.ts.map +0 -1
- package/dist/scene/entities/EntityGraphics.js +0 -226
- package/dist/scene/input/InputManager.d.ts +0 -18
- package/dist/scene/input/InputManager.d.ts.map +0 -1
- package/dist/scene/input/InputManager.js +0 -86
- package/dist/scene/physics/PhysicsEngine.d.ts +0 -15
- package/dist/scene/physics/PhysicsEngine.d.ts.map +0 -1
- package/dist/scene/physics/PhysicsEngine.js +0 -260
- package/dist/scene/timeline/TimelineExecutor.d.ts +0 -6
- package/dist/scene/timeline/TimelineExecutor.d.ts.map +0 -1
- 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 +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
|