| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832 |
- class Game {
- constructor() {
- this.canvas = document.getElementById('gameCanvas');
- this.ctx = this.canvas.getContext('2d');
- this.ctx.imageSmoothingEnabled = false;
-
- this.spriteSheet = new Image();
- this.backgroundMusic = new Audio();
-
- this.player = null;
- this.backgroundElements = [];
- this.backgroundLayers = {
- far: [], // Elementos muy lejanos (nubes, montañas)
- mid: [], // Elementos medios (árboles, edificios)
- near: [] // Elementos cercanos (decoraciones, elementos interactivos)
- };
-
- // Sistema de generación de nubes mejorado
- this.cloudSystem = new CloudSystem(this.canvas);
-
- // Sistema de objetos de fondo
- this.backgroundObjectSystem = new BackgroundObjectSystem(this.canvas, this);
-
- // Sistema de letras sincronizadas
- this.lyricsSystem = new LyricsSystem();
-
- // Estado del juego
- this.gameState = 'playing'; // 'playing', 'gameOver', 'paused'
-
- // Definir tipos de elementos con sus configuraciones
- this.elementTypes = {
- floor: {
- renderMethod: 'sprite',
- defaultSpriteCoords: { x: 2, y: 430, w: 32, h: 32 },
- layer: 'near'
- },
- decoration: {
- renderMethod: 'sprite',
- defaultSpriteCoords: { x: 137, y: 104, w: 35, h: 26 },
- layer: 'near'
- },
- background: {
- renderMethod: 'color',
- color: '#4A90E2',
- layer: 'far'
- },
- text: {
- renderMethod: 'text',
- font: '12px Arial',
- color: 'white',
- align: 'center',
- layer: 'near'
- },
- sprite: {
- renderMethod: 'sprite',
- layer: 'near'
- },
- cloud: {
- renderMethod: 'sprite',
- layer: 'far',
- defaultSpriteCoords: { x: 231, y: 112, w: 287, h: 148 },
- autoMove: true,
- moveSpeed: 0.5
- },
- backgroundObject: {
- renderMethod: 'sprite',
- layer: 'mid',
- autoMove: true,
- moveSpeed: 0.1,
- // Definir las variantes de sprites disponibles
- spriteVariants: [
- { x: 546, y: 264, w: 30, h: 21 },
- { x: 546, y: 286, w: 30, h: 21 },
- { x: 546, y: 308, w: 30, h: 25 },
- { x: 546, y: 334, w: 30, h: 24 },
- { x: 546, y: 359, w: 30, h: 21 },
- { x: 545, y: 382, w: 31, h: 29 },
- { x: 516, y: 264, w: 29, h: 17 },
- { x: 516, y: 282, w: 29, h: 21 },
- { x: 516, y: 304, w: 29, h: 18 },
- { x: 516, y: 323, w: 29, h: 18 },
- { x: 516, y: 342, w: 29, h: 19 },
- { x: 516, y: 362, w: 29, h: 19 },
- { x: 516, y: 382, w: 28, h: 29 },
- { x: 491, y: 270, w: 24, h: 26 },
- { x: 491, y: 297, w: 24, h: 26 },
- { x: 491, y: 324, w: 24, h: 26 },
- { x: 491, y: 353, w: 24, h: 26 },
- { x: 489, y: 382, w: 26, h: 29 },
- { x: 465, y: 310, w: 25, h: 23 },
- { x: 465, y: 334, w: 25, h: 23 },
- { x: 465, y: 358, w: 25, h: 23 },
- { x: 465, y: 382, w: 11, h: 29 },
- { x: 477, y: 382, w: 11, h: 29 }
- ]
- }
- };
-
- this.init();
- }
-
- init() {
- // Load assets
- this.spriteSheet.src = 'assets/sprites.png';
- this.backgroundMusic.src = 'assets/music.mp3';
-
- // Configure audio
- this.backgroundMusic.loop = true;
- this.backgroundMusic.volume = 0.05;
-
- // Create sprite processor for color key-out
- this.spriteProcessor = new SpriteProcessor();
-
- // Wait for assets to load
- Promise.all([
- new Promise(resolve => this.spriteSheet.onload = resolve),
- new Promise(resolve => this.backgroundMusic.oncanplaythrough = resolve)
- ]).then(() => {
- // Process sprite sheet to remove #00ffff background
- this.processedSpriteSheet = this.spriteProcessor.processSpriteSheet(this.spriteSheet, '#00ffff');
- this.processedSpriteSheet.onload = () => {
- this.startGame();
- };
- });
-
- // Handle audio context (for browsers that require user interaction)
- this.canvas.addEventListener('click', () => {
- if (this.backgroundMusic.paused && this.gameState === 'playing') {
- this.backgroundMusic.play().catch(e => console.log('Audio play failed:', e));
- }
- });
-
- // Agregar controles de teclado
- this.setupKeyboardControls();
- }
-
- // Método para agregar elementos de forma dinámica
- addElement(type, x, y, w, h, options = {}) {
- const elementConfig = this.elementTypes[type];
- const layer = options.layer || elementConfig?.layer || 'near';
-
- const element = {
- type,
- x,
- y,
- w,
- h,
- ...options
- };
-
- // Agregar a la capa correspondiente
- if (this.backgroundLayers[layer]) {
- this.backgroundLayers[layer].push(element);
- } else {
- this.backgroundElements.push(element);
- }
-
- return element;
- }
-
- // Método para agregar elementos de piso
- addFloorTile(x, y) {
- return this.addElement('floor', x, y, 32, 32);
- }
-
- // Método para agregar decoraciones
- addDecoration(x, y, w = 35, h = 26) {
- return this.addElement('decoration', x, y, w, h);
- }
-
- // Método para agregar elementos de color sólido
- addColorElement(x, y, w, h, color, layer = 'far') {
- return this.addElement('background', x, y, w, h, { color, layer });
- }
-
- // Método para agregar texto
- addText(x, y, text, options = {}) {
- return this.addElement('text', x, y, 0, 0, { text, ...options });
- }
-
- // Método para agregar sprites personalizados
- addSprite(x, y, w, h, spriteCoords, options = {}) {
- return this.addElement('sprite', x, y, w, h, {
- spriteCoords,
- ...options
- });
- }
-
- // Método para agregar cualquier sprite con coordenadas específicas
- addCustomSprite(type, x, y, w, h, spriteCoords, options = {}) {
- return this.addElement(type, x, y, w, h, {
- spriteCoords,
- ...options
- });
- }
-
- startGame() {
- // Create player
- this.player = new Player(144, 176, this.processedSpriteSheet);
-
- // Crear elementos de fondo usando los nuevos métodos
- // Piso
- for (let i = 0; i < 10; i++) {
- this.addFloorTile(i * 32, 208);
- }
-
- // Inicializar sistema de nubes
- this.cloudSystem.init(this.processedSpriteSheet, this.backgroundLayers.far);
-
- // Inicializar sistema de objetos de fondo
- this.backgroundObjectSystem.init(this.processedSpriteSheet, this.backgroundLayers.mid);
-
- // Macarons
- this.addSprite(10, 183, 35, 26, { x: 137, y: 104, w: 35, h: 26 });
- this.addSprite(42, 183, 35, 26, { x: 173, y: 104, w: 35, h: 26 });
- this.addSprite(74, 183, 35, 26, { x: 137, y: 131, w: 35, h: 26 });
- this.addSprite(106, 183, 35, 26, { x: 173, y: 131, w: 35, h: 26 });
- // Candybar
- this.addSprite(200, 130, 48, 79, { x: 179, y: 438, w: 48, h: 79 });
- // Cake
- this.addSprite(260, 134, 100, 75, { x: 461, y: 414, w: 100, h: 75 });
- // Cat
- this.addSprite(185, 187, 20, 23, { x: 235, y: 87, w: 20, h: 23 });
- this.addSprite(200, 187, 20, 23, { x: 389, y: 87, w: 16, h: 23 });
-
- this.gameLoop();
- }
-
- update() {
- if (this.gameState !== 'playing') return;
-
- if (this.player) {
- this.player.update();
- }
-
- // Actualizar sistema de nubes
- this.cloudSystem.update();
-
- // Actualizar sistema de objetos de fondo
- this.backgroundObjectSystem.update();
-
- // Actualizar sistema de letras
- if (!this.backgroundMusic.paused) {
- this.lyricsSystem.update(this.backgroundMusic.currentTime);
- }
- }
-
- // Método para renderizar un elemento individual
- renderElement(element) {
- const elementConfig = this.elementTypes[element.type];
- if (!elementConfig) {
- console.warn(`Tipo de elemento desconocido: ${element.type}`);
- return;
- }
-
- switch (elementConfig.renderMethod) {
- case 'sprite':
- this.renderSpriteElement(element, elementConfig);
- break;
- case 'color':
- this.renderColorElement(element, elementConfig);
- break;
- case 'text':
- this.renderTextElement(element, elementConfig);
- break;
- default:
- console.warn(`Método de renderizado desconocido: ${elementConfig.renderMethod}`);
- }
- }
-
- renderSpriteElement(element, config) {
- // Usar coordenadas personalizadas si están especificadas, sino usar las por defecto
- const spriteCoords = element.spriteCoords || config.defaultSpriteCoords;
-
- if (!spriteCoords) {
- console.warn(`No se especificaron coordenadas de sprite para elemento tipo: ${element.type}`);
- return;
- }
-
- // Si el elemento tiene rotación, aplicar transformaciones
- if (element.rotation) {
- this.ctx.save();
-
- // Calcular el centro del sprite para la rotación
- const centerX = element.x + element.w / 2;
- const centerY = element.y + element.h / 2;
-
- // Aplicar transformaciones
- this.ctx.translate(centerX, centerY);
- this.ctx.rotate(element.rotation);
- this.ctx.translate(-centerX, -centerY);
- }
-
- this.ctx.drawImage(
- this.processedSpriteSheet,
- spriteCoords.x, spriteCoords.y, spriteCoords.w, spriteCoords.h,
- element.x, element.y, element.w, element.h
- );
-
- // Restaurar el contexto si se aplicó rotación
- if (element.rotation) {
- this.ctx.restore();
- }
- }
-
- renderColorElement(element, config) {
- const color = element.color || config.color;
- this.ctx.fillStyle = color;
- this.ctx.fillRect(element.x, element.y, element.w, element.h);
- }
-
- renderTextElement(element, config) {
- this.ctx.fillStyle = element.color || config.color;
- this.ctx.font = element.font || config.font;
- this.ctx.textAlign = element.align || config.align;
- this.ctx.fillText(element.text, element.x, element.y);
- }
-
- draw() {
- // Clear canvas con degradado
- const gradient = this.ctx.createLinearGradient(0, 0, 0, this.canvas.height);
- gradient.addColorStop(0, '#d9e6ff'); // Color superior
- gradient.addColorStop(1, '#eee5ff'); // Color inferior
-
- this.ctx.fillStyle = gradient;
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
-
- // Renderizar elementos por capas (de lejos a cerca)
- // Capa lejana (nubes, montañas)
- this.backgroundLayers.far.forEach(element => {
- this.renderElement(element);
- });
-
- // Capa media (árboles, edificios)
- this.backgroundLayers.mid.forEach(element => {
- this.renderElement(element);
- });
-
- // Capa cercana (decoraciones, elementos interactivos)
- this.backgroundLayers.near.forEach(element => {
- this.renderElement(element);
- });
-
- // Elementos legacy (para compatibilidad)
- this.backgroundElements.forEach(element => {
- this.renderElement(element);
- });
-
- // Draw player
- if (this.player) {
- this.player.draw(this.ctx);
- }
-
- // Dibujar letras si están activadas
- this.lyricsSystem.draw(this.ctx, this.canvas);
-
- // Draw instructions if music hasn't started
- if (this.backgroundMusic.paused && this.gameState === 'playing') {
- this.ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
-
- this.ctx.fillStyle = 'white';
- this.ctx.font = '8px Courier New';
- this.ctx.textAlign = 'center';
- this.ctx.fillText('Click para iniciar música', 160, 120);
- this.ctx.fillText('AD o ← → para moverte', 160, 130);
- this.ctx.fillText('L - Mostrar/Ocultar letras', 160, 140);
- this.ctx.fillText('R - Reiniciar juego', 160, 150);
- // this.ctx.fillText('Ctrl+G - Simular fin de canción', 160, 160);
- }
-
- // Dibujar pantalla de fin de juego
- if (this.lyricsSystem.gameOverScreen) {
- this.drawGameOverScreen();
- }
- }
-
- gameLoop() {
- this.update();
- this.draw();
- requestAnimationFrame(() => this.gameLoop());
- }
-
- setupKeyboardControls() {
- document.addEventListener('keydown', (e) => {
- switch(e.key.toLowerCase()) {
- case 'l':
- this.lyricsSystem.toggleLyrics();
- break;
- case 'r':
- this.restartGame();
- break;
- case 'g':
- // Shortcut para desarrollo: simular fin de canción
- if (e.ctrlKey) {
- this.lyricsSystem.simulateGameOver();
- }
- break;
- }
- });
- }
-
- drawGameOverScreen() {
- // Fondo negro translúcido
- this.ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
-
- // Texto de fin de juego
- this.ctx.fillStyle = 'white';
- this.ctx.font = '16px Courier New';
- this.ctx.textAlign = 'center';
-
- this.ctx.fillText('¡Canción terminada!', 160, 100);
- this.ctx.fillText('¿Quieres jugar otra vez?', 160, 120);
-
- // Botones
- this.ctx.font = '12px Courier New';
- this.ctx.fillText('Presiona R para reiniciar', 160, 150);
- this.ctx.fillText('o recarga la página', 160, 165);
- }
-
- restartGame() {
- // Detener música
- this.backgroundMusic.pause();
- this.backgroundMusic.currentTime = 0;
-
- // Reiniciar sistemas
- this.lyricsSystem.reset();
-
- // Reiniciar estado del juego
- this.gameState = 'playing';
-
- // Limpiar elementos de fondo
- this.backgroundLayers.far = [];
- this.backgroundLayers.mid = [];
- this.backgroundLayers.near = [];
- this.backgroundElements = [];
-
- // Reiniciar sistemas
- this.cloudSystem.init(this.processedSpriteSheet, this.backgroundLayers.far);
- this.backgroundObjectSystem.init(this.processedSpriteSheet, this.backgroundLayers.mid);
-
- // Reiniciar jugador
- this.player = new Player(144, 176, this.processedSpriteSheet);
-
- // Recrear elementos de fondo
- this.recreateBackgroundElements();
- }
-
- recreateBackgroundElements() {
- // Piso
- for (let i = 0; i < 10; i++) {
- this.addFloorTile(i * 32, 208);
- }
-
- // Macarons
- this.addSprite(10, 183, 35, 26, { x: 137, y: 104, w: 35, h: 26 });
- this.addSprite(42, 183, 35, 26, { x: 173, y: 104, w: 35, h: 26 });
- this.addSprite(74, 183, 35, 26, { x: 137, y: 131, w: 35, h: 26 });
- this.addSprite(106, 183, 35, 26, { x: 173, y: 131, w: 35, h: 26 });
- // Candybar
- this.addSprite(200, 130, 48, 79, { x: 179, y: 438, w: 48, h: 79 });
- // Cake
- this.addSprite(260, 134, 100, 75, { x: 461, y: 414, w: 100, h: 75 });
- // Cat
- this.addSprite(185, 187, 20, 23, { x: 235, y: 87, w: 20, h: 23 });
- this.addSprite(200, 187, 20, 23, { x: 389, y: 87, w: 16, h: 23 });
- }
- }
- // Clase para manejar el sistema de nubes de forma modular
- class CloudSystem {
- constructor(canvas) {
- this.canvas = canvas;
- this.spriteSheet = null;
- this.cloudLayer = null;
- this.frameCount = 0;
-
- // Configuración simplificada
- this.config = {
- spawnRate: 0.1, // Probabilidad de spawn por frame (aumentada)
- maxClouds: 2, // Máximo número de nubes en pantalla (aumentado)
- speedRange: { min: 0.1, max: 0.15 }, // Velocidad de movimiento
- yRange: { min: 1, max: 120 }, // Rango de altura
- spriteCoords: { x: 231, y: 112, w: 287, h: 148 },
- size: { w: 287, h: 148 },
- minSpacing: 100, // Espaciado mínimo entre nubes (reducido)
- minTimeBetweenSpawns: 60 // Frames mínimos entre apariciones (reducido)
- };
-
- this.lastSpawnTime = 0;
- }
-
- init(spriteSheet, cloudLayer) {
- this.spriteSheet = spriteSheet;
- this.cloudLayer = cloudLayer;
-
- // Generar nubes iniciales para que el fondo no se vea vacío
- this.spawnInitialClouds();
- }
-
- spawnInitialClouds() {
- // Generar 2-3 nubes iniciales en posiciones aleatorias
- const initialCount = Math.floor(Math.random() * 2) + 2;
-
- for (let i = 0; i < initialCount; i++) {
- const x = Math.random() * this.canvas.width;
- const y = this.getRandomY();
- const speed = this.getRandomSpeed();
-
- this.createCloud(x, y, speed);
- }
- }
-
- update() {
- this.frameCount++;
-
- // Limpiar nubes que salieron de la pantalla
- this.cleanupOffscreenClouds();
-
- // Intentar generar nueva nube
- if (this.shouldSpawnCloud()) {
- this.spawnCloud();
- }
-
- // Mover nubes existentes
- this.moveClouds();
- }
-
- shouldSpawnCloud() {
- // Verificar tiempo mínimo entre apariciones
- if (this.frameCount - this.lastSpawnTime < this.config.minTimeBetweenSpawns) {
- return false;
- }
-
- // Verificar número máximo de nubes
- const cloudCount = this.cloudLayer.filter(e => e.type === 'cloud').length;
- if (cloudCount >= this.config.maxClouds) {
- return false;
- }
-
- // Verificar probabilidad de spawn
- if (Math.random() > this.config.spawnRate) {
- return false;
- }
-
- // Verificar espaciado mínimo
- const hasSpacing = this.hasEnoughSpacing();
-
- // Debug: mostrar información cada 60 frames (1 segundo)
- if (this.frameCount % 60 === 0) {
- console.log(`Clouds: ${cloudCount}/${this.config.maxClouds}, Spacing: ${hasSpacing}, Time: ${this.frameCount - this.lastSpawnTime}`);
- }
-
- return hasSpacing;
- }
-
- hasEnoughSpacing() {
- const existingClouds = this.cloudLayer.filter(e => e.type === 'cloud');
-
- // Si no hay nubes, siempre se puede generar
- if (existingClouds.length === 0) {
- return true;
- }
-
- // Verificar que no haya nubes muy cerca del borde derecho
- for (const cloud of existingClouds) {
- const distanceFromRight = this.canvas.width - cloud.x;
- if (distanceFromRight < this.config.minSpacing) {
- return false;
- }
- }
-
- return true;
- }
-
- spawnCloud() {
- const y = this.getRandomY();
- const speed = this.getRandomSpeed();
-
- // Generar desde la derecha de la pantalla
- const x = this.canvas.width;
-
- this.createCloud(x, y, speed);
- this.lastSpawnTime = this.frameCount;
-
- console.log(`Nueva nube generada en (${x}, ${y}) con velocidad ${speed}`);
- }
-
- createCloud(x, y, speed) {
- const cloud = {
- type: 'cloud',
- x: x,
- y: y,
- w: this.config.size.w,
- h: this.config.size.h,
- spriteCoords: this.config.spriteCoords,
- moveSpeed: speed,
- layer: 'far'
- };
-
- this.cloudLayer.push(cloud);
- }
-
- moveClouds() {
- this.cloudLayer.forEach(cloud => {
- if (cloud.type === 'cloud' && cloud.moveSpeed) {
- cloud.x -= cloud.moveSpeed;
- }
- });
- }
-
- cleanupOffscreenClouds() {
- for (let i = this.cloudLayer.length - 1; i >= 0; i--) {
- const element = this.cloudLayer[i];
- if (element.type === 'cloud' && element.x + element.w < 0) {
- this.cloudLayer.splice(i, 1);
- }
- }
- }
-
- getRandomY() {
- return Math.random() *
- (this.config.yRange.max - this.config.yRange.min) +
- this.config.yRange.min;
- }
-
- getRandomSpeed() {
- return Math.random() *
- (this.config.speedRange.max - this.config.speedRange.min) +
- this.config.speedRange.min;
- }
- }
- // Clase para manejar el sistema de objetos de fondo
- class BackgroundObjectSystem {
- constructor(canvas, game) {
- this.canvas = canvas;
- this.game = game;
- this.spriteSheet = null;
- this.objectLayer = null;
- this.frameCount = 0;
-
- // Configuración para objetos de fondo
- this.config = {
- spawnRate: 0.3, // Probabilidad de spawn por frame (aumentada)
- maxObjects: 300, // Máximo número de objetos en pantalla (aumentado)
- speedRange: { min: 0.05, max: 0.1 }, // Velocidad de movimiento
- yRange: { min: 10, max: 180 }, // Rango de altura (expandido)
- minSpacing: 80, // Espaciado mínimo entre grupos (reducido)
- minTimeBetweenSpawns: 60, // Frames mínimos entre apariciones (reducido)
- groupSize: { min: 15, max: 25 }, // Tamaño del grupo de objetos (reducido)
- groupSpacing: { min: 5, max: 40 } // Espaciado entre objetos en el grupo (ajustado)
- };
-
- this.lastSpawnTime = 0;
- }
-
- init(spriteSheet, objectLayer) {
- this.spriteSheet = spriteSheet;
- this.objectLayer = objectLayer;
-
- // Generar objetos iniciales
- this.spawnInitialObjects();
- }
-
- spawnInitialObjects() {
- // Generar 3-5 grupos iniciales para llenar la pantalla
- const initialGroups = Math.floor(Math.random() * 3) + 3;
-
- for (let i = 0; i < initialGroups; i++) {
- // Distribuir los grupos iniciales por toda la pantalla
- const x = (i * this.canvas.width / initialGroups) + (Math.random() * 100);
- const y = this.getRandomY();
- const speed = this.getRandomSpeed();
-
- this.createObjectGroup(x, y, speed);
- }
- }
-
- update() {
- this.frameCount++;
-
- // Limpiar objetos que salieron de la pantalla
- this.cleanupOffscreenObjects();
-
- // Intentar generar nuevo grupo
- if (this.shouldSpawnObjectGroup()) {
- this.spawnObjectGroup();
- }
-
- // Mover objetos existentes
- this.moveObjects();
- }
-
- shouldSpawnObjectGroup() {
- // Verificar tiempo mínimo entre apariciones
- if (this.frameCount - this.lastSpawnTime < this.config.minTimeBetweenSpawns) {
- return false;
- }
-
- // Verificar número máximo de objetos
- const objectCount = this.objectLayer.filter(e => e.type === 'backgroundObject').length;
- if (objectCount >= this.config.maxObjects) {
- return false;
- }
-
- // Verificar probabilidad de spawn
- if (Math.random() > this.config.spawnRate) {
- return false;
- }
-
- // Verificar espaciado mínimo
- const hasSpacing = this.hasEnoughSpacing();
-
- return hasSpacing;
- }
-
- hasEnoughSpacing() {
- const existingObjects = this.objectLayer.filter(e => e.type === 'backgroundObject');
-
- // Si no hay objetos, siempre se puede generar
- if (existingObjects.length === 0) {
- return true;
- }
-
- // Verificar que no haya objetos muy cerca del borde derecho
- for (const obj of existingObjects) {
- const distanceFromRight = this.canvas.width - obj.x;
- if (distanceFromRight < this.config.minSpacing) {
- return false;
- }
- }
-
- return true;
- }
-
- spawnObjectGroup() {
- const y = this.getRandomY();
- const speed = this.getRandomSpeed();
-
- // Generar desde la derecha de la pantalla
- const x = this.canvas.width;
-
- this.createObjectGroup(x, y, speed);
- this.lastSpawnTime = this.frameCount;
-
- console.log(`Nuevo grupo de objetos generado en (${x}, ${y}) con velocidad ${speed}`);
- }
-
- createObjectGroup(x, y, speed) {
- const groupSize = Math.floor(Math.random() *
- (this.config.groupSize.max - this.config.groupSize.min + 1)) +
- this.config.groupSize.min;
-
- // Obtener las variantes de sprites desde elementTypes
- const spriteVariants = this.game.elementTypes.backgroundObject.spriteVariants;
-
- let currentX = x;
-
- for (let i = 0; i < groupSize; i++) {
- // Seleccionar sprite aleatorio
- const spriteVariant = spriteVariants[Math.floor(Math.random() * spriteVariants.length)];
-
- // Aplicar rotación aleatoria
- const rotation = (Math.random() - 0.5) * 0.8; // ±0.4 radianes
-
- // Aplicar dispersión vertical aleatoria
- const yOffset = (Math.random() - 0.5) * 40; // ±20 píxeles
-
- const object = {
- type: 'backgroundObject',
- x: currentX,
- y: y + yOffset,
- w: spriteVariant.w,
- h: spriteVariant.h,
- spriteCoords: spriteVariant,
- moveSpeed: speed,
- rotation: rotation,
- layer: 'mid'
- };
-
- this.objectLayer.push(object);
-
- // Calcular siguiente posición con espaciado aleatorio
- const spacing = Math.random() *
- (this.config.groupSpacing.max - this.config.groupSpacing.min) +
- this.config.groupSpacing.min;
- currentX += spriteVariant.w + spacing;
- }
- }
-
- moveObjects() {
- this.objectLayer.forEach(obj => {
- if (obj.type === 'backgroundObject' && obj.moveSpeed) {
- obj.x -= obj.moveSpeed;
- }
- });
- }
-
- cleanupOffscreenObjects() {
- for (let i = this.objectLayer.length - 1; i >= 0; i--) {
- const element = this.objectLayer[i];
- if (element.type === 'backgroundObject' && element.x + element.w < 0) {
- this.objectLayer.splice(i, 1);
- }
- }
- }
-
- getRandomY() {
- return Math.random() *
- (this.config.yRange.max - this.config.yRange.min) +
- this.config.yRange.min;
- }
-
- getRandomSpeed() {
- return Math.random() *
- (this.config.speedRange.max - this.config.speedRange.min) +
- this.config.speedRange.min;
- }
- }
- // Initialize game when DOM is loaded
- window.addEventListener('DOMContentLoaded', () => {
- new Game();
- });
|