diff --git a/src/entities/Player.js b/src/entities/Player.js index 44e7eee..82fdbcc 100644 --- a/src/entities/Player.js +++ b/src/entities/Player.js @@ -67,26 +67,36 @@ export class Player extends Physics.Arcade.Sprite { jump(force = JUMP_VELOCITY) { if (this.state === 'dead' || this.state === 'rocket' || this.state === 'propeller') return false; this.setVelocityY(force); - this.scene.tweens.add({ + // Squash-and-stretch, but never let two squash tweens stack on rapid + // bounces (that made the sprite scale pop and look like it was breaking up). + if (this.squashTween) this.squashTween.stop(); + this.setScale(0.45); + this.squashTween = this.scene.tweens.add({ targets: this, scaleX: 0.55, - scaleY: 0.4, - duration: 80, + scaleY: 0.38, + duration: 90, yoyo: true, ease: 'Quad.easeOut', + onComplete: () => { + this.squashTween = null; + this.setScale(0.45); + }, }); return true; } - cutJump(factor) { - if (this.state !== 'normal') return; - if (this.body.velocity.y < 0) { - this.setVelocityY(this.body.velocity.y * factor); + _stopSquash() { + if (this.squashTween) { + this.squashTween.stop(); + this.squashTween = null; } + this.setScale(0.45); } startPropeller() { if (this.state === 'dead') return; + this._stopSquash(); this.state = 'propeller'; this.propellerTimer = POWERUP_DURATION.propeller; const oldHeight = this.displayHeight; @@ -98,6 +108,7 @@ export class Player extends Physics.Arcade.Sprite { startRocket() { if (this.state === 'dead') return; + this._stopSquash(); this.state = 'rocket'; this.rocketTimer = POWERUP_DURATION.rocket; const oldHeight = this.displayHeight; @@ -108,6 +119,7 @@ export class Player extends Physics.Arcade.Sprite { } endPowerUp() { + this._stopSquash(); const oldHeight = this.displayHeight; this.state = 'normal'; this.setTexture('player_idle'); @@ -119,6 +131,7 @@ export class Player extends Physics.Arcade.Sprite { die() { if (this.state === 'dead') return; + this._stopSquash(); this.state = 'dead'; this.setTexture('player_dead'); this.setScale(0.4); diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js index f6edb01..46cb942 100644 --- a/src/scenes/GameScene.js +++ b/src/scenes/GameScene.js @@ -97,6 +97,10 @@ export class GameScene extends Scene { this.escKey.on('down', () => this.togglePause()); + // Move the camera after physics has synced the sprite (POST_UPDATE) so the + // player never lags the camera by a frame on fast vertical movement. + this.events.on('postupdate', this.lateUpdate, this); + // Auto-pause when the tab/window is hidden — avoids a delta-spike teleport // on refocus. Player must resume manually (not auto-resumed). this._onHidden = () => { @@ -114,6 +118,19 @@ export class GameScene extends Scene { this.game.events.off('hidden', this._onHidden); this._onHidden = null; } + this.events.off('postupdate', this.lateUpdate, this); + } + + // Runs after the physics step (sprite positions already synced this frame). + lateUpdate() { + if (this.isGameOver || this.isPaused || !this.player) return; + // Doodle-jump camera: latch the player at the trigger line going up; never + // scroll back down. + const targetScrollY = this.player.y - this.cameraTriggerY; + if (targetScrollY < this.cameras.main.scrollY) { + this.cameras.main.scrollY = targetScrollY; + } + this.bg.tilePositionY = Math.round(this.cameras.main.scrollY * 0.3); } update(time, delta) { @@ -127,14 +144,9 @@ export class GameScene extends Scene { this.player.setVelocityY(PHYSICS.maxFallSpeed); } - // Camera: latch player at trigger line going up, free movement otherwise. - const targetScrollY = this.player.y - this.cameraTriggerY; - if (targetScrollY < this.cameras.main.scrollY) { - this.cameras.main.scrollY = targetScrollY; - } - - this.bg.tilePositionY = Math.round(this.cameras.main.scrollY * 0.3); - + // NOTE: camera following happens in lateUpdate (POST_UPDATE) — after the + // physics step syncs the sprite — otherwise the camera lags the player by + // one frame and fast ascent/rocket looks jittery. this.minScrollY = Math.min(this.minScrollY, this.cameras.main.scrollY); const killLine = this.minScrollY + GAME_HEIGHT;