@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,84 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useGameLoop } from '../../pixi';
|
|
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
|
|
24
|
+
const scrollSpeed = (cameraConfig?.scrollSpeed ?? 320) * playerState.speedMultiplier;
|
|
25
|
+
const direction = cameraConfig?.scrollDirection ?? 'right';
|
|
26
|
+
switch (direction) {
|
|
27
|
+
case 'right':
|
|
28
|
+
cameraState.x += scrollSpeed * delta * 0.016;
|
|
29
|
+
break;
|
|
30
|
+
case 'left':
|
|
31
|
+
cameraState.x -= scrollSpeed * delta * 0.016;
|
|
32
|
+
break;
|
|
33
|
+
case 'down':
|
|
34
|
+
cameraState.y += scrollSpeed * delta * 0.016;
|
|
35
|
+
break;
|
|
36
|
+
case 'up':
|
|
37
|
+
cameraState.y -= scrollSpeed * delta * 0.016;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case 'follow': {
|
|
43
|
+
// Follow target entity
|
|
44
|
+
const targetId = cameraConfig?.followTarget;
|
|
45
|
+
if (!targetId)
|
|
46
|
+
break;
|
|
47
|
+
const targetEntity = entities.current.get(targetId);
|
|
48
|
+
if (!targetEntity)
|
|
49
|
+
break;
|
|
50
|
+
const smoothing = cameraConfig?.smoothing ?? 0.1;
|
|
51
|
+
const targetX = targetEntity.x;
|
|
52
|
+
const targetY = targetEntity.y;
|
|
53
|
+
// Lerp camera towards target
|
|
54
|
+
cameraState.x += (targetX - cameraState.x) * smoothing;
|
|
55
|
+
cameraState.y += (targetY - cameraState.y) * smoothing;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Apply camera bounds
|
|
60
|
+
const bounds = cameraConfig?.bounds;
|
|
61
|
+
if (bounds) {
|
|
62
|
+
if (bounds.minX !== undefined)
|
|
63
|
+
cameraState.x = Math.max(cameraState.x, bounds.minX);
|
|
64
|
+
if (bounds.maxX !== undefined)
|
|
65
|
+
cameraState.x = Math.min(cameraState.x, bounds.maxX);
|
|
66
|
+
if (bounds.minY !== undefined)
|
|
67
|
+
cameraState.y = Math.max(cameraState.y, bounds.minY);
|
|
68
|
+
if (bounds.maxY !== undefined)
|
|
69
|
+
cameraState.y = Math.min(cameraState.y, bounds.maxY);
|
|
70
|
+
}
|
|
71
|
+
// Apply camera shake
|
|
72
|
+
if (cameraState.shakeTimeRemaining > 0) {
|
|
73
|
+
const intensity = cameraState.shakeIntensity;
|
|
74
|
+
cameraState.x += (Math.random() - 0.5) * intensity;
|
|
75
|
+
cameraState.y += (Math.random() - 0.5) * intensity;
|
|
76
|
+
cameraState.shakeTimeRemaining -= delta * 16.67;
|
|
77
|
+
if (cameraState.shakeTimeRemaining <= 0) {
|
|
78
|
+
cameraState.shakeIntensity = 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Apply zoom
|
|
82
|
+
cameraState.zoom = cameraConfig?.zoom ?? 1;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SceneContextValue, EntityState } from '../SceneContext';
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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,iBAAiB,CAAC;AAatE;;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;AA+MD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,QAExE"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useGameLoop } from '../../pixi';
|
|
3
|
+
import { isJumpPressed } from '../input/InputManager';
|
|
4
|
+
import { setVelocity } from '../physics/PhysicsEngine';
|
|
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 death
|
|
129
|
+
if (playerState.dead) {
|
|
130
|
+
// Wait a moment then respawn
|
|
131
|
+
if (state.componentState.deathTimer === undefined) {
|
|
132
|
+
state.componentState.deathTimer = 0;
|
|
133
|
+
}
|
|
134
|
+
state.componentState.deathTimer += delta * 16.67;
|
|
135
|
+
if (state.componentState.deathTimer > 500) {
|
|
136
|
+
state.componentState.deathTimer = 0;
|
|
137
|
+
respawnPlayer();
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Handle jump input
|
|
142
|
+
const jumpPressed = isJumpPressed(context);
|
|
143
|
+
const wasJumpPressed = state.componentState.wasJumpPressed ?? false;
|
|
144
|
+
const justPressed = jumpPressed && !wasJumpPressed;
|
|
145
|
+
state.componentState.wasJumpPressed = jumpPressed;
|
|
146
|
+
if (justPressed) {
|
|
147
|
+
// Check if touching an orb
|
|
148
|
+
if (playerState.touchingOrb) {
|
|
149
|
+
const orb = context.getEntity(playerState.touchingOrb);
|
|
150
|
+
if (orb) {
|
|
151
|
+
const orbComponent = orb.entity.components?.find((c) => c.type === 'JumpOrb');
|
|
152
|
+
const impulse = orbComponent?.params?.impulse ?? -900;
|
|
153
|
+
if (state.body) {
|
|
154
|
+
Body.setVelocity(state.body, {
|
|
155
|
+
x: state.body.velocity.x,
|
|
156
|
+
y: impulse * 0.01 * playerState.gravityDir,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
emitEvent('orb.used', { orbId: playerState.touchingOrb });
|
|
160
|
+
playerState.touchingOrb = null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Normal jump from ground
|
|
164
|
+
else if (playerState.grounded) {
|
|
165
|
+
const jumpVelocity = -15 * playerState.gravityDir;
|
|
166
|
+
if (state.body) {
|
|
167
|
+
Body.setVelocity(state.body, {
|
|
168
|
+
x: state.body.velocity.x,
|
|
169
|
+
y: jumpVelocity,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
playerState.grounded = false;
|
|
173
|
+
playerState.jumpCount++;
|
|
174
|
+
emitEvent('player.jump', { jumpCount: playerState.jumpCount });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Clear orb touching state if not colliding anymore
|
|
178
|
+
// This is handled in physics collision detection
|
|
179
|
+
// Rotate player based on movement (visual only)
|
|
180
|
+
if (state.body && Math.abs(state.body.velocity.y) > 1) {
|
|
181
|
+
state.rotation += 0.1 * delta * playerState.gravityDir;
|
|
182
|
+
}
|
|
183
|
+
// Apply constant forward velocity (for auto-runners)
|
|
184
|
+
const scrollSpeed = (context.spec.camera?.scrollSpeed ?? 320) * playerState.speedMultiplier;
|
|
185
|
+
if (state.body) {
|
|
186
|
+
Body.setVelocity(state.body, {
|
|
187
|
+
x: scrollSpeed * 0.01,
|
|
188
|
+
y: state.body.velocity.y,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Register a custom component handler
|
|
194
|
+
*/
|
|
195
|
+
export function registerComponent(type, handler) {
|
|
196
|
+
componentHandlers[type] = handler;
|
|
197
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GlowFilter.d.ts","sourceRoot":"","sources":["../../../src/scene/effects/GlowFilter.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,WAAgB;;;;;;EAe1D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAO/C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* Parse color string to number
|
|
4
|
+
*/
|
|
5
|
+
export function parseColor(color) {
|
|
6
|
+
return parseInt(color.replace('#', ''), 16);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create glow filter options
|
|
10
|
+
* Returns options compatible with @pixi/filter-glow GlowFilter
|
|
11
|
+
*/
|
|
12
|
+
export function createGlowOptions(options = {}) {
|
|
13
|
+
const { color = '#ffffff', strength = 1, blur = 4, quality = 0.1, } = options;
|
|
14
|
+
return {
|
|
15
|
+
color: parseColor(color),
|
|
16
|
+
outerStrength: strength,
|
|
17
|
+
innerStrength: 0,
|
|
18
|
+
distance: blur * 2,
|
|
19
|
+
quality,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check if glow filter is available
|
|
24
|
+
* Returns true if @pixi/filter-glow is installed
|
|
25
|
+
*/
|
|
26
|
+
export function isGlowFilterAvailable() {
|
|
27
|
+
try {
|
|
28
|
+
// Dynamic import check
|
|
29
|
+
return typeof window !== 'undefined';
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a CSS text-shadow style for glow effect (fallback)
|
|
37
|
+
*/
|
|
38
|
+
export function createGlowShadow(color, strength, blur) {
|
|
39
|
+
return `0 0 ${blur}px ${color}, 0 0 ${blur * 2}px ${color}`;
|
|
40
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { EmitterGeometry } from '@thewhateverapp/scene-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Particle representation
|
|
4
|
+
*/
|
|
5
|
+
export interface Particle {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
velocityX: number;
|
|
9
|
+
velocityY: number;
|
|
10
|
+
life: number;
|
|
11
|
+
maxLife: number;
|
|
12
|
+
scale: number;
|
|
13
|
+
scaleStart: number;
|
|
14
|
+
scaleEnd: number;
|
|
15
|
+
color: number;
|
|
16
|
+
alpha: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Particle emitter state
|
|
20
|
+
*/
|
|
21
|
+
export interface ParticleEmitter {
|
|
22
|
+
/** Particles in this emitter */
|
|
23
|
+
particles: Particle[];
|
|
24
|
+
/** Emitter configuration */
|
|
25
|
+
config: EmitterGeometry;
|
|
26
|
+
/** Position */
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
/** Time since last emission */
|
|
30
|
+
timeSinceEmit: number;
|
|
31
|
+
/** Whether emitter is active */
|
|
32
|
+
active: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create a new particle emitter
|
|
36
|
+
*/
|
|
37
|
+
export declare function createEmitter(config: EmitterGeometry, x: number, y: number): ParticleEmitter;
|
|
38
|
+
/**
|
|
39
|
+
* Update particle emitter
|
|
40
|
+
* @param emitter The emitter to update
|
|
41
|
+
* @param deltaMs Time since last update in milliseconds
|
|
42
|
+
*/
|
|
43
|
+
export declare function updateEmitter(emitter: ParticleEmitter, deltaMs: number): void;
|
|
44
|
+
/**
|
|
45
|
+
* Draw particles to a Graphics object
|
|
46
|
+
*/
|
|
47
|
+
export declare function drawParticles(graphics: import('pixi.js').Graphics, emitter: ParticleEmitter): void;
|
|
48
|
+
/**
|
|
49
|
+
* Burst emit multiple particles at once
|
|
50
|
+
*/
|
|
51
|
+
export declare function burstEmit(emitter: ParticleEmitter, count: number): void;
|
|
52
|
+
//# sourceMappingURL=ParticleSystem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ParticleSystem.d.ts","sourceRoot":"","sources":["../../../src/scene/effects/ParticleSystem.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,gCAAgC;IAChC,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,4BAA4B;IAC5B,MAAM,EAAE,eAAe,CAAC;IACxB,eAAe;IACf,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,+BAA+B;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,eAAe,EACvB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,GACR,eAAe,CASjB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAoC7E;AAyCD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,OAAO,SAAS,EAAE,QAAQ,EACpC,OAAO,EAAE,eAAe,GACvB,IAAI,CAQN;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIvE"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* Create a new particle emitter
|
|
4
|
+
*/
|
|
5
|
+
export function createEmitter(config, x, y) {
|
|
6
|
+
return {
|
|
7
|
+
particles: [],
|
|
8
|
+
config,
|
|
9
|
+
x,
|
|
10
|
+
y,
|
|
11
|
+
timeSinceEmit: 0,
|
|
12
|
+
active: true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Update particle emitter
|
|
17
|
+
* @param emitter The emitter to update
|
|
18
|
+
* @param deltaMs Time since last update in milliseconds
|
|
19
|
+
*/
|
|
20
|
+
export function updateEmitter(emitter, deltaMs) {
|
|
21
|
+
const { config, particles } = emitter;
|
|
22
|
+
// Update existing particles
|
|
23
|
+
for (let i = particles.length - 1; i >= 0; i--) {
|
|
24
|
+
const particle = particles[i];
|
|
25
|
+
// Update life
|
|
26
|
+
particle.life -= deltaMs;
|
|
27
|
+
if (particle.life <= 0) {
|
|
28
|
+
particles.splice(i, 1);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
// Update position
|
|
32
|
+
particle.x += particle.velocityX * (deltaMs / 1000);
|
|
33
|
+
particle.y += particle.velocityY * (deltaMs / 1000);
|
|
34
|
+
// Update scale
|
|
35
|
+
const lifeRatio = particle.life / particle.maxLife;
|
|
36
|
+
particle.scale = particle.scaleStart + (particle.scaleEnd - particle.scaleStart) * (1 - lifeRatio);
|
|
37
|
+
// Update alpha (fade out)
|
|
38
|
+
particle.alpha = lifeRatio;
|
|
39
|
+
}
|
|
40
|
+
// Emit new particles
|
|
41
|
+
if (emitter.active) {
|
|
42
|
+
const emitInterval = 1000 / config.rate;
|
|
43
|
+
emitter.timeSinceEmit += deltaMs;
|
|
44
|
+
while (emitter.timeSinceEmit >= emitInterval) {
|
|
45
|
+
emitter.timeSinceEmit -= emitInterval;
|
|
46
|
+
emitParticle(emitter);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Emit a single particle
|
|
52
|
+
*/
|
|
53
|
+
function emitParticle(emitter) {
|
|
54
|
+
const { config, x, y, particles } = emitter;
|
|
55
|
+
// Calculate random velocity
|
|
56
|
+
const speed = randomRange(config.speed.min, config.speed.max);
|
|
57
|
+
const angle = config.angle
|
|
58
|
+
? randomRange(config.angle.min, config.angle.max) * (Math.PI / 180)
|
|
59
|
+
: Math.random() * Math.PI * 2;
|
|
60
|
+
const velocityX = Math.cos(angle) * speed;
|
|
61
|
+
const velocityY = Math.sin(angle) * speed;
|
|
62
|
+
// Calculate scale
|
|
63
|
+
const scaleStart = config.scale?.start ?? 1;
|
|
64
|
+
const scaleEnd = config.scale?.end ?? 0;
|
|
65
|
+
// Pick random color
|
|
66
|
+
const colors = config.colors ?? ['#ffffff'];
|
|
67
|
+
const colorStr = colors[Math.floor(Math.random() * colors.length)];
|
|
68
|
+
const color = parseInt(colorStr.replace('#', ''), 16);
|
|
69
|
+
particles.push({
|
|
70
|
+
x,
|
|
71
|
+
y,
|
|
72
|
+
velocityX,
|
|
73
|
+
velocityY,
|
|
74
|
+
life: config.lifetime,
|
|
75
|
+
maxLife: config.lifetime,
|
|
76
|
+
scale: scaleStart,
|
|
77
|
+
scaleStart,
|
|
78
|
+
scaleEnd,
|
|
79
|
+
color,
|
|
80
|
+
alpha: 1,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Draw particles to a Graphics object
|
|
85
|
+
*/
|
|
86
|
+
export function drawParticles(graphics, emitter) {
|
|
87
|
+
graphics.clear();
|
|
88
|
+
for (const particle of emitter.particles) {
|
|
89
|
+
graphics.beginFill(particle.color, particle.alpha);
|
|
90
|
+
graphics.drawCircle(particle.x, particle.y, 3 * particle.scale);
|
|
91
|
+
graphics.endFill();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Burst emit multiple particles at once
|
|
96
|
+
*/
|
|
97
|
+
export function burstEmit(emitter, count) {
|
|
98
|
+
for (let i = 0; i < count; i++) {
|
|
99
|
+
emitParticle(emitter);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Generate random number in range
|
|
104
|
+
*/
|
|
105
|
+
function randomRange(min, max) {
|
|
106
|
+
return min + Math.random() * (max - min);
|
|
107
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { EntityState } from '../SceneContext';
|
|
3
|
+
/**
|
|
4
|
+
* Props for EntityRenderer
|
|
5
|
+
*/
|
|
6
|
+
export interface EntityRendererProps {
|
|
7
|
+
state: EntityState;
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Renders a single entity based on its kind
|
|
12
|
+
*/
|
|
13
|
+
export declare function EntityRenderer({ state, debug }: EntityRendererProps): React.JSX.Element | null;
|
|
14
|
+
//# sourceMappingURL=EntityRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntityRenderer.d.ts","sourceRoot":"","sources":["../../../src/scene/entities/EntityRenderer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA+B,MAAM,OAAO,CAAC;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAWnD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,KAAa,EAAE,EAAE,mBAAmB,4BAyG3E"}
|