player.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. class Player {
  2. constructor(x, y, spriteSheet) {
  3. this.x = x;
  4. this.y = y;
  5. this.width = 48; // Scaled for larger canvas
  6. this.height = 68; // Scaled for larger canvas
  7. this.speed = 0.625; // Scaled speed
  8. this.direction = 'right';
  9. // Jump physics
  10. this.velocityY = 0;
  11. this.velocityX = 0;
  12. this.gravity = 0.3; // Gravedad más suave
  13. this.jumpPower = -8; // Salto más suave
  14. this.airSpeed = 0.4; // Velocidad horizontal en el aire
  15. this.groundY = 500; // Ground level (adjusted for new canvas size)
  16. this.isOnGround = true;
  17. this.jumpPressed = false; // Para evitar saltos múltiples
  18. // Dash system
  19. this.dashCooldown = 0;
  20. this.isDashing = false;
  21. this.dashDuration = 0;
  22. this.dashSpeed = 8;
  23. this.lastKeyPresses = [];
  24. this.keyPressWindow = 300; // ms para detectar teclas rápidas (reducido)
  25. this.smokeParticles = [];
  26. // Vibration effect
  27. this.isVibrating = false;
  28. this.vibrationDuration = 0;
  29. this.vibrationIntensity = 2;
  30. this.originalX = 0;
  31. this.originalY = 0;
  32. // Key state tracking for proper alternation detection
  33. this.leftKeyPressed = false;
  34. this.rightKeyPressed = false;
  35. this.lastKeyReleased = null;
  36. // Teleport dash system
  37. this.teleportDashCooldown = 0;
  38. this.teleportDashDistance = 100;
  39. this.shiftPressed = false;
  40. // Animation
  41. this.frameIndex = 0;
  42. this.frameTimer = 0;
  43. this.idleFrameInterval = 60; // Más lento para idle
  44. this.walkFrameInterval = 15; // Más rápido para caminar
  45. this.isMoving = false;
  46. // Sprite coordinates from the 930x614 sprite sheet
  47. // Adjust these based on your actual sprite positions
  48. this.sprites = {
  49. idle: [
  50. { x: 370, y: 15, w: 24, h: 34 },
  51. { x: 341, y: 15, w: 24, h: 34 },
  52. { x: 404, y: 15, w: 24, h: 34 },
  53. { x: 341, y: 15, w: 24, h: 34 }
  54. ],
  55. walk: [
  56. { x: 2, y: 16, w: 24, h: 34 },
  57. { x: 28, y: 16, w: 24, h: 34 },
  58. { x: 54, y: 16, w: 24, h: 34 },
  59. { x: 80, y: 16, w: 24, h: 34 },
  60. { x: 106, y: 16, w: 24, h: 34 },
  61. { x: 132, y: 16, w: 24, h: 34 }
  62. ]
  63. };
  64. this.currentAnimation = 'idle';
  65. this.spriteSheet = spriteSheet;
  66. }
  67. update() {
  68. this.isMoving = false;
  69. // Detectar teclas rápidas para dash
  70. this.detectRapidKeyPresses();
  71. // Actualizar dash cooldowns y vibración
  72. if (this.dashCooldown > 0) this.dashCooldown--;
  73. if (this.dashDuration > 0) this.dashDuration--;
  74. if (this.dashDuration <= 0) this.isDashing = false;
  75. if (this.teleportDashCooldown > 0) this.teleportDashCooldown--;
  76. if (this.vibrationDuration > 0) {
  77. this.vibrationDuration--;
  78. if (this.vibrationDuration <= 0) {
  79. this.isVibrating = false;
  80. }
  81. }
  82. // Detectar dash direccional con Shift
  83. if (input.isShiftPressed() && !this.shiftPressed && this.teleportDashCooldown <= 0) {
  84. if (input.isMovingLeft() || input.isMovingRight()) {
  85. this.performTeleportDash(input.isMovingLeft() ? 'left' : 'right');
  86. }
  87. }
  88. this.shiftPressed = input.isShiftPressed();
  89. // Horizontal movement con detección de alternancia mejorada
  90. let horizontalInput = 0;
  91. // Detectar cambios en el estado de las teclas
  92. const currentLeftPressed = input.isMovingLeft();
  93. const currentRightPressed = input.isMovingRight();
  94. // Registrar cuando se presiona una tecla nueva
  95. if (currentLeftPressed && !this.leftKeyPressed) {
  96. this.recordKeyPress('left');
  97. }
  98. if (currentRightPressed && !this.rightKeyPressed) {
  99. this.recordKeyPress('right');
  100. }
  101. // Actualizar estado de teclas
  102. this.leftKeyPressed = currentLeftPressed;
  103. this.rightKeyPressed = currentRightPressed;
  104. // Solo permitir movimiento si no se presionan ambas teclas a la vez
  105. if (currentLeftPressed && !currentRightPressed) {
  106. horizontalInput = -1;
  107. this.direction = 'left';
  108. this.isMoving = true;
  109. } else if (currentRightPressed && !currentLeftPressed) {
  110. horizontalInput = 1;
  111. this.direction = 'right';
  112. this.isMoving = true;
  113. }
  114. // Jump logic con control de una sola pulsación
  115. if (input.isJumping() && this.isOnGround && !this.jumpPressed) {
  116. this.velocityY = this.jumpPower;
  117. this.isOnGround = false;
  118. this.jumpPressed = true;
  119. // Salto direccional - agregar velocidad horizontal si se está moviendo
  120. if (horizontalInput !== 0) {
  121. this.velocityX = horizontalInput * 3; // Impulso horizontal al saltar
  122. }
  123. }
  124. // Resetear jumpPressed cuando se suelta la tecla
  125. if (!input.isJumping()) {
  126. this.jumpPressed = false;
  127. }
  128. // Movimiento horizontal con dash
  129. if (this.isDashing) {
  130. // Durante el dash, movimiento súper rápido
  131. this.x += this.direction === 'left' ? -this.dashSpeed : this.dashSpeed;
  132. this.createSmokeParticle();
  133. } else {
  134. // Movimiento normal
  135. if (this.isOnGround) {
  136. this.x += horizontalInput * this.speed;
  137. this.velocityX *= 0.8; // Fricción en el suelo
  138. } else {
  139. // Movimiento en el aire - más limitado pero posible
  140. this.x += horizontalInput * this.airSpeed;
  141. }
  142. // Aplicar velocidad horizontal (para el salto direccional)
  143. this.x += this.velocityX;
  144. // Fricción del aire para velocidad horizontal
  145. this.velocityX *= 0.95;
  146. }
  147. // Actualizar partículas de humo
  148. this.updateSmokeParticles();
  149. // Apply gravity
  150. this.velocityY += this.gravity;
  151. this.y += this.velocityY;
  152. // Ground collision
  153. if (this.y >= this.groundY) {
  154. this.y = this.groundY;
  155. this.velocityY = 0;
  156. this.isOnGround = true;
  157. }
  158. // Keep player within horizontal bounds (800px canvas width)
  159. this.x = Math.max(0, Math.min(800 - this.width, this.x));
  160. // Update animation
  161. this.updateAnimation();
  162. }
  163. updateAnimation() {
  164. const newAnimation = this.isMoving ? 'walk' : 'idle';
  165. if (this.currentAnimation !== newAnimation) {
  166. this.currentAnimation = newAnimation;
  167. this.frameIndex = 0;
  168. this.frameTimer = 0;
  169. }
  170. this.frameTimer++;
  171. const frames = this.sprites[this.currentAnimation];
  172. // Usar diferentes velocidades según la animación
  173. const frameInterval = this.currentAnimation === 'walk' ? this.walkFrameInterval : this.idleFrameInterval;
  174. if (this.frameTimer >= frameInterval) {
  175. this.frameTimer = 0;
  176. this.frameIndex = (this.frameIndex + 1) % frames.length;
  177. }
  178. }
  179. draw(ctx) {
  180. const frame = this.sprites[this.currentAnimation][this.frameIndex];
  181. // Calcular posición con vibración
  182. let drawX = this.x;
  183. let drawY = this.y;
  184. if (this.isVibrating) {
  185. drawX += (Math.random() - 0.5) * this.vibrationIntensity;
  186. drawY += (Math.random() - 0.5) * this.vibrationIntensity;
  187. // Crear humo continuo durante la vibración
  188. if (Math.random() < 0.3) {
  189. this.createHeadSmokeParticle();
  190. }
  191. }
  192. ctx.save();
  193. if (this.direction === 'left') {
  194. ctx.scale(-1, 1);
  195. ctx.drawImage(
  196. this.spriteSheet,
  197. frame.x, frame.y, frame.w, frame.h,
  198. -drawX - this.width, drawY,
  199. this.width, this.height
  200. );
  201. } else {
  202. ctx.drawImage(
  203. this.spriteSheet,
  204. frame.x, frame.y, frame.w, frame.h,
  205. drawX, drawY,
  206. this.width, this.height
  207. );
  208. }
  209. ctx.restore();
  210. // Dibujar partículas de humo
  211. this.drawSmokeParticles(ctx);
  212. }
  213. recordKeyPress(direction) {
  214. const now = Date.now();
  215. this.lastKeyPresses.push({ direction, time: now });
  216. // Limpiar teclas antiguas
  217. this.lastKeyPresses = this.lastKeyPresses.filter(press =>
  218. now - press.time < this.keyPressWindow
  219. );
  220. }
  221. detectRapidKeyPresses() {
  222. if (this.dashCooldown > 0 || this.isDashing) return;
  223. const now = Date.now();
  224. const recentPresses = this.lastKeyPresses.filter(press =>
  225. now - press.time < this.keyPressWindow
  226. );
  227. // Detectar alternancia rápida entre izquierda y derecha (simplificado)
  228. if (recentPresses.length >= 3) {
  229. let alternating = true;
  230. for (let i = 1; i < recentPresses.length; i++) {
  231. if (recentPresses[i].direction === recentPresses[i-1].direction) {
  232. alternating = false;
  233. break;
  234. }
  235. }
  236. if (alternating) {
  237. this.startLateralDash();
  238. console.log('¡Dash lateral activado!');
  239. }
  240. }
  241. }
  242. startLateralDash() {
  243. this.isDashing = true;
  244. this.dashDuration = 15; // frames
  245. this.dashCooldown = 60; // frames antes de poder hacer otro dash
  246. this.lastKeyPresses = []; // Limpiar historial
  247. // Activar vibración
  248. this.isVibrating = true;
  249. this.vibrationDuration = 45; // frames de vibración
  250. this.originalX = this.x;
  251. this.originalY = this.y;
  252. // Crear humo tipo fogata encima de la cabeza
  253. for (let i = 0; i < 15; i++) {
  254. this.createHeadSmokeParticle();
  255. }
  256. }
  257. performTeleportDash(direction) {
  258. this.teleportDashCooldown = 90; // frames antes de poder hacer otro teleport dash
  259. // Calcular nueva posición
  260. const dashDistance = direction === 'left' ? -this.teleportDashDistance : this.teleportDashDistance;
  261. const newX = this.x + dashDistance;
  262. // Mantener dentro de los límites
  263. this.x = Math.max(0, Math.min(800 - this.width, newX));
  264. // Crear efecto de humo en posición inicial y final
  265. const oldX = this.x - dashDistance;
  266. for (let i = 0; i < 12; i++) {
  267. // Humo en posición inicial
  268. this.smokeParticles.push({
  269. x: oldX + this.width / 2 + (Math.random() - 0.5) * this.width,
  270. y: this.y + this.height / 2 + (Math.random() - 0.5) * this.height,
  271. vx: (Math.random() - 0.5) * 6,
  272. vy: (Math.random() - 0.5) * 6,
  273. life: 40,
  274. maxLife: 40,
  275. size: Math.random() * 12 + 6,
  276. type: 'normal'
  277. });
  278. // Humo en posición final
  279. this.createSmokeParticle();
  280. }
  281. console.log(`¡Dash direccional ${direction} activado!`);
  282. }
  283. createSmokeParticle() {
  284. this.smokeParticles.push({
  285. x: this.x + this.width / 2 + (Math.random() - 0.5) * this.width,
  286. y: this.y + this.height / 2 + (Math.random() - 0.5) * this.height,
  287. vx: (Math.random() - 0.5) * 4,
  288. vy: (Math.random() - 0.5) * 4,
  289. life: 30,
  290. maxLife: 30,
  291. size: Math.random() * 8 + 4,
  292. type: 'normal'
  293. });
  294. }
  295. createHeadSmokeParticle() {
  296. this.smokeParticles.push({
  297. x: this.x + this.width / 2 + (Math.random() - 0.5) * 20,
  298. y: this.y - 10 + (Math.random() * 15), // Encima de la cabeza
  299. vx: (Math.random() - 0.5) * 2,
  300. vy: -Math.random() * 3 - 1, // Hacia arriba como humo
  301. life: 60,
  302. maxLife: 60,
  303. size: Math.random() * 6 + 3,
  304. type: 'head'
  305. });
  306. }
  307. updateSmokeParticles() {
  308. for (let i = this.smokeParticles.length - 1; i >= 0; i--) {
  309. const particle = this.smokeParticles[i];
  310. particle.x += particle.vx;
  311. particle.y += particle.vy;
  312. particle.life--;
  313. particle.vx *= 0.98;
  314. particle.vy *= 0.98;
  315. if (particle.life <= 0) {
  316. this.smokeParticles.splice(i, 1);
  317. }
  318. }
  319. }
  320. drawSmokeParticles(ctx) {
  321. ctx.save();
  322. for (const particle of this.smokeParticles) {
  323. const alpha = particle.life / particle.maxLife;
  324. ctx.globalAlpha = alpha * 0.7;
  325. // Diferentes colores según el tipo de humo
  326. if (particle.type === 'head') {
  327. // Humo de cabeza - más amarillento como fogata
  328. const intensity = Math.floor(255 * alpha);
  329. ctx.fillStyle = `rgb(${intensity}, ${Math.floor(intensity * 0.8)}, ${Math.floor(intensity * 0.3)})`;
  330. } else {
  331. // Humo normal - blanco
  332. ctx.fillStyle = '#ffffff';
  333. }
  334. ctx.beginPath();
  335. ctx.arc(particle.x, particle.y, particle.size * alpha, 0, Math.PI * 2);
  336. ctx.fill();
  337. }
  338. ctx.restore();
  339. }
  340. }