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.
55 lines
1.4 KiB
JavaScript
55 lines
1.4 KiB
JavaScript
import { Game, AUTO, Scale } from 'phaser';
|
|
import { BootScene } from './scenes/BootScene.js';
|
|
import { MenuScene } from './scenes/MenuScene.js';
|
|
import { GameScene } from './scenes/GameScene.js';
|
|
import { GameOverScene } from './scenes/GameOverScene.js';
|
|
import { GAME_WIDTH, GAME_HEIGHT, GRAVITY } from './config/game.config.js';
|
|
|
|
const config = {
|
|
type: AUTO,
|
|
parent: 'game-container',
|
|
width: GAME_WIDTH,
|
|
height: GAME_HEIGHT,
|
|
backgroundColor: '#0d001a',
|
|
scale: {
|
|
mode: Scale.FIT,
|
|
autoCenter: Scale.CENTER_BOTH,
|
|
},
|
|
physics: {
|
|
default: 'arcade',
|
|
arcade: {
|
|
gravity: { y: GRAVITY },
|
|
debug: false,
|
|
},
|
|
},
|
|
fps: {
|
|
target: 60,
|
|
min: 30,
|
|
},
|
|
disableContextMenu: true,
|
|
scene: [BootScene, MenuScene, GameScene, GameOverScene],
|
|
pixelArt: false,
|
|
antialias: true,
|
|
};
|
|
|
|
const game = new Game(config);
|
|
window.game = game;
|
|
|
|
// Global crash guard: if an uncaught error happens during gameplay, recover to
|
|
// the menu instead of leaving a frozen canvas.
|
|
function recoverToMenu() {
|
|
try {
|
|
if (!game || !game.scene) return;
|
|
const inGame = game.scene.isActive('GameScene');
|
|
if (inGame) {
|
|
game.scene.stop('GameScene');
|
|
game.scene.start('MenuScene');
|
|
}
|
|
} catch (_) {
|
|
/* swallow — last-resort guard */
|
|
}
|
|
}
|
|
|
|
window.addEventListener('error', recoverToMenu);
|
|
window.addEventListener('unhandledrejection', recoverToMenu);
|