| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- class Player {
- constructor(x, y, spriteSheet) {
- this.x = x;
- this.y = y;
- this.width = 24; // Original size
- this.height = 34; // Original size
- this.speed = 0.3125; // Original speed
- this.direction = 'right';
-
- // Jump physics
- this.velocityY = 0;
- this.velocityX = 0;
- this.gravity = 0.4; // Gravedad más suave
- this.jumpPower = -7; // Salto más suave
- this.airSpeed = 0.4; // Velocidad horizontal en el aire
- this.groundY = 176; // Ground level (original for 320x240)
- 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 = 50;
- 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.crouchFrameInterval = 30; // Intervalo para animación de sentarse
- this.isMoving = false;
- this.isCrouching = false;
- this.stayingCrouched = false; // Para mantener el estado de agachado
-
- // Sprite coordinates from the 930x614 sprite sheet
- 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 }
- ],
- crouch: [
- { x: 287, y: 16, w: 24, h: 34 },
- { x: 313, y: 16, w: 24, h: 34 }
- ]
- };
-
- this.currentAnimation = 'idle';
- this.spriteSheet = spriteSheet;
- }
-
- update() {
- this.isMoving = false;
-
- // Detectar estado de agacharse
- if (input.isCrouching() && !this.stayingCrouched) {
- this.stayingCrouched = true;
- }
-
- // Salir del estado agachado si se presionan teclas de movimiento o salto
- if (this.stayingCrouched && (input.isMovingLeft() || input.isMovingRight() || input.isJumping())) {
- this.stayingCrouched = false;
- }
-
- this.isCrouching = this.stayingCrouched;
-
- // 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 y no está agachado
- if (!this.isCrouching) {
- 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 (no saltar si está agachado)
- if (input.isJumping() && this.isOnGround && !this.jumpPressed && !this.isCrouching) {
- 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 (320px canvas width)
- this.x = Math.max(0, Math.min(320 - this.width, this.x));
-
- // Update animation
- this.updateAnimation();
- }
-
- updateAnimation() {
- let newAnimation;
-
- if (this.isCrouching) {
- newAnimation = 'crouch';
- } else if (this.isMoving) {
- newAnimation = 'walk';
- } else {
- newAnimation = '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
- let frameInterval;
- if (this.currentAnimation === 'walk') {
- frameInterval = this.walkFrameInterval;
- } else if (this.currentAnimation === 'crouch') {
- frameInterval = this.crouchFrameInterval;
- } else {
- frameInterval = 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();
- }
- }
|