From d3f880d917eb45e6b4263b6eb9e1b578f9afc2b9 Mon Sep 17 00:00:00 2001 From: AnRil Date: Sat, 23 May 2026 16:51:51 +0700 Subject: [PATCH] Sprint 0: bug fixes and cleanup - Fix Rocket/PropellerHat hitboxes (relative to sprite size, not magic offsets) - Spring now destroys itself on use with squash animation instead of staying invisible - Guard all powerups against double-trigger via consumed flag - Save genesis glow tween reference for clean destroy - Player.die() disables collisions (no more post-death platform bounces) - Replace hardcoded enemy spawn cap 0.5 with DIFFICULTY.maxEnemyRate - Enemy spawn position now anchored to platform X (-60/+60), not random - Powerup spawn clamped to screen bounds - Skip powerup spawn on breaking platforms (was unfair) - ScoreManager.addPoints() public method; remove direct score mutation - HUD elements use setScrollFactor(0) instead of per-frame repositioning - All magic numbers extracted to SCORE / PHYSICS / POWERUP_DURATION config - Remove dead code: showDeathVignette, drawDebug, createParticles, deathOverlay - Remove dead constants: PLATFORM_WIDTH/HEIGHT, PLAYER_MAX_SPEED, tileBias - Remove SUBMIT TO CHAIN fake button, replace with Coming Soon text - Safer cleanup loop: collect-then-destroy instead of mutating during iterate - gwei particles now have setScrollFactor(0.3) for parallax depth --- src/config/game.config.js | 34 ++++++-- src/entities/Platform.js | 23 ++++-- src/entities/Player.js | 23 ++++-- src/entities/PropellerHat.js | 13 +-- src/entities/Rocket.js | 22 +++-- src/entities/Spring.js | 18 ++-- src/main.js | 11 ++- src/managers/PlatformManager.js | 66 +++++++-------- src/managers/ScoreManager.js | 56 ++++++------- src/scenes/BootScene.js | 7 -- src/scenes/GameOverScene.js | 9 +- src/scenes/GameScene.js | 142 ++++---------------------------- 12 files changed, 175 insertions(+), 249 deletions(-) diff --git a/src/config/game.config.js b/src/config/game.config.js index 57e79da..856fb5f 100644 --- a/src/config/game.config.js +++ b/src/config/game.config.js @@ -3,17 +3,14 @@ export const GAME_HEIGHT = 854; export const GRAVITY = 1200; export const JUMP_VELOCITY = -650; -export const SUPER_JUMP_VELOCITY = -950; +export const SUPER_JUMP_VELOCITY = -1050; export const ROCKET_VELOCITY = -1400; export const PROPELLER_VELOCITY = -950; export const PLAYER_SPEED = 300; -export const PLAYER_MAX_SPEED = 400; export const PLATFORM_GAP_MIN = 60; export const PLATFORM_GAP_MAX = 120; -export const PLATFORM_WIDTH = 80; -export const PLATFORM_HEIGHT = 24; export const SPAWN_RATES = { stable: 0.60, @@ -23,14 +20,12 @@ export const SPAWN_RATES = { }; export const POWERUP_RATES = { - none: 0.91, spring: 0.04, propeller: 0.025, - rocket: 0.015, + rocket: 0.012, }; export const ENEMY_RATES = { - none: 0.90, bug: 0.10, }; @@ -39,5 +34,28 @@ export const DIFFICULTY = { gapIncreasePer1000: 12, maxGap: 160, enemyIncreasePer1000: 0.015, - maxEnemyRate: 0.25, + maxEnemyRate: 0.30, +}; + +export const SCORE = { + basePoints: 10, + genesisBonus: 50, + genesisJumps: 5, + genesisMultiplier: 2, + stompBonus: 25, + comboStep: 0.1, + comboMax: 3, + fallResetDistance: 300, +}; + +export const PHYSICS = { + stompTolerance: 12, + coyoteTime: 110, + jumpBufferTime: 130, + variableJumpCutoff: 0.45, +}; + +export const POWERUP_DURATION = { + propeller: 3500, + rocket: 3000, }; diff --git a/src/entities/Platform.js b/src/entities/Platform.js index 155d6e6..648444d 100644 --- a/src/entities/Platform.js +++ b/src/entities/Platform.js @@ -4,17 +4,16 @@ export class Platform extends Physics.Arcade.Sprite { constructor(scene, x, y, type = 'stable') { super(scene, x, y, 'platform'); scene.add.existing(this); - scene.physics.add.existing(this, true); // static body by default + scene.physics.add.existing(this, true); this.platformType = type; - this.breakingState = 0; this.moveSpeed = 0; this.moveRange = 0; this.startX = x; + this.glowTween = null; if (type === 'moving') { - // Convert to dynamic for movement this.body.destroy(); this.body = new Phaser.Physics.Arcade.Body(scene.physics.world, this); this.body.allowGravity = false; @@ -22,12 +21,12 @@ export class Platform extends Physics.Arcade.Sprite { this.moveSpeed = Phaser.Math.Between(50, 120) * (Math.random() < 0.5 ? 1 : -1); this.moveRange = Phaser.Math.Between(60, 160); } else if (type === 'breaking') { - this.setTint(0x999999); // grey tint for broken look + this.setTint(0x999999); } else if (type === 'genesis') { - this.setTint(0xffd700); // gold + this.setTint(0xffd700); this.genesisGlow = scene.add.ellipse(x, y + 10, 100, 30, 0xffd700, 0.25) .setDepth(this.depth - 1); - scene.tweens.add({ + this.glowTween = scene.tweens.add({ targets: this.genesisGlow, scaleX: 1.4, scaleY: 1.4, @@ -63,7 +62,7 @@ export class Platform extends Physics.Arcade.Sprite { duration: 250, onComplete: () => this.destroy(), }); - return false; + return true; } return true; } @@ -73,8 +72,14 @@ export class Platform extends Physics.Arcade.Sprite { } destroy(fromScene) { - if (this.glowTween) this.glowTween.stop(); - if (this.genesisGlow) this.genesisGlow.destroy(); + if (this.glowTween) { + this.glowTween.stop(); + this.glowTween = null; + } + if (this.genesisGlow) { + this.genesisGlow.destroy(); + this.genesisGlow = null; + } super.destroy(fromScene); } } diff --git a/src/entities/Player.js b/src/entities/Player.js index ff91108..81d1414 100644 --- a/src/entities/Player.js +++ b/src/entities/Player.js @@ -2,10 +2,10 @@ import { Physics } from 'phaser'; import { GAME_WIDTH, JUMP_VELOCITY, - SUPER_JUMP_VELOCITY, ROCKET_VELOCITY, PROPELLER_VELOCITY, PLAYER_SPEED, + POWERUP_DURATION, } from '../config/game.config.js'; export class Player extends Physics.Arcade.Sprite { @@ -18,7 +18,7 @@ export class Player extends Physics.Arcade.Sprite { this.setScale(0.45); this.body.setSize(70, 90); - this.state = 'normal'; // normal, propeller, rocket, dead + this.state = 'normal'; this.propellerTimer = 0; this.rocketTimer = 0; } @@ -31,11 +31,9 @@ export class Player extends Physics.Arcade.Sprite { if (cursors.right.isDown || wasd.right.isDown || touchRight) velocityX = PLAYER_SPEED; this.setVelocityX(velocityX); - // Screen wrap if (this.x < -this.width / 2) this.x = GAME_WIDTH + this.width / 2; if (this.x > GAME_WIDTH + this.width / 2) this.x = -this.width / 2; - // Tilt sprite based on movement if (velocityX < 0) { this.setFlipX(true); this.setAngle(-5); @@ -46,7 +44,6 @@ export class Player extends Physics.Arcade.Sprite { this.setAngle(0); } - // Power-up timers if (this.state === 'propeller') { this.propellerTimer -= delta; this.setVelocityY(PROPELLER_VELOCITY); @@ -59,9 +56,8 @@ export class Player extends Physics.Arcade.Sprite { } jump(force = JUMP_VELOCITY) { - if (this.state === 'dead' || this.state === 'rocket' || this.state === 'propeller') return; + if (this.state === 'dead' || this.state === 'rocket' || this.state === 'propeller') return false; this.setVelocityY(force); - // Squash animation this.scene.tweens.add({ targets: this, scaleX: 0.55, @@ -70,12 +66,20 @@ export class Player extends Physics.Arcade.Sprite { yoyo: true, ease: 'Quad.easeOut', }); + return true; + } + + cutJump(factor) { + if (this.state !== 'normal') return; + if (this.body.velocity.y < 0) { + this.setVelocityY(this.body.velocity.y * factor); + } } startPropeller() { if (this.state === 'dead') return; this.state = 'propeller'; - this.propellerTimer = 3500; + this.propellerTimer = POWERUP_DURATION.propeller; const oldHeight = this.displayHeight; this.setTexture('player_propeller'); this.setScale(0.45); @@ -86,7 +90,7 @@ export class Player extends Physics.Arcade.Sprite { startRocket() { if (this.state === 'dead') return; this.state = 'rocket'; - this.rocketTimer = 4000; + this.rocketTimer = POWERUP_DURATION.rocket; const oldHeight = this.displayHeight; this.setTexture('player_rocket'); this.setScale(0.52); @@ -111,6 +115,7 @@ export class Player extends Physics.Arcade.Sprite { this.setScale(0.4); this.setAngle(0); this.body.setSize(70, 90); + this.body.checkCollision.none = true; this.setVelocity(0, -250); this.body.allowGravity = true; } diff --git a/src/entities/PropellerHat.js b/src/entities/PropellerHat.js index ed789bf..8004cb6 100644 --- a/src/entities/PropellerHat.js +++ b/src/entities/PropellerHat.js @@ -7,15 +7,18 @@ export class PropellerHat extends Physics.Arcade.Sprite { scene.physics.add.existing(this, true); this.setScale(0.45); - this.body.setSize(50, 40); - this.body.setOffset(48, 43); - - // No spin — static propeller hat + this.body.setSize(this.width * 0.7, this.height * 0.5); + this.body.setOffset(this.width * 0.15, this.height * 0.25); + this.consumed = false; } onPlayerTouch(player) { - this.destroy(); + if (this.consumed) return false; + this.consumed = true; + this.body.enable = false; + this.setVisible(false); player.startPropeller(); + this.destroy(); return true; } } diff --git a/src/entities/Rocket.js b/src/entities/Rocket.js index 02ab527..d6776ed 100644 --- a/src/entities/Rocket.js +++ b/src/entities/Rocket.js @@ -7,11 +7,11 @@ export class Rocket extends Physics.Arcade.Sprite { scene.physics.add.existing(this, true); this.setScale(0.45); - this.body.setSize(80, 100, false); - this.body.setOffset(59, 91); + this.body.setSize(this.width * 0.5, this.height * 0.7); + this.body.setOffset(this.width * 0.25, this.height * 0.15); + this.consumed = false; - // Float animation - scene.tweens.add({ + this.floatTween = scene.tweens.add({ targets: this, y: y - 8, duration: 600, @@ -22,8 +22,20 @@ export class Rocket extends Physics.Arcade.Sprite { } onPlayerTouch(player) { - this.destroy(); + if (this.consumed) return false; + this.consumed = true; + this.body.enable = false; + this.setVisible(false); player.startRocket(); + this.destroy(); return true; } + + destroy(fromScene) { + if (this.floatTween) { + this.floatTween.stop(); + this.floatTween = null; + } + super.destroy(fromScene); + } } diff --git a/src/entities/Spring.js b/src/entities/Spring.js index a701394..05e0f38 100644 --- a/src/entities/Spring.js +++ b/src/entities/Spring.js @@ -9,15 +9,21 @@ export class Spring extends Physics.Arcade.Sprite { this.setScale(1); this.body.setSize(20, 32); - this.active = true; + this.consumed = false; } onPlayerTouch(player) { - if (!this.active) return false; - this.active = false; - this.setVisible(false); - this.body.enable = false; - player.jump(SUPER_JUMP_VELOCITY); + if (this.consumed) return false; + this.consumed = true; + player.jump(SUPER_JUMP_VELOCITY, true); + this.scene.tweens.add({ + targets: this, + scaleY: 1.6, + duration: 80, + yoyo: true, + ease: 'Quad.easeOut', + onComplete: () => this.destroy(), + }); return true; } } diff --git a/src/main.js b/src/main.js index a8b0291..e2256c4 100644 --- a/src/main.js +++ b/src/main.js @@ -1,9 +1,9 @@ -import { Game, AUTO } from 'phaser'; +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 } from './config/game.config.js'; +import { GAME_WIDTH, GAME_HEIGHT, GRAVITY } from './config/game.config.js'; const config = { type: AUTO, @@ -12,15 +12,14 @@ const config = { height: GAME_HEIGHT, backgroundColor: '#0d001a', scale: { - mode: Phaser.Scale.FIT, - autoCenter: Phaser.Scale.CENTER_BOTH, + mode: Scale.FIT, + autoCenter: Scale.CENTER_BOTH, }, physics: { default: 'arcade', arcade: { - gravity: { y: 1200 }, + gravity: { y: GRAVITY }, debug: false, - tileBias: 64, }, }, scene: [BootScene, MenuScene, GameScene, GameOverScene], diff --git a/src/managers/PlatformManager.js b/src/managers/PlatformManager.js index d49fa87..ea07803 100644 --- a/src/managers/PlatformManager.js +++ b/src/managers/PlatformManager.js @@ -25,22 +25,19 @@ export class PlatformManager { this.spawnPlatform(difficultyLevel); } - // Cleanup below kill line — destroy when object's bottom crosses it - this.platforms.children.iterate((p) => { - if (p && p.y + p.displayHeight / 2 > killLine) { - p.destroy(); - } - }); - this.enemies.children.iterate((e) => { - if (e && e.y + e.displayHeight / 2 > killLine) { - e.destroy(); - } - }); - this.powerups.children.iterate((pu) => { - if (pu && pu.y + pu.displayHeight / 2 > killLine) { - pu.destroy(); + this.cleanupGroup(this.platforms, killLine); + this.cleanupGroup(this.enemies, killLine); + this.cleanupGroup(this.powerups, killLine); + } + + cleanupGroup(group, killLine) { + const toDestroy = []; + group.children.iterate((obj) => { + if (obj && obj.y + obj.displayHeight / 2 > killLine) { + toDestroy.push(obj); } }); + toDestroy.forEach((obj) => obj.destroy()); } spawnPlatform(difficultyLevel) { @@ -56,7 +53,7 @@ export class PlatformManager { const x = Phaser.Math.Between(60, GAME_WIDTH - 60); const rand = Math.random(); - let type = 'stable'; + let type; if (rand < SPAWN_RATES.stable) type = 'stable'; else if (rand < SPAWN_RATES.stable + SPAWN_RATES.moving) type = 'moving'; else if (rand < SPAWN_RATES.stable + SPAWN_RATES.moving + SPAWN_RATES.breaking) type = 'breaking'; @@ -65,47 +62,44 @@ export class PlatformManager { const platform = new Platform(this.scene, x, this.highestY, type); this.platforms.add(platform); - this.maybeSpawnPowerUp(x, this.highestY - 25); + if (type !== 'breaking') { + this.maybeSpawnPowerUp(x, this.highestY - 25); + } this.maybeSpawnEnemy(x, this.highestY, difficultyLevel); } - maybeSpawnPowerUp(x, y) { + maybeSpawnPowerUp(platformX, y) { const rand = Math.random(); - let type = 'none'; + let type = null; if (rand < POWERUP_RATES.spring) type = 'spring'; else if (rand < POWERUP_RATES.spring + POWERUP_RATES.propeller) type = 'propeller'; else if (rand < POWERUP_RATES.spring + POWERUP_RATES.propeller + POWERUP_RATES.rocket) type = 'rocket'; - if (type === 'none') return; + if (!type) return; - const offsetX = Phaser.Math.Between(-25, 25); + const x = Phaser.Math.Clamp(platformX + Phaser.Math.Between(-15, 15), 30, GAME_WIDTH - 30); if (type === 'spring') { - this.powerups.add(new Spring(this.scene, x + offsetX, y)); + this.powerups.add(new Spring(this.scene, x, y)); } else if (type === 'propeller') { - this.powerups.add(new PropellerHat(this.scene, x + offsetX, y - 35)); + this.powerups.add(new PropellerHat(this.scene, x, y - 35)); } else if (type === 'rocket') { - this.powerups.add(new Rocket(this.scene, x + offsetX, y - 45)); + this.powerups.add(new Rocket(this.scene, x, y - 45)); } } - maybeSpawnEnemy(x, y, difficultyLevel) { + maybeSpawnEnemy(platformX, platformY, difficultyLevel) { const enemyBonus = Math.min( Math.floor(difficultyLevel / 1000) * DIFFICULTY.enemyIncreasePer1000, DIFFICULTY.maxEnemyRate ); - const bugRate = Math.min(ENEMY_RATES.bug + enemyBonus, 0.5); - const rand = Math.random(); + const bugRate = Math.min(ENEMY_RATES.bug + enemyBonus, DIFFICULTY.maxEnemyRate); + if (Math.random() >= bugRate) return; - let type = 'none'; - if (rand < bugRate) type = 'bug'; + const offset = Phaser.Math.Between(-60, 60); + const ex = Phaser.Math.Clamp(platformX + offset, 50, GAME_WIDTH - 50); + const ey = platformY - Phaser.Math.Between(60, 130); - if (type === 'none') return; - - const ex = Phaser.Math.Between(50, GAME_WIDTH - 50); - const ey = y - Phaser.Math.Between(50, 140); - - // Ensure minimum distance from existing enemies - const minDist = 120; + const minDist = 150; let tooClose = false; this.enemies.children.iterate((e) => { if (e && Phaser.Math.Distance.Between(ex, ey, e.x, e.y) < minDist) { @@ -114,7 +108,7 @@ export class PlatformManager { }); if (tooClose) return; - this.enemies.add(new Enemy(this.scene, ex, ey, type)); + this.enemies.add(new Enemy(this.scene, ex, ey, 'bug')); } getPlatforms() { diff --git a/src/managers/ScoreManager.js b/src/managers/ScoreManager.js index fec3a70..92a55dd 100644 --- a/src/managers/ScoreManager.js +++ b/src/managers/ScoreManager.js @@ -1,3 +1,5 @@ +import { SCORE } from '../config/game.config.js'; + export class ScoreManager { constructor(scene) { this.scene = scene; @@ -5,56 +7,47 @@ export class ScoreManager { this.blockHeight = 0; this.combo = 0; this.comboMultiplier = 1; - this.lastPlatformY = null; this.genesisActive = false; this.genesisJumps = 0; - this.hudBg = scene.add.rectangle(0, 0, 200, 90, 0x000000, 0.5) + this.hudBg = scene.add.rectangle(10, 10, 200, 90, 0x000000, 0.5) .setOrigin(0) + .setScrollFactor(0) .setDepth(200); this.hudScore = scene.add.text(16, 16, 'Gas: 0', { fontFamily: '"Press Start 2P", monospace', fontSize: '14px', color: '#d8b4fe', - }).setOrigin(0).setDepth(200).setShadow(1, 1, '#000000', 2, false, true); + }).setOrigin(0).setScrollFactor(0).setDepth(200).setShadow(1, 1, '#000000', 2, false, true); this.hudBlocks = scene.add.text(16, 42, 'Block: 0', { fontFamily: '"Press Start 2P", monospace', fontSize: '12px', color: '#a855f7', - }).setOrigin(0).setDepth(200).setShadow(1, 1, '#000000', 2, false, true); + }).setOrigin(0).setScrollFactor(0).setDepth(200).setShadow(1, 1, '#000000', 2, false, true); this.hudCombo = scene.add.text(16, 66, '', { fontFamily: '"Press Start 2P", monospace', fontSize: '10px', color: '#22c55e', - }).setOrigin(0).setDepth(200).setShadow(1, 1, '#000000', 2, false, true); + }).setOrigin(0).setScrollFactor(0).setDepth(200).setShadow(1, 1, '#000000', 2, false, true); - this.hudBest = scene.add.text(scene.scale.width - 80, 16, 'Best: 0', { + this.hudBest = scene.add.text(scene.scale.width - 16, 16, 'Best: 0', { fontFamily: '"Press Start 2P", monospace', fontSize: '9px', - color: '#666', - }).setOrigin(1, 0).setDepth(200).setShadow(1, 1, '#000000', 2, false, true); + color: '#aaa', + }).setOrigin(1, 0).setScrollFactor(0).setDepth(200).setShadow(1, 1, '#000000', 2, false, true); this.updateBestDisplay(); } update(playerY) { - const cam = this.scene.cameras.main; - const width = this.scene.scale.width; - this.hudBg.setPosition(cam.scrollX + 10, cam.scrollY + 10); - this.hudScore.setPosition(cam.scrollX + 16, cam.scrollY + 16); - this.hudBlocks.setPosition(cam.scrollX + 16, cam.scrollY + 42); - this.hudCombo.setPosition(cam.scrollX + 16, cam.scrollY + 66); - this.hudBest.setPosition(cam.scrollX + width - 80, cam.scrollY + 16); - const blocks = Math.max(0, Math.floor((this.scene.scale.height - playerY) / 100)); if (blocks > this.blockHeight) { this.blockHeight = blocks; this.hudBlocks.setText(`Block: ${this.blockHeight}`); - // Milestone effect every 100 blocks if (this.blockHeight % 100 === 0 && this.blockHeight > 0) { this.showMilestone(this.blockHeight); } @@ -63,24 +56,23 @@ export class ScoreManager { onLand(platformY, platformType) { this.combo += 1; - this.comboMultiplier = Math.min(1 + (this.combo - 1) * 0.1, 3); + this.comboMultiplier = Math.min(1 + (this.combo - 1) * SCORE.comboStep, SCORE.comboMax); - let basePoints = 10; + let basePoints = SCORE.basePoints; if (platformType === 'genesis') { this.genesisActive = true; - this.genesisJumps = 5; - basePoints = 50; + this.genesisJumps = SCORE.genesisJumps; + basePoints = SCORE.genesisBonus; } let multiplier = this.comboMultiplier; if (this.genesisActive) { - multiplier *= 2; + multiplier *= SCORE.genesisMultiplier; this.genesisJumps--; if (this.genesisJumps <= 0) this.genesisActive = false; } - this.score += Math.floor(basePoints * multiplier); - this.hudScore.setText(`Gas: ${this.score}`); + this.addPoints(Math.floor(basePoints * multiplier)); if (this.combo > 1) { this.hudCombo.setText(`Combo x${this.comboMultiplier.toFixed(1)}`); @@ -88,6 +80,11 @@ export class ScoreManager { } } + addPoints(amount) { + this.score += amount; + this.hudScore.setText(`Gas: ${this.score}`); + } + onFall() { this.combo = 0; this.comboMultiplier = 1; @@ -98,17 +95,20 @@ export class ScoreManager { const { width, height } = this.scene.scale; const txt = this.scene.add.text(width / 2, height * 0.35, `EPOCH ${blocks} REACHED!`, { fontFamily: '"Press Start 2P", monospace', - fontSize: '18px', + fontSize: '20px', color: '#ffd700', - }).setOrigin(0.5).setScrollFactor(0).setDepth(200); + }).setOrigin(0.5).setScrollFactor(0).setDepth(200).setShadow(2, 2, '#7c2d12', 0, false, true); this.scene.tweens.add({ targets: txt, - scale: 1.4, + scale: 1.5, alpha: 0, - duration: 1500, + duration: 1800, + ease: 'Quad.easeOut', onComplete: () => txt.destroy(), }); + + if (this.scene.onMilestone) this.scene.onMilestone(blocks); } updateBestDisplay() { diff --git a/src/scenes/BootScene.js b/src/scenes/BootScene.js index 1476d9d..7c4b14e 100644 --- a/src/scenes/BootScene.js +++ b/src/scenes/BootScene.js @@ -17,10 +17,7 @@ export class BootScene extends Scene { } create() { - // Generate procedural textures this.createBackgrounds(); - this.createParticles(); - this.scene.start('MenuScene'); } @@ -115,8 +112,4 @@ export class BootScene extends Scene { springGfx.strokePath(); springGfx.generateTexture('spring', 20, 32); } - - createParticles() { - // Pre-create particle emitters config if needed - } } diff --git a/src/scenes/GameOverScene.js b/src/scenes/GameOverScene.js index 8a10717..cc20c45 100644 --- a/src/scenes/GameOverScene.js +++ b/src/scenes/GameOverScene.js @@ -64,9 +64,12 @@ export class GameOverScene extends Scene { this.scene.start('MenuScene'); }); - const submitBtn = this.createButton(width / 2, height * 0.92, 'SUBMIT TO CHAIN', () => {}); - submitBtn.bg.setAlpha(0.4); - submitBtn.label.setAlpha(0.4); + this.add.text(width / 2, height * 0.92, 'ON-CHAIN SUBMIT — COMING SOON', { + fontFamily: '"Press Start 2P", monospace', + fontSize: '9px', + color: '#666', + align: 'center', + }).setOrigin(0.5); } createButton(x, y, text, callback) { diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js index f6228f7..de7a55c 100644 --- a/src/scenes/GameScene.js +++ b/src/scenes/GameScene.js @@ -3,7 +3,7 @@ import { Player } from '../entities/Player.js'; import { Platform } from '../entities/Platform.js'; import { PlatformManager } from '../managers/PlatformManager.js'; import { ScoreManager } from '../managers/ScoreManager.js'; -import { GAME_WIDTH, GAME_HEIGHT, GRAVITY } from '../config/game.config.js'; +import { GAME_WIDTH, GAME_HEIGHT, SCORE, PHYSICS } from '../config/game.config.js'; export class GameScene extends Scene { constructor() { @@ -11,70 +11,46 @@ export class GameScene extends Scene { } create() { - this.physics.world.gravity.y = GRAVITY; - - // Background this.bg = this.add.tileSprite(GAME_WIDTH / 2, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, 'gridBg') .setScrollFactor(0) .setDepth(-10); - // Gwei particles this.createGweiParticles(); - // Input this.cursors = this.input.keyboard.createCursorKeys(); - this.wasd = this.input.keyboard.addKeys({ - up: 'W', down: 'S', left: 'A', right: 'D', - }); + this.wasd = this.input.keyboard.addKeys({ up: 'W', down: 'S', left: 'A', right: 'D' }); this.touchLeft = false; this.touchRight = false; this.setupTouchControls(); - // Start platform const startY = GAME_HEIGHT - 80; this.startPlatform = new Platform(this, GAME_WIDTH / 2, startY, 'stable'); this.startPlatform.setTint(0xa78bfa); - this.startPlatform.setAlpha(1); - // Player this.player = new Player(this, GAME_WIDTH / 2, startY - 140); this.lastJumpY = this.player.y; - // Player shadow/glow this.playerShadow = this.add.graphics(); this.playerShadow.setDepth(-4); - // Trail container for fast movement this.trailPoints = []; + this.trailGraphics = this.add.graphics().setDepth(-3); - // Managers this.platformManager = new PlatformManager(this); this.scoreManager = new ScoreManager(this); - // Pre-spawn platforms for (let i = 0; i < 10; i++) { this.platformManager.spawnPlatform(0); } - // Collisions this.physics.add.collider(this.player, this.startPlatform, this.handlePlatformCollision, this.platformCollisionFilter, this); this.physics.add.collider(this.player, this.platformManager.getPlatforms(), this.handlePlatformCollision, this.platformCollisionFilter, this); this.physics.add.overlap(this.player, this.platformManager.getPowerups(), this.handlePowerup, null, this); this.physics.add.overlap(this.player, this.platformManager.getEnemies(), this.handleEnemy, null, this); - // Camera this.cameras.main.setBounds(0, -999999, GAME_WIDTH, 999999 + GAME_HEIGHT); this.cameras.main.startFollow(this.player, true, 0, 0.05, 0, 180); - // Debug hitbox renderer - this.debugGraphics = this.add.graphics().setDepth(1000); - - // Death vignette overlay - this.deathOverlay = this.add.graphics(); - this.deathOverlay.setScrollFactor(0); - this.deathOverlay.setDepth(100); - this.deathOverlay.setAlpha(0); - this.isGameOver = false; this.difficultyLevel = 0; this.minScrollY = this.cameras.main.scrollY; @@ -85,44 +61,33 @@ export class GameScene extends Scene { this.player.update(this.cursors, this.wasd, this.touchLeft, this.touchRight, time, delta); - // Parallax background this.bg.tilePositionY = this.cameras.main.scrollY * 0.3; - // Kill line rises with camera but never falls back down this.minScrollY = Math.min(this.minScrollY, this.cameras.main.scrollY); const killLine = this.minScrollY + GAME_HEIGHT; - // Update difficulty const height = Math.max(0, GAME_HEIGHT - this.player.y); this.difficultyLevel = height; this.platformManager.update(this.difficultyLevel, killLine); this.scoreManager.update(this.player.y); - // Check death — same fixed kill line as platform cleanup if (this.player.body.bottom > killLine) { this.gameOver(); + return; } - // Reset combo if falling too far without landing - if (this.player.body.velocity.y > 0 && this.player.y > this.lastJumpY + 300) { + if (this.player.body.velocity.y > 0 && this.player.y > this.lastJumpY + SCORE.fallResetDistance) { this.scoreManager.onFall(); this.lastJumpY = this.player.y; } - // Update player shadow this.updatePlayerShadow(); - - // Update trail for fast movement this.updateTrail(); - - // Debug disabled - // this.drawDebug(killLine); } updatePlayerShadow() { this.playerShadow.clear(); if (!this.player.active) return; - const alpha = Math.max(0.05, 0.25 - (Math.abs(this.player.body.velocity.y) / 1200)); this.playerShadow.fillStyle(0x000000, alpha); this.playerShadow.fillCircle(this.player.x, this.player.y + 42, 16); @@ -146,11 +111,7 @@ export class GameScene extends Scene { } } - // Redraw trail - this.trailGraphics = this.trailGraphics || this.add.graphics(); this.trailGraphics.clear(); - this.trailGraphics.setDepth(-3); - for (const point of this.trailPoints) { const color = this.player.state === 'rocket' ? 0xff4444 : 0x44aaff; this.trailGraphics.fillStyle(color, point.alpha); @@ -161,7 +122,6 @@ export class GameScene extends Scene { platformCollisionFilter(player, platform) { if (!platform || !platform.body) return false; if (typeof platform.isBroken === 'function' && platform.isBroken()) return false; - // Only bounce when falling and player is above platform center return player.body.velocity.y > 0 && player.y < platform.y + 8; } @@ -181,9 +141,11 @@ export class GameScene extends Scene { if (powerup && typeof powerup.onPlayerTouch === 'function') { const px = powerup.x; const py = powerup.y; - powerup.onPlayerTouch(player); - this.createPowerupParticles(px, py); - this.flashScreen(); + const consumed = powerup.onPlayerTouch(player); + if (consumed) { + this.createPowerupParticles(px, py); + this.flashScreen(); + } } } @@ -200,14 +162,11 @@ export class GameScene extends Scene { enemy.destroy(); return; } - // Mario-style stomp: falling onto enemy kills it - const stompTolerance = 12; - if (player.body.velocity.y > 0 && player.body.bottom <= enemy.body.top + stompTolerance) { + if (player.body.velocity.y > 0 && player.body.bottom <= enemy.body.top + PHYSICS.stompTolerance) { this.createExplosion(enemy.x, enemy.y); enemy.destroy(); player.jump(); - this.scoreManager.score += 25; - this.scoreManager.hudScore.setText(`Gas: ${this.scoreManager.score}`); + this.scoreManager.addPoints(SCORE.stompBonus); this.createJumpParticles(player.x, player.y + player.displayHeight / 2 + 3); return; } @@ -219,7 +178,6 @@ export class GameScene extends Scene { this.isGameOver = true; this.player.die(); this.cameras.main.shake(300, 0.012); - // Small explosion at impact point instead of full-screen vignette const bx = ex ?? this.player.x; const by = ey ?? this.player.y; this.createExplosion(bx, by); @@ -233,76 +191,6 @@ export class GameScene extends Scene { }); } - drawDebug(camBottom) { - this.debugGraphics.clear(); - - // Kill line — magenta - this.debugGraphics.lineStyle(2, 0xff00ff, 0.7); - this.debugGraphics.lineBetween(0, camBottom, GAME_WIDTH, camBottom); - - // Player — red - if (this.player.active && this.player.body) { - this.debugGraphics.lineStyle(2, 0xff0000, 1); - this.debugGraphics.strokeRectShape(this.player.body); - } - - // Start platform — green - if (this.startPlatform && this.startPlatform.body) { - this.debugGraphics.lineStyle(2, 0x00ff00, 1); - this.debugGraphics.strokeRectShape(this.startPlatform.body); - } - - // Platforms — green - this.platformManager.getPlatforms().children.iterate((p) => { - if (p && p.body) { - this.debugGraphics.lineStyle(2, 0x00ff00, 1); - this.debugGraphics.strokeRectShape(p.body); - } - }); - - // Enemies — blue - this.platformManager.getEnemies().children.iterate((e) => { - if (e && e.body) { - this.debugGraphics.lineStyle(2, 0x0000ff, 1); - this.debugGraphics.strokeRectShape(e.body); - } - }); - - // Powerups — yellow - this.platformManager.getPowerups().children.iterate((pu) => { - if (pu && pu.body) { - this.debugGraphics.lineStyle(2, 0xffff00, 1); - this.debugGraphics.strokeRectShape(pu.body); - } - }); - } - - showDeathVignette() { - const cx = GAME_WIDTH / 2; - const cy = GAME_HEIGHT / 2; - const maxR = Math.max(GAME_WIDTH, GAME_HEIGHT); - - this.deathOverlay.clear(); - // Draw a radial gradient-like vignette using fewer circles - const steps = 5; - const band = (maxR * 0.8) / steps; - for (let i = steps; i >= 0; i--) { - const t = i / steps; - const r = 50 + t * (maxR * 0.8); - const alpha = 0.05 + (1 - t) * 0.55; - this.deathOverlay.lineStyle(band, 0x7f0000, alpha); - this.deathOverlay.strokeCircle(cx, cy, r); - } - this.deathOverlay.setAlpha(0); - - this.tweens.add({ - targets: this.deathOverlay, - alpha: 1, - duration: 400, - ease: 'Quad.easeOut', - }); - } - flashScreen() { const flash = this.add.rectangle(GAME_WIDTH / 2, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, 0xffffff, 0.3); flash.setScrollFactor(0); @@ -316,7 +204,7 @@ export class GameScene extends Scene { } setupTouchControls() { - const { width, height } = this.scale; + const { width } = this.scale; this.input.on('pointerdown', (pointer) => { if (pointer.x < width / 2) this.touchLeft = true; else this.touchRight = true; @@ -339,7 +227,7 @@ export class GameScene extends Scene { Phaser.Math.Between(0, GAME_WIDTH), Phaser.Math.Between(0, GAME_HEIGHT), 'gwei' - ).setAlpha(0.5); + ).setAlpha(0.5).setScrollFactor(0.3).setDepth(-8); const scene = this; this.tweens.add({ targets: p, @@ -349,7 +237,7 @@ export class GameScene extends Scene { repeat: -1, delay: Phaser.Math.Between(0, 4000), onRepeat: function() { - p.y = scene.cameras.main.scrollY + GAME_HEIGHT + 20; + p.y = Phaser.Math.Between(0, GAME_HEIGHT); p.x = Phaser.Math.Between(0, GAME_WIDTH); p.setAlpha(0.5); },