A1 Pooled particle system - New ParticleManager owns 9 reusable Phaser emitters created once per scene (jump, explosion, powerup, puff, sparks, flame, wind, spring, speedline) - BootScene generates reusable white textures (px_square/soft/streak/ring) - GameScene burst helpers + EffectsManager flow effects now delegate to the pool instead of allocating rectangles + tweens every frame - Quality auto-detect (low on mobile/small screens) cuts particle counts A2 Fix high-speed landing tunneling - Cap downward velocity at PHYSICS.maxFallSpeed (boosts unaffected) - platformCollisionFilter now uses a swept deltaY one-way check so a fast fall can never be wrongly rejected or passed through A3 Safe storage - New utils/storage.js wraps localStorage with in-memory fallback so private mode / quota errors cannot crash the game; all access routed through it A4-A6 Lifecycle and robustness - Global error / unhandledrejection guard recovers to the menu - GameScene auto-pauses on tab hidden and cleans up the cross-scene listener on shutdown; fps pacing + disableContextMenu in game config - Moving platforms use a proper dynamic body instead of destroy()+new Body Verified in-browser: menu + game load with zero console errors, all emitters active, normal bouncing works, fall speed capped.
68 lines
1.8 KiB
JavaScript
68 lines
1.8 KiB
JavaScript
/**
|
|
* Drives WHEN boost visual effects play (state machine over the player's
|
|
* power-up state). All actual particle allocation is delegated to the pooled
|
|
* ParticleManager (scene.particles), so this class never creates GameObjects.
|
|
*/
|
|
export class EffectsManager {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
this.speedLineTimer = 0;
|
|
this.lastState = 'normal';
|
|
this.springTrailUntil = 0;
|
|
}
|
|
|
|
get pm() {
|
|
return this.scene.particles;
|
|
}
|
|
|
|
update(player, delta) {
|
|
const pm = this.pm;
|
|
if (!pm) return;
|
|
|
|
if (!player || !player.active || player.state === 'dead') {
|
|
pm.stopFlow();
|
|
return;
|
|
}
|
|
|
|
const state = player.state;
|
|
const now = this.scene.time.now;
|
|
const feetY = player.y + player.displayHeight / 2 - 6;
|
|
|
|
if (state === 'rocket' && Math.abs(player.body.velocity.y) > 150) {
|
|
pm.flameAt(player.x, feetY);
|
|
this._speedLines(delta);
|
|
} else if (state === 'propeller' && Math.abs(player.body.velocity.y) > 150) {
|
|
pm.windAt(player.x, player.y);
|
|
this._speedLines(delta);
|
|
} else if (state === 'normal' && now < this.springTrailUntil && player.body.velocity.y < -500) {
|
|
pm.springAt(player.x, feetY);
|
|
} else {
|
|
pm.stopFlow();
|
|
}
|
|
|
|
if (this.lastState !== state) {
|
|
if ((this.lastState === 'rocket' || this.lastState === 'propeller') && state === 'normal') {
|
|
pm.puff(player.x, player.y);
|
|
}
|
|
this.lastState = state;
|
|
}
|
|
}
|
|
|
|
_speedLines(delta) {
|
|
this.speedLineTimer += delta;
|
|
if (this.speedLineTimer > 65) {
|
|
this.speedLineTimer = 0;
|
|
this.pm.speedLine();
|
|
}
|
|
}
|
|
|
|
startBoost(player, type) {
|
|
if (this.pm) this.pm.boostBurst(player.x, player.y, type);
|
|
if (type === 'spring') {
|
|
this.springTrailUntil = this.scene.time.now + 700;
|
|
} else {
|
|
this.lastState = type;
|
|
}
|
|
}
|
|
}
|