1class Player extends Phaser.Physics.Arcade.Sprite { 2 3 // --- Physics Constants (Adjust these!) --- 4 MOVE_SPEED = 320; // Max horizontal speed (scaled from 2.2) 5 ACCELERATION = 1050; // Horizontal acceleration (scaled from 0.10, needs to be higher) 6 DRAG = 1600; // Horizontal drag (scaled from 0.10, friction/deceleration) 7 JUMP_VELOCITY = -620; // Vertical jump velocity (scaled/interpreted from 10.0) 8 GRAVITY = 1450; // World gravity (scaled from 0.35) 9 AIR_CONTROL_FACTOR = 0.71; // How much control in air (scaled from 0.25) 10 ATTACK_DURATION = 300; // Duration of the attack animation (adjust based on frameRate) 11 HITBOX_OFFSET_X = 45; // Horizontal offset - RE-EVALUATE BASED ON SCALE/ANIM 12 HITBOX_OFFSET_Y = -10; // Vertical offset - RE-EVALUATE 13 HITBOX_WIDTH = 75; // Width - RE-EVALUATE 14 HITBOX_HEIGHT = 50; // Height - RE-EVALUATE 15 // Baseline frame height captured from first frame; used to keep collider bottom constant across differently trimmed frames 16 baseFrameHeight = null; 17 // ---------------------------------------- 18 19 // --- Scaling Factor --- 20 SPRITE_SCALE = 0.4; // Enlarged scale 21 22 // --- Collider Tuning (edit here!) --- 23 IDLE_WIDTH_MULT = 0.60; // body width when not attacking 24 ATTACK_WIDTH_MULT = 0.28; // narrower body while attacking frames are wider 25 HEIGHT_MULT = 1; // shorter body so feet sit on ground (0.80â0.90 range) 26 FEET_FUDGE = -25; // Downward adjustment so shoes touch ground 27 // -------------------- 28 29 constructor(scene, x, y) { // Remove texture default 30 console.log("Player Constructor Start"); 31 // Use the atlas key and the initial idle frame 32 super(scene, x, y, 'player_atlas', 'idle.png'); 33 console.log("Player super() called with player_atlas"); 34 35 scene.add.existing(this); 36 console.log("Player scene.add.existing called"); 37 scene.physics.add.existing(this); 38 console.log("Player scene.physics.add.existing called"); 39 40 this.isHoldingLoan = false; // Initialize here 41 42 // --- Set Origin and Scale FIRST --- 43 this.setOrigin(0.5, 0.5); // Set origin to center for better alignment 44 this.setScale(this.SPRITE_SCALE); 45 console.log(`Player scale set to: ${this.SPRITE_SCALE}, Origin set to (0.5,0.5)`); 46 47 // --- Physics Setup --- 48 this.setGravityY(this.GRAVITY); 49 this.setDragX(this.DRAG); 50 this.setMaxVelocity(this.MOVE_SPEED, Math.abs(this.JUMP_VELOCITY * 2)); 51 this.setCollideWorldBounds(true); 52 this.body.onWorldBounds = true; 53 54 // Store baseline trimmed frame height (idle frame) once 55 this.baseFrameHeight = this.frame.height; 56 57 // Configure physics body using shared utility, computing vertical adjust dynamically 58 const widthMult = this.isAttacking ? this.ATTACK_WIDTH_MULT : this.IDLE_WIDTH_MULT; 59 const heightMult = this.HEIGHT_MULT; 60 const verticalAdjust = ((this.frame.height - this.baseFrameHeight) / 2) - this.FEET_FUDGE; 61 applyBodyFromFrame(this, { widthMult, heightMult, verticalAdjust, debug: window.APPLY_BODY_DEBUG }); 62 63 // Extra per-frame debug so we can see collider consistency without 64 // having to toggle anything from the console. 65 if (window.APPLY_BODY_DEBUG) { 66 console.log(`[PlayerCollider] frame=${this.frame.name} fh=${this.frame.height} ` + 67 `base=${this.baseFrameHeight} vertAdj=${verticalAdjust.toFixed(1)} ` + 68 `offY=${this.body.offset.y.toFixed(1)} onFloor=${this.body.onFloor()}`); 69 } 70 71 console.log("Player Physics Setup Done"); 72 73 // --- Input Setup --- 74 this.cursors = scene.input.keyboard.createCursorKeys(); 75 this.keyA = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A); 76 this.keyD = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D); 77 this.keySpace = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); 78 console.log("Player Input Setup Done"); 79 80 this.movementEnabled = true; 81 this.isAttacking = false;
82 this.attackTimer = null; // Keep timer for now, maybe switch to anim complete later 83 84 // --- Guitar Hitbox --- 85 console.log("Creating Guitar Hitbox..."); 86 this.guitarHitbox = scene.add.rectangle(0, 0, this.HITBOX_WIDTH, this.HITBOX_HEIGHT, 0xff0000, 0.5) 87 .setOrigin(0.5) 88 .setVisible(false); 89 console.log("Guitar Hitbox Rectangle Created (No Physics Body)"); 90 91 // --- Attack Input --- 92 scene.input.on('pointerdown', this.attack, this); 93 console.log("Player Attack Input Listener Added (global canvas tap)"); 94 95 // --- Start Idle Animation --- 96 this.play('player_idle'); 97 98 console.log("Player initialized with keys, attack input, and atlas."); 99 100 this.jumpKeyHeld = false; 101 this.hasJumped = false; 102 } 103 104 disableMovement() { 105 this.movementEnabled = false; 106 this.setVelocityX(0); 107 this.play('player_idle'); // Go to idle when disabled 108 console.log("Player movement DISABLED"); 109 } 110 111 enableMovement() { 112 this.movementEnabled = true; 113 console.log("Player movement ENABLED"); 114 } 115 116 // --- NEW: Methods for Aave Loan State --- 117 startHoldingLoan() { 118 this.isHoldingLoan = true; 119 this.play('player_aave_loan', true); // Play and ignore if already playing 120 this.updateBodyOffset(); // Ensure body is correct for this sprite 121 console.log("Player started holding Aave loan"); 122 } 123 124 stopHoldingLoan() { // Call this if the loan state can end 125 this.isHoldingLoan = false; 126 // The regular animation logic in update() will take over 127 console.log("Player stopped holding Aave loan"); 128 } 129 // --- END NEW --- 130 131 attack() { 132 const onGround = this.body.blocked.down || this.body.touching.down; 133 if (!this.movementEnabled || this.isAttacking /*|| !onGround*/) { 134 return; 135 } 136 137 console.log("Player attacking!"); 138 this.isAttacking = true; 139 this.play('player_attack', true); // Play attack animation, ignoreIfPlaying = true 140 141 // --- Play Swing Guitar Sound --- 142 const soundManager = this.scene.registry.get('soundManager'); 143 if (soundManager) { 144 soundManager.playSound('swingGuitarSound', { volume: 0.1 }); 145 } else { 146 console.warn('Player: SoundManager not found in scene registry. Cannot play swing guitar sound.'); 147 } 148 // --- End Sound --- 149 150 // Recalculate body offset for attack animation 151 this.updateBodyOffset(); 152 153 // Hitbox positioning logic is moved to update for continuous positioning 154 155 // Reset attack state after duration (could also use animation complete event) 156 if (this.attackTimer) this.attackTimer.remove(); 157 this.attackTimer = this.scene.time.delayedCall(this.ATTACK_DURATION, () => { 158 this.isAttacking = false; 159 this.guitarHitbox.setVisible(false); 160 // Don't force idle here, let update() handle the next state 161 // console.log("Attack finished."); 162 }, [], this); 163 164 // Make hitbox visible immediately when attack starts 165 // Position will be set in update 166 const hitboxX = this.x + (this.flipX ? -this.HITBOX_OFFSET_X : this.HITBOX_OFFSET_X); 167 const hitboxY = this.y + this.HITBOX_OFFSET_Y; 168 this.guitarHitbox.setPosition(hitboxX, hitboxY); 169 this.checkHitboxOverlap(); // Check overlap while attacking 170 171 // Re-apply body offset each attack frame 172 this.updateBodyOffset(); 173 } 174 175 checkHitboxOverlap() { 176 if (!this.scene.enemyManager) { 177 return; 178 } 179 const enemyGroup = this.scene.enemyManager.getGroup(); 180 const hitboxGeom = this.guitarHitbox.getBounds(); 181 182 enemyGroup.children.each(enemy => { 183 if (enemy.active && !enemy.isHit && Phaser.Geom.Intersects.RectangleToRectangle(hitboxGeom, enemy.getBounds())) { 184 console.log("Hitbox overlapped with enemy!"); 185 this.handleEnemyHit(enemy); 186 } 187 }); 188 } 189 190 handleEnemyHit(enemy) { 191 if (!enemy || typeof enemy.takeHit !== 'function') return; 192 193 const hitFromLeft = this.x < enemy.x; 194 enemy.takeHit(hitFromLeft); 195 196 // --- Play Hit Sound --- 197 const soundManager = this.scene.registry.get('soundManager'); 198 if (soundManager) { 199 soundManager.playSound('playerHitSound', { volume: 0.2 }); 200 } else { 201 console.warn('Player: SoundManager not found in scene registry. Cannot play hit sound.'); 202 } 203 // --- End Hit Sound --- 204 205 const scene = this.scene; 206 207 // 1. Camera shake for impact 208 if (scene.cameras && scene.cameras.main) { 209 scene.cameras.main.shake(120, 0.008); 210 } 211 212 // 2. Quick white flash on enemy 213 enemy.setTintFill(0xffffff); 214 setTimeout(() => enemy.clearTint(), 40); 215 216 // 3. Dust/spark particle burst 217 if (!scene.textures.exists('hit-dust')) { 218 const gfx = scene.add.graphics({ x: 0, y: 0, add: false }); 219 gfx.fillStyle(0xffffff, 1); 220 gfx.fillCircle(4, 4, 4); 221 gfx.generateTexture('hit-dust', 8, 8); 222 gfx.destroy(); 223 } 224 const emitter = scene.add.particles(enemy.x, enemy.y, 'hit-dust', { 225 speed: { min: -120, max: 120 }, 226 angle: { min: 0, max: 360 }, 227 lifespan: 350, 228 quantity: 15, 229 scale: { start: 0.7, end: 0 }, 230 alpha: { start: 1, end: 0 }, 231 gravityY: 300 232 }); 233 emitter.setDepth(enemy.depth + 1); 234 scene.time.delayedCall(400, () => emitter.destroy()); 235 236 // 4. Floating score popup 237 const popup = scene.add.text(enemy.x, enemy.y - enemy.displayHeight / 2, '+100', { 238 fontFamily: '"Press Start 2P", monospace', 239 fontSize: '14px', 240 fill: '#ffffff', 241 stroke: '#000', 242 strokeThickness: 4 243 }).setOrigin(0.5).setDepth(enemy.depth + 2); 244 scene.tweens.add({ 245 targets: popup, 246 y: popup.y - 30, 247 alpha: 0,
248 duration: 800, 249 ease: 'Cubic.easeOut', 250 onComplete: () => popup.destroy() 251 }); 252 253 // (Hit-stop removed temporarily to avoid game freeze) 254 255 console.log("Enemy hit by guitar!"); 256 } 257 258 update(time, delta) { 259 const onGround = this.body.blocked.down || this.body.touching.down; 260 const touch = this.scene.game.touchControls ? this.scene.game.touchControls.directions : {}; 261 262 // --- Handle Attack Input (from touch controls / mann.cool virtual controller) --- 263 if (touch.action && !this.isAttacking) { 264 this.attack(); 265 } 266 267 // --- Handle Movement/Input (only if enabled) --- 268 if (this.movementEnabled) { 269 const currentAcceleration = onGround ? this.ACCELERATION : this.ACCELERATION * this.AIR_CONTROL_FACTOR; 270 271 if (this.cursors.left.isDown || this.keyA.isDown || touch.left) { 272 this.setAccelerationX(-currentAcceleration); 273 this.setFlipX(true); 274 } else if (this.cursors.right.isDown || this.keyD.isDown || touch.right) { 275 this.setAccelerationX(currentAcceleration); 276 this.setFlipX(false); 277 } else { 278 this.setAccelerationX(0); 279 } 280 281 // --- Variable Jump Height --- 282 const jumpPressed = this.cursors.up.isDown || this.keySpace.isDown || touch.up; 283 if (onGround && jumpPressed && !this.jumpKeyHeld) { 284 this.setVelocityY(this.JUMP_VELOCITY); 285 this.jumpKeyHeld = true; 286 this.hasJumped = true; 287 288 // --- Play Jump Sound --- 289 const soundManager = this.scene.registry.get('soundManager'); 290 if (soundManager) { 291 soundManager.playSound('playerJumpSound', { volume: 0.1 }); 292 } else { 293 console.warn('Player: SoundManager not found in scene registry. Cannot play jump sound.'); 294 } 295 } 296 if (!jumpPressed) { 297 this.jumpKeyHeld = false; 298 } 299 // If jump key released early and player is still moving up, cut velocity for short hop 300 if (this.hasJumped && !jumpPressed && this.body.velocity.y < 0) { 301 this.setVelocityY(this.body.velocity.y * 0.45); // Cut upward velocity for short hop 302 this.hasJumped = false; 303 } 304 if (onGround) { 305 this.hasJumped = false; 306 } 307 if (!onGround && this.body.blocked.left && this.body.velocity.x < 0) { 308 this.setAccelerationX(0); 309 } 310 if (!onGround && this.body.blocked.right && this.body.velocity.x > 0) { 311 this.setAccelerationX(0); 312 } 313 } 314 315 // --- Animation Control --- 316 // Priority: Aave Loan > Attack > Jump > Walk > Idle 317 if (this.isHoldingLoan) { 318 this.play('player_aave_loan', true); // Ensure loan animation stays if active 319 // Recalculate body offset for loan animation 320 this.updateBodyOffset(); 321 } else if (this.isAttacking) { 322 // Attack animation is already playing, do nothing here 323 // Ensure hitbox stays positioned correctly 324 const hitboxX = this.x + (this.flipX ? -this.HITBOX_OFFSET_X : this.HITBOX_OFFSET_X); 325 const hitboxY = this.y + this.HITBOX_OFFSET_Y; 326 this.guitarHitbox.setPosition(hitboxX, hitboxY); 327 this.checkHitboxOverlap(); // Check overlap while attacking 328 329 // Re-apply body offset each attack frame 330 this.updateBodyOffset(); 331 } else if (!onGround) { 332 this.play('player_jump', true); // Play jump animation if in the air (and not attacking) 333 // Recalculate body offset for jump animation 334 this.updateBodyOffset(); 335 } else if (this.body.velocity.x !== 0) { 336 this.play('player_walk', true); // Play walk animation if moving horizontally on ground 337 // Recalculate body offset for walk animation 338 this.updateBodyOffset(); 339 } else { 340 this.play('player_idle', true); // Play idle animation if on ground and not moving 341 // Recalculate body offset for idle animation 342 this.updateBodyOffset(); 343 } 344 345 // --- Update Hitbox Position (even if not attacking, just keep it hidden) --- 346 // We position it during attack state now, and hide it otherwise via the attack t
346imer. 347 // If not attacking, ensure it's hidden (belt and suspenders) 348 // if (!this.isAttacking && this.guitarHitbox.visible) { 349 // this.guitarHitbox.setVisible(false); 350 // } 351 352 // Note: checkHitboxOverlap() is now called only when isAttacking is true. 353 } 354 355 handleCollision(otherObject) { 356 // console.log('Player collided with:', otherObject); 357 } 358 359 // Helper method to update body offset based on current animation frame 360 updateBodyOffset() { 361 const widthMult = this.isAttacking ? this.ATTACK_WIDTH_MULT : this.IDLE_WIDTH_MULT; 362 const heightMult = this.HEIGHT_MULT; 363 const verticalAdjust = ((this.frame.height - this.baseFrameHeight) / 2) - this.FEET_FUDGE; 364 applyBodyFromFrame(this, { widthMult, heightMult, verticalAdjust, debug: window.APPLY_BODY_DEBUG }); 365 } 366} 367 368// Make class available for import (if using modules later) 369// export default Player;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.