import TilePropertyHelper from '../systems/TilePropertyHelper.js'; export default class Player { /** * @param {Phaser.Scene} scene * @param {number} x World pixel x (center) * @param {number} y World pixel y (center) * @param {Phaser.Tilemaps.TilemapLayer} laddersLayer */ constructor(scene, x, y, laddersLayer) { this._scene = scene; this._laddersLayer = laddersLayer; this.lives = 3; this.onLadder = false; this.atLadderTop = false; this._enteredFromTop = false; // Create physics sprite this._sprite = scene.physics.add.sprite(x, y, 'player', 0); this._sprite.setCollideWorldBounds(false); // Body is narrower/shorter than the 128x256 frame to fit the character artwork this._sprite.body.setSize(80, 240).setOffset(24, 8); // Animations scene.anims.create({ key: 'walk', frames: scene.anims.generateFrameNumbers('player', { start: 0, end: 1 }), frameRate: 8, repeat: -1, }); scene.anims.create({ key: 'stand', frames: [{ key: 'player', frame: 0 }], frameRate: 1, repeat: 0, }); this._cursors = scene.input.keyboard.createCursorKeys(); this._jumpKey = this._cursors.up; } get sprite() { return this._sprite; } get body() { return this._sprite.body; } update(time, delta) { this._handleLadder(); this._handleMovement(); this._updateAnimation(); } _handleLadder() { const sprite = this._sprite; const cursors = this._cursors; const body = sprite.body; // Tile at player center (for body-of-ladder detection) const centerTile = this._laddersLayer.getTileAtWorldXY(sprite.x, sprite.y); // Tile just below body bottom (body bottom = sprite.y + 120; +2 puts us inside // the tile when standing on it, since physics places body bottom at tile top) const feetTile = this._laddersLayer.getTileAtWorldXY(sprite.x, sprite.y + 122); const centerIsLadder = centerTile && TilePropertyHelper.isLadder(centerTile.index); const feetIsLadderTop = feetTile && TilePropertyHelper.isLadderTop(feetTile.index); // Update atLadderTop: feet are resting on a top rung and not actively climbing this.atLadderTop = !!(feetIsLadderTop && !this.onLadder); if (this.onLadder) { // Move up or down if (cursors.up.isDown) { sprite.body.setVelocityY(-200); } else if (cursors.down.isDown) { sprite.body.setVelocityY(200); } else { sprite.body.setVelocityY(0); } // Exit ladder when pressing left or right if (cursors.left.isDown || cursors.right.isDown) { this._exitLadder(); return; } // Once the center reaches a ladder tile we are fully on the ladder if (centerIsLadder) { this._enteredFromTop = false; } // Exit if center is no longer over a ladder tile. // Exception: when we entered from the top the center starts above the ladder; // suppress the exit until the player descends into the rungs, or until // they press up (changing their mind). if (!centerIsLadder && (!this._enteredFromTop || cursors.up.isDown)) { this._exitLadder(); } } else { // Enter ladder when player center overlaps a ladder tile and pressing up/down if (centerIsLadder && (cursors.up.isDown || cursors.down.isDown)) { this.onLadder = true; sprite.body.setGravityY(-800); // Cancels world gravity (800 + -800 = 0) sprite.body.setVelocityY(0); sprite.body.setVelocityX(0); // Snap horizontally to ladder center sprite.x = centerTile.pixelX + 64; // Enter from the top: standing on the top rung tile, pressing down } else if (feetIsLadderTop && cursors.down.isDown && body.blocked.down) { this._enteredFromTop = true; this.onLadder = true; sprite.body.setGravityY(-800); sprite.body.setVelocityY(200); sprite.body.setVelocityX(0); sprite.x = feetTile.pixelX + 64; } } } _exitLadder() { this.onLadder = false; this._enteredFromTop = false; this._sprite.body.setGravityY(0); // Remove local gravity override; world gravity resumes } _handleMovement() { if (this.onLadder) return; const sprite = this._sprite; const cursors = this._cursors; const body = sprite.body; // Horizontal movement if (cursors.left.isDown) { body.setVelocityX(-250); sprite.setFlipX(true); } else if (cursors.right.isDown) { body.setVelocityX(250); sprite.setFlipX(false); } else { body.setVelocityX(0); } // Jump: only on key press (JustDown prevents hold-to-jump repeat) // Allowed when: grounded AND (not on ladder OR standing at ladder top) if (Phaser.Input.Keyboard.JustDown(this._jumpKey)) { if (body.blocked.down) { body.setVelocityY(-550); } } } _updateAnimation() { const sprite = this._sprite; if (this.onLadder) { sprite.anims.stop(); sprite.setFrame(0); return; } if (sprite.body.velocity.x !== 0) { sprite.anims.play('walk', true); } else { sprite.anims.play('stand', true); } } }