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
This commit is contained in:
2026-05-23 16:51:51 +07:00
parent ea848c8923
commit d3f880d917
12 changed files with 175 additions and 249 deletions

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}