import Phaser from 'phaser'; import './style.css'; import airshipSheetUrl from './assets/airships.svg?url'; import explosionSheetUrl from './assets/explosions.svg?url'; import { airshipLog, registerDebugSnapshot } from './diagnostics.js'; import { normalizePlayerDamage, processQueuedPlayerHits } from './game-logic.js'; const WORLD_SIZE = 2400; const PLAYER_MAX_SPEED = 180; const PLAYER_REVERSE_SPEED = -55; const PLAYER_ACCELERATION = 85; const PLAYER_BRAKE = 110; const PLAYER_TURN_RATE = 72; const PIRATE_SPEED = 118; const PIRATE_TURN_RATE = 52; const SHOT_SPEED = 520; const SHOT_LIFETIME = 900; const PLAYER_MAX_HULL = 100; const PIRATE_MAX_HULL = 120; const PIRATE_SHOT_DAMAGE = 6; const PLAYER_HIT_COOLDOWN_MS = 300; const MAX_PIRATES = 6; const AUTO_RESET_MS = 2200; const clamp = (v, min, max) => Math.max(min, Math.min(max, v)); const degToRad = (deg) => deg * Math.PI / 180; const normalizeDegrees = (deg) => ((deg % 360) + 360) % 360; const signedAngleDelta = (target, current) => { let delta = normalizeDegrees(target) - normalizeDegrees(current); if (delta > 180) delta -= 360; if (delta < -180) delta += 360; return delta; }; const bearingTo = (from, to) => normalizeDegrees(Math.atan2(to.y - from.y, to.x - from.x) * 180 / Math.PI + 90); const headingRad = (ship) => degToRad(ship.angle - 90); class GameScene extends Phaser.Scene { constructor() { super('game'); } init() { this.playerSpeed = 0; this.playerHull = PLAYER_MAX_HULL; this.gameState = 'playing'; this.lastPlayerShot = 0; this.lastPlayerHitAt = -Infinity; this.pendingPlayerHits = []; this.lastDiagnosticsAt = -Infinity; this.wave = 1; this.defeated = 0; airshipLog('scene:init', { hull: this.playerHull, wave: this.wave, }); } preload() { this.load.spritesheet('airships', airshipSheetUrl, { frameWidth: 64, frameHeight: 64, }); this.load.spritesheet('explosions', explosionSheetUrl, { frameWidth: 64, frameHeight: 64, }); } create() { this.physics.world.setBounds(0, 0, WORLD_SIZE, WORLD_SIZE); this.drawWorld(); this.createTextures(); this.createAnimations(); this.player = this.physics.add.sprite(1200, 1550, 'airships', 0); this.player.setDepth(6).setCollideWorldBounds(true).setDisplaySize(74, 74); this.player.body.setAllowGravity(false); this.player.body.setSize(42, 56, true); this.player.play('player-idle'); this.pirates = this.physics.add.group(); this.playerShots = this.physics.add.group(); this.pirateShots = this.physics.add.group(); this.arcGraphics = this.add.graphics().setDepth(4); this.physics.add.overlap(this.playerShots, this.pirates, this.hitPirate, null, this); this.physics.add.overlap(this.pirateShots, this.player, this.hitPlayer, null, this); registerDebugSnapshot(() => ({ gameState: this.gameState, playerHull: this.playerHull, playerSpeed: this.playerSpeed, pendingPlayerHits: this.pendingPlayerHits?.length || 0, activePirates: this.pirates?.countActive?.(true) || 0, playerShots: this.playerShots?.countActive?.(true) || 0, pirateShots: this.pirateShots?.countActive?.(true) || 0, wave: this.wave, defeated: this.defeated, now: this.time?.now ?? null, player: this.player ? { x: Math.round(this.player.x), y: Math.round(this.player.y), angle: Math.round(this.player.angle), active: this.player.active, visible: this.player.visible, hasBody: Boolean(this.player.body), } : null, })); airshipLog('scene:create', { hull: this.playerHull, wave: this.wave, worldSize: WORLD_SIZE, }); this.cursors = this.input.keyboard.createCursorKeys(); this.keys = this.input.keyboard.addKeys({ firePort: Phaser.Input.Keyboard.KeyCodes.Q, fireStarboard: Phaser.Input.Keyboard.KeyCodes.E, stop: Phaser.Input.Keyboard.KeyCodes.SPACE, restart: Phaser.Input.Keyboard.KeyCodes.R, }); this.cameras.main.setBounds(0, 0, WORLD_SIZE, WORLD_SIZE); this.cameras.main.startFollow(this.player, true, 0.08, 0.08); this.cameras.main.setZoom(1.12); this.createHud(); this.scale.on('resize', this.layoutHud, this); this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => { this.scale.off('resize', this.layoutHud, this); registerDebugSnapshot(null); airshipLog('scene:shutdown', { gameState: this.gameState, hull: this.playerHull, wave: this.wave, defeated: this.defeated, }); }); this.layoutHud({ width: this.scale.width, height: this.scale.height }); this.spawnWave(); } createHud() { this.mobileHudActive = !window.matchMedia('(hover: hover) and (pointer: fine)').matches; const fixedText = (x, y, text, style = {}) => this.add.text(x, y, text, { fontFamily: 'monospace', color: '#e8f2f7', ...style, }).setScrollFactor(0).setDepth(31); this.hudGraphics = this.add.graphics().setScrollFactor(0).setDepth(30); this.brandText = fixedText(14, 10, 'ARROWSTACK // SPARROW', { fontSize: '10px', color: '#8cd3ff', }); this.speedText = fixedText(14, 29, '', { fontSize: '16px', color: '#ffffff', }); this.headingText = fixedText(this.scale.width / 2, 10, '', { fontSize: '17px', color: '#d8ecff', align: 'center', }).setOrigin(0.5, 0); this.waveText = fixedText(this.scale.width / 2, 32, '', { fontSize: '11px', color: '#8cd3ff', align: 'center', }).setOrigin(0.5, 0); this.combatText = fixedText(this.scale.width - 76, 11, '', { fontSize: '10px', color: '#c8d6df', align: 'right', lineSpacing: 2, }).setOrigin(1, 0); this.playerHullLabel = fixedText(14, 55, '', { fontSize: '10px', color: '#a7f0c1', }); this.enemyHullLabel = fixedText(this.scale.width - 14, 55, '', { fontSize: '10px', color: '#ffb8b8', align: 'right', }).setOrigin(1, 0); this.status = fixedText(this.scale.width / 2, 102, '', { fontSize: '15px', color: '#ffffff', backgroundColor: '#071018dd', padding: { x: 14, y: 8 }, align: 'center', }).setOrigin(0.5, 0); this.controlsHint = fixedText(this.scale.width / 2, this.scale.height - 22, 'ARROWS move • Q/E broadside • SPACE brake • R restart', { fontSize: '10px', color: '#91a8b7', backgroundColor: '#07101899', padding: { x: 8, y: 4 }, }).setOrigin(0.5, 1); if (this.mobileHudActive) { for (const item of [ this.hudGraphics, this.brandText, this.speedText, this.headingText, this.waveText, this.combatText, this.playerHullLabel, this.enemyHullLabel, this.status, this.controlsHint, ]) { item?.setVisible(false); } } } createAnimations() { if (!this.anims.exists('player-idle')) { this.anims.create({ key: 'player-idle', frames: this.anims.generateFrameNumbers('airships', { start: 0, end: 3 }), frameRate: 5, repeat: -1, }); } if (!this.anims.exists('pirate-idle')) { this.anims.create({ key: 'pirate-idle', frames: this.anims.generateFrameNumbers('airships', { start: 4, end: 7 }), frameRate: 5, repeat: -1, }); } if (!this.anims.exists('impact-explosion')) { this.anims.create({ key: 'impact-explosion', frames: this.anims.generateFrameNumbers('explosions', { start: 0, end: 5 }), frameRate: 18, repeat: 0, }); } } spawnWave() { const count = Math.min(this.wave, MAX_PIRATES); airshipLog('wave:spawn', { wave: this.wave, count }); this.status.setText(`WAVE ${this.wave}\n${count} PIRATE${count === 1 ? '' : 'S'}`); for (let i = 0; i < count; i++) { const angle = (Math.PI * 2 * i / count) + Phaser.Math.FloatBetween(-0.25, 0.25); const radius = Phaser.Math.Between(500, 760); const x = clamp(this.player.x + Math.cos(angle) * radius, 120, WORLD_SIZE - 120); const y = clamp(this.player.y + Math.sin(angle) * radius, 120, WORLD_SIZE - 120); this.spawnPirate(x, y, angle); } this.time.delayedCall(900, () => { if (this.gameState === 'playing') this.status.setText(''); }); } spawnPirate(x, y, angle) { const pirate = this.pirates.create(x, y, 'airships', 4); pirate.setDepth(6).setCollideWorldBounds(true).setDisplaySize(78, 78); pirate.body.setAllowGravity(false); pirate.body.setSize(46, 58, true); pirate.angle = normalizeDegrees(Phaser.Math.RadToDeg(angle) + 90); pirate.setData('hull', PIRATE_MAX_HULL); pirate.setData('lastShot', this.time.now + Phaser.Math.Between(-700, 300)); pirate.play('pirate-idle'); return pirate; } drawWorld() { const bg = this.add.graphics(); bg.fillStyle(0x132335, 1); bg.fillRect(0, 0, WORLD_SIZE, WORLD_SIZE); bg.lineStyle(1, 0x8db3cc, 0.08); for (let x = 0; x <= WORLD_SIZE; x += 100) bg.lineBetween(x, 0, x, WORLD_SIZE); for (let y = 0; y <= WORLD_SIZE; y += 100) bg.lineBetween(0, y, WORLD_SIZE, y); const clouds = this.add.graphics(); for (let i = 0; i < 46; i++) { const x = Phaser.Math.Between(80, WORLD_SIZE - 80); const y = Phaser.Math.Between(80, WORLD_SIZE - 80); const r = Phaser.Math.Between(24, 62); clouds.fillStyle(0xbcd2df, Phaser.Math.FloatBetween(0.025, 0.08)); clouds.fillCircle(x, y, r); clouds.fillCircle(x + r * 0.55, y + 8, r * 0.7); clouds.fillCircle(x - r * 0.45, y + 5, r * 0.6); } const island = this.add.graphics(); island.fillStyle(0x5d694e, 1); island.fillEllipse(430, 480, 230, 150); island.fillStyle(0xa18a62, 1); island.fillEllipse(430, 470, 190, 105); island.fillStyle(0x283827, 1); island.fillCircle(390, 440, 18); island.fillCircle(455, 450, 14); island.fillCircle(490, 425, 12); const island2 = this.add.graphics(); island2.fillStyle(0x5d694e, 1); island2.fillEllipse(1940, 1840, 260, 165); island2.fillStyle(0xa18a62, 1); island2.fillEllipse(1940, 1830, 210, 112); } createTextures() { const playerShot = this.make.graphics({ x: 0, y: 0, add: false }); playerShot.fillStyle(0xffd36a, 1); playerShot.fillCircle(5, 5, 5); playerShot.lineStyle(1, 0xfff0b0, 1); playerShot.strokeCircle(5, 5, 4); playerShot.generateTexture('player-shot', 10, 10); playerShot.destroy(); const pirateShot = this.make.graphics({ x: 0, y: 0, add: false }); pirateShot.fillStyle(0xff6b62, 1); pirateShot.fillCircle(5, 5, 5); pirateShot.lineStyle(1, 0xffb0aa, 1); pirateShot.strokeCircle(5, 5, 4); pirateShot.generateTexture('pirate-shot', 10, 10); pirateShot.destroy(); } update(_time, deltaMs) { const dt = Math.min(deltaMs / 1000, 0.05); if (this.gameState !== 'playing') { if (Phaser.Input.Keyboard.JustDown(this.keys.restart)) this.scene.restart(); this.drawHud(); return; } this.processPlayerHits(); if (this.gameState !== 'playing') { this.drawHud(); return; } if (this.time.now - this.lastDiagnosticsAt >= 1000) { this.lastDiagnosticsAt = this.time.now; airshipLog('scene:heartbeat', { gameState: this.gameState, hull: this.playerHull, pendingHits: this.pendingPlayerHits.length, activePirates: this.pirates?.countActive?.(true) || 0, playerShots: this.playerShots?.countActive?.(true) || 0, pirateShots: this.pirateShots?.countActive?.(true) || 0, wave: this.wave, now: this.time.now, }, 'debug'); } this.updatePlayer(dt, deltaMs); this.updatePirates(dt); this.updateProjectiles(); this.drawWeaponArcs(); this.drawHud(); this.checkGameState(); } updatePlayer(dt, deltaMs) { if (!this.player?.body) return; if (this.cursors.up.isDown) this.playerSpeed += PLAYER_ACCELERATION * dt; if (this.cursors.down.isDown) this.playerSpeed -= PLAYER_BRAKE * dt; if (this.keys.stop.isDown) this.playerSpeed *= Math.pow(0.025, dt); if (!this.cursors.up.isDown && !this.cursors.down.isDown && !this.keys.stop.isDown) { this.playerSpeed *= Math.pow(0.985, deltaMs / 16.667); } this.playerSpeed = clamp(this.playerSpeed, PLAYER_REVERSE_SPEED, PLAYER_MAX_SPEED); const turnStrength = clamp(Math.abs(this.playerSpeed) / 100, 0.28, 1); const directionSign = this.playerSpeed < 0 ? -1 : 1; if (this.cursors.left.isDown) this.player.angle -= PLAYER_TURN_RATE * turnStrength * directionSign * dt; if (this.cursors.right.isDown) this.player.angle += PLAYER_TURN_RATE * turnStrength * directionSign * dt; const h = headingRad(this.player); this.player.setVelocity(Math.cos(h) * this.playerSpeed, Math.sin(h) * this.playerSpeed); if (Phaser.Input.Keyboard.JustDown(this.keys.firePort)) this.playerBroadside(-1); if (Phaser.Input.Keyboard.JustDown(this.keys.fireStarboard)) this.playerBroadside(1); } updatePirates(dt) { for (const pirate of this.pirates.getChildren()) { if (!pirate.active || !pirate.body) continue; const dx = this.player.x - pirate.x; const dy = this.player.y - pirate.y; const distance = Math.hypot(dx, dy); const bearing = bearingTo(pirate, this.player); let desiredHeading = bearing; if (distance < 360) { const optionA = normalizeDegrees(bearing + 90); const optionB = normalizeDegrees(bearing - 90); desiredHeading = Math.abs(signedAngleDelta(optionA, pirate.angle)) < Math.abs(signedAngleDelta(optionB, pirate.angle)) ? optionA : optionB; } const delta = signedAngleDelta(desiredHeading, pirate.angle); pirate.angle += clamp(delta, -PIRATE_TURN_RATE * dt, PIRATE_TURN_RATE * dt); const speed = distance > 430 ? PIRATE_SPEED : distance < 210 ? 72 : 96; const h = headingRad(pirate); pirate.setVelocity(Math.cos(h) * speed, Math.sin(h) * speed); const relative = signedAngleDelta(bearing, pirate.angle); const broadsideError = Math.abs(Math.abs(relative) - 90); const lastShot = pirate.getData('lastShot') || 0; if (distance < 420 && broadsideError < 38 && this.time.now - lastShot > 1650) { const side = relative >= 0 ? 1 : -1; this.fireBroadside(pirate, side, this.pirateShots, 'pirate-shot', PIRATE_SHOT_DAMAGE, 0.9); pirate.setData('lastShot', this.time.now); } } } playerBroadside(side) { if (this.time.now - this.lastPlayerShot < 850) return; this.fireBroadside(this.player, side, this.playerShots, 'player-shot', 16, 1.0); this.lastPlayerShot = this.time.now; } fireBroadside(ship, side, group, texture, damage, velocityScale) { if (!ship?.body) return; const actor = group === this.pirateShots ? 'pirate' : 'player'; const base = headingRad(ship) + side * Math.PI / 2; airshipLog('combat:broadside-fired', { actor, side: side < 0 ? 'port' : 'starboard', damage, x: Math.round(ship.x), y: Math.round(ship.y), angle: Math.round(ship.angle), }, 'debug'); const offsets = [-7, 0, 7]; for (const offset of offsets) { const angle = base + degToRad(offset); const x = ship.x + Math.cos(base) * 34; const y = ship.y + Math.sin(base) * 34; const shot = group.create(x, y, texture); shot.setDepth(8); shot.body.setAllowGravity(false); shot.setData('bornAt', this.time.now); shot.setData('damage', damage); shot.setVelocity( Math.cos(angle) * SHOT_SPEED * velocityScale + ship.body.velocity.x * 0.25, Math.sin(angle) * SHOT_SPEED * velocityScale + ship.body.velocity.y * 0.25, ); } } hitPirate(shot, pirate) { if (!shot?.active || !pirate?.active || this.gameState !== 'playing') return; const damage = shot.getData('damage') || 10; const { x, y } = shot; shot.destroy(); this.spawnExplosion(x, y, 0.32); const previousHull = pirate.getData('hull') || PIRATE_MAX_HULL; const hull = Math.max(0, previousHull - damage); pirate.setData('hull', hull); airshipLog('combat:pirate-hit', { damage, hullBefore: previousHull, hullAfter: hull, }); if (hull <= 0) { this.spawnExplosion(pirate.x, pirate.y, 1.25); pirate.destroy(); this.defeated += 1; if (this.pirates.countActive(true) === 0) { this.wave += 1; this.playerHull = Math.min(PLAYER_MAX_HULL, this.playerHull + 15); this.status.setText('WAVE CLEARED\n+15 HULL'); this.time.delayedCall(1100, () => { if (this.gameState === 'playing' && this.pirates.countActive(true) === 0) this.spawnWave(); }); } return; } this.flashShip(pirate, 0xffcccc); } hitPlayer(shot) { if (!shot?.active || this.gameState !== 'playing') return; if (shot.getData('playerHitQueued')) return; const damage = normalizePlayerDamage(shot.getData('damage'), PIRATE_SHOT_DAMAGE); // The Arcade overlap callback only records the collision. Hull mutation // happens from the scene update after the physics step; projectile cleanup // remains on the normal lifetime path to avoid collision-time lifecycle changes. shot.setData('playerHitQueued', true); this.pendingPlayerHits.push({ shot, damage }); airshipLog('combat:player-hit-queued', { damage, pendingHits: this.pendingPlayerHits.length, shotAgeMs: this.time.now - (shot.getData('bornAt') || this.time.now), hull: this.playerHull, }); } processPlayerHits() { if (!this.pendingPlayerHits.length || this.gameState !== 'playing') return; const hits = this.pendingPlayerHits; this.pendingPlayerHits = []; const beforeHull = this.playerHull; const result = processQueuedPlayerHits({ hull: this.playerHull, lastHitAt: this.lastPlayerHitAt, now: this.time.now, hits, cooldownMs: PLAYER_HIT_COOLDOWN_MS, maxHull: PLAYER_MAX_HULL, }); this.playerHull = result.hull; this.lastPlayerHitAt = result.lastHitAt; airshipLog('combat:player-hit-processed', { queuedHits: hits.length, appliedHits: result.appliedHits, ignoredHits: result.ignoredHits, damage: result.totalDamage, hullBefore: beforeHull, hullAfter: this.playerHull, gameOver: result.gameOver, now: this.time.now, }, result.gameOver ? 'warn' : 'info'); if (result.gameOver) this.finishGame(); } spawnExplosion(x, y, scale = 1) { const fx = this.add.sprite(x, y, 'explosions', 0).setDepth(20).setScale(scale); fx.play('impact-explosion'); fx.once(Phaser.Animations.Events.ANIMATION_COMPLETE, () => fx.destroy()); } flashShip(ship, tint) { ship.setTint(tint); this.time.delayedCall(90, () => { if (ship.active) ship.clearTint(); }); } updateProjectiles() { for (const group of [this.playerShots, this.pirateShots]) { for (const shot of group.getChildren()) { if (!shot.active) continue; if (this.time.now - shot.getData('bornAt') > SHOT_LIFETIME) shot.destroy(); } } } drawWeaponArcs() { this.arcGraphics.clear(); if (this.gameState !== 'playing') return; const radius = 190; const forward = headingRad(this.player); this.arcGraphics.lineStyle(2, 0xffc857, 0.28); for (const side of [-1, 1]) { const center = forward + side * Math.PI / 2; this.arcGraphics.beginPath(); this.arcGraphics.arc(this.player.x, this.player.y, radius, center - Math.PI / 4, center + Math.PI / 4); this.arcGraphics.strokePath(); } } nearestPirate() { let nearest = null; let nearestRange = Infinity; for (const pirate of this.pirates.getChildren()) { if (!pirate.active) continue; const range = Math.hypot(this.player.x - pirate.x, this.player.y - pirate.y); if (range < nearestRange) { nearest = pirate; nearestRange = range; } } return { pirate: nearest, range: nearestRange }; } drawHud() { const width = this.scale.width; const compact = width < 700; const heading = normalizeDegrees(this.player.angle); const activePirates = this.pirates?.countActive(true) || 0; const { pirate: nearest, range } = this.nearestPirate(); const nearestHull = nearest ? (nearest.getData('hull') || 0) : 0; const rangeText = Number.isFinite(range) ? `${range.toFixed(0)}m` : '--'; this.speedText.setText(`${Math.abs(this.playerSpeed).toFixed(0)} ${this.playerSpeed < 0 ? 'REV' : 'FWD'}`); this.headingText.setText(`${heading.toFixed(0).padStart(3, '0')}°`); this.waveText.setText(`WAVE ${this.wave} • ${activePirates} HOSTILE${activePirates === 1 ? '' : 'S'}`); this.combatText.setText(compact ? `K ${this.defeated}\n${rangeText}` : `DEFEATED ${this.defeated}\nRANGE ${rangeText}`); this.playerHullLabel.setText(`SPARROW ${this.playerHull}/${PLAYER_MAX_HULL}`); this.enemyHullLabel.setText(nearest ? `PIRATE ${nearestHull}/${PIRATE_MAX_HULL}` : `WAVE ${this.wave} CLEAR`); const side = 14; const gap = 14; const barY = compact ? 75 : 72; const barHeight = compact ? 8 : 10; const barWidth = (width - side * 2 - gap) / 2; const enemyX = side + barWidth + gap; this.hudGraphics.clear(); this.hudGraphics.fillStyle(0x071018, 0.78); this.hudGraphics.fillRect(0, 0, width, compact ? 96 : 98); this.hudGraphics.lineStyle(1, 0x8cd3ff, 0.15); this.hudGraphics.lineBetween(0, compact ? 96 : 98, width, compact ? 96 : 98); this.hudGraphics.fillStyle(0x17232d, 0.95); this.hudGraphics.fillRoundedRect(side, barY, barWidth, barHeight, 4); this.hudGraphics.fillRoundedRect(enemyX, barY, barWidth, barHeight, 4); this.hudGraphics.fillStyle(0x61c98b, 1); this.hudGraphics.fillRoundedRect(side, barY, barWidth * (this.playerHull / PLAYER_MAX_HULL), barHeight, 4); if (nearest) { this.hudGraphics.fillStyle(0xd65757, 1); this.hudGraphics.fillRoundedRect(enemyX, barY, barWidth * (nearestHull / PIRATE_MAX_HULL), barHeight, 4); } } checkGameState() { if (this.playerHull <= 0) this.finishGame(); } finishGame() { if (this.gameState !== 'playing') return; airshipLog('game:finish', { hull: this.playerHull, wave: this.wave, defeated: this.defeated, pendingHits: this.pendingPlayerHits?.length || 0, }, 'warn'); this.gameState = 'lost'; this.player.setVelocity(0, 0); this.playerShots.clear(true, true); this.pirateShots.clear(true, true); for (const pirate of this.pirates.getChildren()) { if (pirate?.body) pirate.setVelocity(0, 0); } this.spawnExplosion(this.player.x, this.player.y, 1.35); this.player.setVisible(false); this.status.setText('SHIP DESTROYED\nRestarting in 2s...'); this.time.delayedCall(AUTO_RESET_MS, () => { this.scene.restart(); }); } layoutHud(gameSize) { const width = gameSize.width; const height = gameSize.height; const compact = width < 700; if (this.headingText) this.headingText.setPosition(width / 2, 9); if (this.waveText) this.waveText.setPosition(width / 2, 32); if (this.combatText) this.combatText.setPosition(width - (compact ? 76 : 14), 11); if (this.enemyHullLabel) this.enemyHullLabel.setPosition(width - 14, compact ? 57 : 54); if (this.playerHullLabel) this.playerHullLabel.setPosition(14, compact ? 57 : 54); if (this.status) this.status.setPosition(width / 2, compact ? 104 : 108); if (this.controlsHint) { this.controlsHint.setPosition(width / 2, height - 12); this.controlsHint.setVisible(!compact); } } } new Phaser.Game({ type: Phaser.AUTO, parent: 'game', width: window.innerWidth, height: window.innerHeight, backgroundColor: '#132335', physics: { default: 'arcade', arcade: { debug: false }, }, scale: { mode: Phaser.Scale.RESIZE, autoCenter: Phaser.Scale.CENTER_BOTH, }, scene: GameScene, });