class Player { constructor(x, y, spriteSheet) { this.x = x; this.y = y; this.width = 48; // Scaled for larger canvas this.height = 68; // Scaled for larger canvas this.speed = 0.625; // Scaled speed this.direction = 'right'; // Jump physics this.velocityY = 0; this.velocityX = 0; this.gravity = 0.3; // Gravedad más suave this.jumpPower = -8; // Salto más suave this.airSpeed = 0.4; // Velocidad horizontal en el aire this.groundY = 500; // Ground level (adjusted for new canvas size) this.isOnGround = true; this.jumpPressed = false; // Para evitar saltos múltiples // Dash system this.dashCooldown = 0; this.isDashing = false; this.dashDuration = 0; this.dashSpeed = 8; this.lastKeyPresses = []; this.keyPressWindow = 300; // ms para detectar teclas rápidas (reducido) this.smokeParticles = []; // Vibration effect this.isVibrating = false; this.vibrationDuration = 0; this.vibrationIntensity = 2; this.originalX = 0; this.originalY = 0; // Key state tracking for proper alternation detection this.leftKeyPressed = false; this.rightKeyPressed = false; this.lastKeyReleased = null; // Teleport dash system this.teleportDashCooldown = 0; this.teleportDashDistance = 100; this.shiftPressed = false; // Animation this.frameIndex = 0; this.frameTimer = 0; this.idleFrameInterval = 60; // Más lento para idle this.walkFrameInterval = 15; // Más rápido para caminar this.isMoving = false; // Sprite coordinates from the 930x614 sprite sheet // Adjust these based on your actual sprite positions this.sprites = { idle: [ { x: 370, y: 15, w: 24, h: 34 }, { x: 341, y: 15, w: 24, h: 34 }, { x: 404, y: 15, w: 24, h: 34 }, { x: 341, y: 15, w: 24, h: 34 } ], walk: [ { x: 2, y: 16, w: 24, h: 34 }, { x: 28, y: 16, w: 24, h: 34 }, { x: 54, y: 16, w: 24, h: 34 }, { x: 80, y: 16, w: 24, h: 34 }, { x: 106, y: 16, w: 24, h: 34 }, { x: 132, y: 16, w: 24, h: 34 } ] }; this.currentAnimation = 'idle'; this.spriteSheet = spriteSheet; } update() { this.isMoving = false; // Detectar teclas rápidas para dash this.detectRapidKeyPresses(); // Actualizar dash cooldowns y vibración if (this.dashCooldown > 0) this.dashCooldown--; if (this.dashDuration > 0) this.dashDuration--; if (this.dashDuration <= 0) this.isDashing = false; if (this.teleportDashCooldown > 0) this.teleportDashCooldown--; if (this.vibrationDuration > 0) { this.vibrationDuration--; if (this.vibrationDuration <= 0) { this.isVibrating = false; } } // Detectar dash direccional con Shift if (input.isShiftPressed() && !this.shiftPressed && this.teleportDashCooldown <= 0) { if (input.isMovingLeft() || input.isMovingRight()) { this.performTeleportDash(input.isMovingLeft() ? 'left' : 'right'); } } this.shiftPressed = input.isShiftPressed(); // Horizontal movement con detección de alternancia mejorada let horizontalInput = 0; // Detectar cambios en el estado de las teclas const currentLeftPressed = input.isMovingLeft(); const currentRightPressed = input.isMovingRight(); // Registrar cuando se presiona una tecla nueva if (currentLeftPressed && !this.leftKeyPressed) { this.recordKeyPress('left'); } if (currentRightPressed && !this.rightKeyPressed) { this.recordKeyPress('right'); } // Actualizar estado de teclas this.leftKeyPressed = currentLeftPressed; this.rightKeyPressed = currentRightPressed; // Solo permitir movimiento si no se presionan ambas teclas a la vez if (currentLeftPressed && !currentRightPressed) { horizontalInput = -1; this.direction = 'left'; this.isMoving = true; } else if (currentRightPressed && !currentLeftPressed) { horizontalInput = 1; this.direction = 'right'; this.isMoving = true; } // Jump logic con control de una sola pulsación if (input.isJumping() && this.isOnGround && !this.jumpPressed) { this.velocityY = this.jumpPower; this.isOnGround = false; this.jumpPressed = true; // Salto direccional - agregar velocidad horizontal si se está moviendo if (horizontalInput !== 0) { this.velocityX = horizontalInput * 3; // Impulso horizontal al saltar } } // Resetear jumpPressed cuando se suelta la tecla if (!input.isJumping()) { this.jumpPressed = false; } // Movimiento horizontal con dash if (this.isDashing) { // Durante el dash, movimiento súper rápido this.x += this.direction === 'left' ? -this.dashSpeed : this.dashSpeed; this.createSmokeParticle(); } else { // Movimiento normal if (this.isOnGround) { this.x += horizontalInput * this.speed; this.velocityX *= 0.8; // Fricción en el suelo } else { // Movimiento en el aire - más limitado pero posible this.x += horizontalInput * this.airSpeed; } // Aplicar velocidad horizontal (para el salto direccional) this.x += this.velocityX; // Fricción del aire para velocidad horizontal this.velocityX *= 0.95; } // Actualizar partículas de humo this.updateSmokeParticles(); // Apply gravity this.velocityY += this.gravity; this.y += this.velocityY; // Ground collision if (this.y >= this.groundY) { this.y = this.groundY; this.velocityY = 0; this.isOnGround = true; } // Keep player within horizontal bounds (800px canvas width) this.x = Math.max(0, Math.min(800 - this.width, this.x)); // Update animation this.updateAnimation(); } updateAnimation() { const newAnimation = this.isMoving ? 'walk' : 'idle'; if (this.currentAnimation !== newAnimation) { this.currentAnimation = newAnimation; this.frameIndex = 0; this.frameTimer = 0; } this.frameTimer++; const frames = this.sprites[this.currentAnimation]; // Usar diferentes velocidades según la animación const frameInterval = this.currentAnimation === 'walk' ? this.walkFrameInterval : this.idleFrameInterval; if (this.frameTimer >= frameInterval) { this.frameTimer = 0; this.frameIndex = (this.frameIndex + 1) % frames.length; } } draw(ctx) { const frame = this.sprites[this.currentAnimation][this.frameIndex]; // Calcular posición con vibración let drawX = this.x; let drawY = this.y; if (this.isVibrating) { drawX += (Math.random() - 0.5) * this.vibrationIntensity; drawY += (Math.random() - 0.5) * this.vibrationIntensity; // Crear humo continuo durante la vibración if (Math.random() < 0.3) { this.createHeadSmokeParticle(); } } ctx.save(); if (this.direction === 'left') { ctx.scale(-1, 1); ctx.drawImage( this.spriteSheet, frame.x, frame.y, frame.w, frame.h, -drawX - this.width, drawY, this.width, this.height ); } else { ctx.drawImage( this.spriteSheet, frame.x, frame.y, frame.w, frame.h, drawX, drawY, this.width, this.height ); } ctx.restore(); // Dibujar partículas de humo this.drawSmokeParticles(ctx); } recordKeyPress(direction) { const now = Date.now(); this.lastKeyPresses.push({ direction, time: now }); // Limpiar teclas antiguas this.lastKeyPresses = this.lastKeyPresses.filter(press => now - press.time < this.keyPressWindow ); } detectRapidKeyPresses() { if (this.dashCooldown > 0 || this.isDashing) return; const now = Date.now(); const recentPresses = this.lastKeyPresses.filter(press => now - press.time < this.keyPressWindow ); // Detectar alternancia rápida entre izquierda y derecha (simplificado) if (recentPresses.length >= 3) { let alternating = true; for (let i = 1; i < recentPresses.length; i++) { if (recentPresses[i].direction === recentPresses[i-1].direction) { alternating = false; break; } } if (alternating) { this.startLateralDash(); console.log('¡Dash lateral activado!'); } } } startLateralDash() { this.isDashing = true; this.dashDuration = 15; // frames this.dashCooldown = 60; // frames antes de poder hacer otro dash this.lastKeyPresses = []; // Limpiar historial // Activar vibración this.isVibrating = true; this.vibrationDuration = 45; // frames de vibración this.originalX = this.x; this.originalY = this.y; // Crear humo tipo fogata encima de la cabeza for (let i = 0; i < 15; i++) { this.createHeadSmokeParticle(); } } performTeleportDash(direction) { this.teleportDashCooldown = 90; // frames antes de poder hacer otro teleport dash // Calcular nueva posición const dashDistance = direction === 'left' ? -this.teleportDashDistance : this.teleportDashDistance; const newX = this.x + dashDistance; // Mantener dentro de los límites this.x = Math.max(0, Math.min(800 - this.width, newX)); // Crear efecto de humo en posición inicial y final const oldX = this.x - dashDistance; for (let i = 0; i < 12; i++) { // Humo en posición inicial this.smokeParticles.push({ x: oldX + this.width / 2 + (Math.random() - 0.5) * this.width, y: this.y + this.height / 2 + (Math.random() - 0.5) * this.height, vx: (Math.random() - 0.5) * 6, vy: (Math.random() - 0.5) * 6, life: 40, maxLife: 40, size: Math.random() * 12 + 6, type: 'normal' }); // Humo en posición final this.createSmokeParticle(); } console.log(`¡Dash direccional ${direction} activado!`); } createSmokeParticle() { this.smokeParticles.push({ x: this.x + this.width / 2 + (Math.random() - 0.5) * this.width, y: this.y + this.height / 2 + (Math.random() - 0.5) * this.height, vx: (Math.random() - 0.5) * 4, vy: (Math.random() - 0.5) * 4, life: 30, maxLife: 30, size: Math.random() * 8 + 4, type: 'normal' }); } createHeadSmokeParticle() { this.smokeParticles.push({ x: this.x + this.width / 2 + (Math.random() - 0.5) * 20, y: this.y - 10 + (Math.random() * 15), // Encima de la cabeza vx: (Math.random() - 0.5) * 2, vy: -Math.random() * 3 - 1, // Hacia arriba como humo life: 60, maxLife: 60, size: Math.random() * 6 + 3, type: 'head' }); } updateSmokeParticles() { for (let i = this.smokeParticles.length - 1; i >= 0; i--) { const particle = this.smokeParticles[i]; particle.x += particle.vx; particle.y += particle.vy; particle.life--; particle.vx *= 0.98; particle.vy *= 0.98; if (particle.life <= 0) { this.smokeParticles.splice(i, 1); } } } drawSmokeParticles(ctx) { ctx.save(); for (const particle of this.smokeParticles) { const alpha = particle.life / particle.maxLife; ctx.globalAlpha = alpha * 0.7; // Diferentes colores según el tipo de humo if (particle.type === 'head') { // Humo de cabeza - más amarillento como fogata const intensity = Math.floor(255 * alpha); ctx.fillStyle = `rgb(${intensity}, ${Math.floor(intensity * 0.8)}, ${Math.floor(intensity * 0.3)})`; } else { // Humo normal - blanco ctx.fillStyle = '#ffffff'; } ctx.beginPath(); ctx.arc(particle.x, particle.y, particle.size * alpha, 0, Math.PI * 2); ctx.fill(); } ctx.restore(); } }