lyrics.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. class LyricsSystem {
  2. constructor() {
  3. this.lyrics = [];
  4. this.currentLyricIndex = -1;
  5. this.showLyrics = true; // Mostrar letras por defecto
  6. this.font = null;
  7. this.songDuration = 189; // 3:09 en segundos
  8. this.isGameOver = false;
  9. this.gameOverScreen = false;
  10. this.fadeOpacity = 1.0;
  11. this.fadeDirection = 1; // 1 para aparecer, -1 para desaparecer
  12. // Offset de tiempo para ajustar sincronización (en segundos)
  13. // Valor positivo = letras aparecen ANTES
  14. // Valor negativo = letras aparecen DESPUÉS
  15. this.timeOffset = 0.25;
  16. // Configuración de texto
  17. this.maxLineWidth = 280; // Ancho máximo de línea en píxeles
  18. this.lineHeight = 18; // Altura entre líneas
  19. // Cargar la fuente personalizada
  20. this.loadCustomFont();
  21. // Parsear las letras desde el archivo
  22. this.parseLyrics();
  23. }
  24. loadCustomFont() {
  25. this.font = new FontFace('CustomFont', 'url(assets/font.ttf)');
  26. this.font.load().then(() => {
  27. document.fonts.add(this.font);
  28. }).catch(err => {
  29. console.warn('No se pudo cargar la fuente personalizada:', err);
  30. });
  31. }
  32. parseLyrics() {
  33. // Letras en romaji usando los tiempos exactos del archivo
  34. const lyricsData = [
  35. { time: 2.8, text: "mogumogu" },
  36. { time: 6.4, text: "shinra banshou no akusenkutou mo mayoneizu kaketara daitai oishiku naru?" },
  37. { time: 13.3, text: "kimi ga naite mo onaka wa suku yo shouka dekinakatta \"gomen gomen\"" },
  38. { time: 19.8, text: "sui mo amai mo katte ni tabete gomeiwaku okakeshite imasu" },
  39. { time: 26.7, text: "pakuchii na okite hen na aji no juusu kokoro wo mu ni shite nomikomimasu" },
  40. { time: 33.3, text: "aa konton jouhou kata no resutoran de usoppachi no menyuu ni ocha koboshite" },
  41. { time: 39.8, text: "kimi to mogumogu (mogumogu) mogumogu (mogumogu)" },
  42. { time: 43, text: "juugeki-sen no mannaka de mogumogu (ugya~)" },
  43. { time: 46, text: "chimi mouryou harapeko no mure manpuku ni naru mirai wo negatteiru yo" },
  44. { time: 53, text: "mogumogu (mogumogu) mogumogu (mogumogu) sekai ga owaru mae ni" },
  45. { time: 59, text: "mogumogu yamii maji kami yamuyamu ari no manma" },
  46. { time: 66, text: "mogumogu yamii Magic Coming konoyo wo ajiwaun da umauma" },
  47. { time: 67, text: "sorry i got lazy lmfao" },
  48. // { time: 43.2, text: "riron busou no mirufiiyu sando (sando)" },
  49. // { time: 44.9, text: "tokumei kibou no ourora sousu (sousu)" },
  50. // { time: 48.3, text: "oozappa na ajitsuke oome ni mite yo oishiku nattara \"ok ok.\"" },
  51. // { time: 51.1, text: "ii mon warui mon ippai tabete yogoreta kuchimoto nuguimasu" },
  52. // { time: 55.2, text: "onigiri piza keiki gouka na furu kousu wana wo utagai ki wo tsukemasu" },
  53. // { time: 58.6, text: "aa mousou ryuugen higo no baikingu de dokuiri to zeppin ryouri yoriwakete" },
  54. // { time: 61.2, text: "kimi to mogumogu (mogumogu) mogumogu (mogumogu)" },
  55. // { time: 64.5, text: "desugeimu no kyoushitsu de mogumogu (ugya~)" },
  56. // { time: 67.9, text: "chimi mouryou harapeko no mure naka no ii kimi to ikinokoretara ii na" },
  57. // { time: 162, text: "mogumogu (mogumogu) mogumogu (mogumogu) suu nen-go mo kono basho de" },
  58. // { time: 74.6, text: "mogumogu yamii maji kami kon'ya no bangohan wa" },
  59. // { time: 78, text: "mogumogu yamii Magic Coming tsugi wa omae no ban da" },
  60. // { time: 81.4, text: "mogumogu yamii maji kami yamuyamu ari no manma" },
  61. // { time: 84.8, text: "mogumogu yamii Magic Coming konoyo wo ajiwaun da umauma" },
  62. { time: 162, text: "mogumogu mogumogu mogumogu mogumogu mogumogu mogumogu mogumogu yamii" },
  63. { time: 168, text: "mogumogu mogumogu mogumogu mogumogu mogumogu mogumogu mogumogu yamii" },
  64. { time: 175, text: "mogumogu mogumogu mogumogu mogumogu mogumogu mogumogu mogumogu yamii" },
  65. { time: 181, text: "mogumogu mogumogu mogumogu mogumogu"},
  66. { time: 187, text: "yamii yamii" }
  67. ];
  68. this.lyrics = lyricsData;
  69. }
  70. update(currentTime) {
  71. // Aplicar offset de tiempo
  72. const adjustedTime = currentTime + this.timeOffset;
  73. // Verificar si la canción ha terminado
  74. if (adjustedTime >= this.songDuration && !this.isGameOver) {
  75. this.isGameOver = true;
  76. this.gameOverScreen = true;
  77. return;
  78. }
  79. // Actualizar letra actual basada en el tiempo ajustado
  80. this.updateCurrentLyric(adjustedTime);
  81. }
  82. updateCurrentLyric(currentTime) {
  83. let newIndex = -1;
  84. for (let i = 0; i < this.lyrics.length; i++) {
  85. if (currentTime >= this.lyrics[i].time) {
  86. newIndex = i;
  87. } else {
  88. break;
  89. }
  90. }
  91. if (newIndex !== this.currentLyricIndex) {
  92. this.currentLyricIndex = newIndex;
  93. }
  94. }
  95. draw(ctx, canvas) {
  96. if (!this.showLyrics && this.fadeOpacity <= 0) return;
  97. // Actualizar opacidad para transición suave
  98. if (this.showLyrics && this.fadeOpacity < 1.0) {
  99. this.fadeOpacity += 0.05;
  100. } else if (!this.showLyrics && this.fadeOpacity > 0) {
  101. this.fadeOpacity -= 0.05;
  102. }
  103. // Dibujar letra actual
  104. if (this.currentLyricIndex >= 0 && this.currentLyricIndex < this.lyrics.length) {
  105. const currentLyric = this.lyrics[this.currentLyricIndex];
  106. // Configurar fuente
  107. ctx.font = '14px CustomFont, Arial, sans-serif';
  108. ctx.fillStyle = `rgba(255, 255, 255, ${this.fadeOpacity})`;
  109. ctx.strokeStyle = `rgba(0, 0, 0, ${this.fadeOpacity})`;
  110. ctx.lineWidth = 3;
  111. ctx.textAlign = 'center';
  112. // Dividir texto en líneas
  113. const lines = this.wrapText(currentLyric.text, ctx, this.maxLineWidth);
  114. // Calcular posición Y inicial
  115. const totalHeight = lines.length * this.lineHeight;
  116. const startY = canvas.height - 180 - (totalHeight - this.lineHeight);
  117. // Agregar sombra
  118. ctx.shadowColor = `rgba(0, 0, 0, ${this.fadeOpacity * 0.8})`;
  119. ctx.shadowBlur = 4;
  120. ctx.shadowOffsetX = 2;
  121. ctx.shadowOffsetY = 2;
  122. // Dibujar cada línea
  123. lines.forEach((line, index) => {
  124. const x = canvas.width / 2;
  125. const y = startY + (index * this.lineHeight);
  126. ctx.strokeText(line, x, y);
  127. ctx.fillText(line, x, y);
  128. });
  129. // Resetear sombra
  130. ctx.shadowColor = 'transparent';
  131. ctx.shadowBlur = 0;
  132. ctx.shadowOffsetX = 0;
  133. ctx.shadowOffsetY = 0;
  134. }
  135. }
  136. toggleLyrics() {
  137. this.showLyrics = !this.showLyrics;
  138. this.fadeDirection = this.showLyrics ? 1 : -1;
  139. }
  140. // Método para ajustar offset de tiempo
  141. // seconds > 0: letras aparecen ANTES (más temprano)
  142. // seconds < 0: letras aparecen DESPUÉS (más tarde)
  143. adjustTimeOffset(seconds) {
  144. this.timeOffset += seconds;
  145. console.log(`Offset de tiempo ajustado a: ${this.timeOffset} segundos`);
  146. console.log(`Positivo = letras ANTES, Negativo = letras DESPUÉS`);
  147. }
  148. // Método para establecer offset de tiempo específico
  149. setTimeOffset(seconds) {
  150. this.timeOffset = seconds;
  151. console.log(`Offset de tiempo establecido a: ${this.timeOffset} segundos`);
  152. }
  153. // Método para dividir texto en líneas
  154. wrapText(text, ctx, maxWidth) {
  155. const words = text.split(' ');
  156. const lines = [];
  157. let currentLine = words[0];
  158. for (let i = 1; i < words.length; i++) {
  159. const word = words[i];
  160. const width = ctx.measureText(currentLine + ' ' + word).width;
  161. if (width < maxWidth) {
  162. currentLine += ' ' + word;
  163. } else {
  164. lines.push(currentLine);
  165. currentLine = word;
  166. }
  167. }
  168. lines.push(currentLine);
  169. return lines;
  170. }
  171. // Método para desarrollo: simular fin de canción
  172. simulateGameOver() {
  173. this.isGameOver = true;
  174. this.gameOverScreen = true;
  175. }
  176. // Método para reiniciar el juego
  177. reset() {
  178. this.currentLyricIndex = -1;
  179. this.isGameOver = false;
  180. this.gameOverScreen = false;
  181. this.showLyrics = true;
  182. this.fadeOpacity = 1.0;
  183. this.timeOffset = 0; // Resetear offset
  184. }
  185. }